Skip to content

Commit 77acfa2

Browse files
authored
pass all upstream node:os tests, all supported node:async_hooks tests (#15802)
1 parent 9d3b461 commit 77acfa2

34 files changed

+2452
-1053
lines changed

build.zig

+27-7
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,12 @@ pub fn build(b: *Build) !void {
328328
});
329329
}
330330

331+
// zig build translate-c-headers
332+
{
333+
const step = b.step("translate-c", "Copy generated translated-c-headers.zig to zig-out");
334+
step.dependOn(&b.addInstallFile(getTranslateC(b, b.host, .Debug).getOutput(), "translated-c-headers.zig").step);
335+
}
336+
331337
// zig build enum-extractor
332338
{
333339
// const step = b.step("enum-extractor", "Extract enum definitions (invoked by a code generator)");
@@ -380,6 +386,25 @@ pub fn addMultiCheck(
380386
}
381387
}
382388

389+
fn getTranslateC(b: *Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *Step.TranslateC {
390+
const translate_c = b.addTranslateC(.{
391+
.root_source_file = b.path("src/c-headers-for-zig.h"),
392+
.target = target,
393+
.optimize = optimize,
394+
.link_libc = true,
395+
});
396+
inline for ([_](struct { []const u8, bool }){
397+
.{ "WINDOWS", translate_c.target.result.os.tag == .windows },
398+
.{ "POSIX", translate_c.target.result.os.tag != .windows },
399+
.{ "LINUX", translate_c.target.result.os.tag == .linux },
400+
.{ "DARWIN", translate_c.target.result.os.tag.isDarwin() },
401+
}) |entry| {
402+
const str, const value = entry;
403+
translate_c.defineCMacroRaw(b.fmt("{s}={d}", .{ str, @intFromBool(value) }));
404+
}
405+
return translate_c;
406+
}
407+
383408
pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
384409
const obj = b.addObject(.{
385410
.name = if (opts.optimize == .Debug) "bun-debug" else "bun",
@@ -428,13 +453,8 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
428453
addInternalPackages(b, obj, opts);
429454
obj.root_module.addImport("build_options", opts.buildOptionsModule(b));
430455

431-
const translate_plugin_api = b.addTranslateC(.{
432-
.root_source_file = b.path("./packages/bun-native-bundler-plugin-api/bundler_plugin.h"),
433-
.target = opts.target,
434-
.optimize = opts.optimize,
435-
.link_libc = true,
436-
});
437-
obj.root_module.addImport("bun-native-bundler-plugin-api", translate_plugin_api.createModule());
456+
const translate_c = getTranslateC(b, opts.target, opts.optimize);
457+
obj.root_module.addImport("translated-c-headers", translate_c.createModule());
438458

439459
return obj;
440460
}

docs/project/bindgen.md

+30-4
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ This function declaration is equivalent to:
6161
declare function add(a: number, b: number = 1): number;
6262
```
6363

64-
The code generator will provide `bun.gen.math.jsAdd`, which is the native function implementation. To pass to JavaScript, use `bun.gen.math.createAddCallback(global)`
64+
The code generator will provide `bun.gen.math.jsAdd`, which is the native
65+
function implementation. To pass to JavaScript, use
66+
`bun.gen.math.createAddCallback(global)`. JS files in `src/js/` may use
67+
`$bindgenFn("math.bind.ts", "add")` to get a handle to the implementation.
6568

6669
## Strings
6770

@@ -104,7 +107,7 @@ export const action = fn({
104107

105108
In Zig, each variant gets a number, based on the order the schema defines.
106109

107-
```
110+
```zig
108111
fn action1(a: i32) i32 {
109112
return a;
110113
}
@@ -180,16 +183,39 @@ export const add = fn({
180183
// enforce in i32 range
181184
a: t.i32.enforceRange(),
182185
// clamp to u16 range
183-
c: t.u16,
186+
b: t.u16,
184187
// enforce in arbitrary range, with a default if not provided
185-
b: t.i32.enforceRange(0, 1000).default(5),
188+
c: t.i32.enforceRange(0, 1000).default(5),
186189
// clamp to arbitrary range, or null
187190
d: t.u16.clamp(0, 10).optional,
188191
},
189192
ret: t.i32,
190193
});
191194
```
192195

196+
Various Node.js validator functions such as `validateInteger`, `validateNumber`, and more are available. Use these when implementing Node.js APIs, so the error messages match 1:1 what Node would do.
197+
198+
Unlike `enforceRange`, which is taken from WebIDL, `validate*` functions are much more strict on the input they accept. For example, Node's numerical validator check `typeof value === 'number'`, while WebIDL uses `ToNumber` for lossy conversion.
199+
200+
```ts
201+
import { t, fn } from "bindgen";
202+
203+
export const add = fn({
204+
args: {
205+
global: t.globalObject,
206+
// throw if not given a number
207+
a: t.f64.validateNumber(),
208+
// valid in i32 range
209+
a: t.i32.validateInt32(),
210+
// f64 within safe integer range
211+
b: t.f64.validateInteger(),
212+
// f64 in given range
213+
c: t.f64.validateNumber(-10000, 10000),
214+
},
215+
ret: t.i32,
216+
});
217+
```
218+
193219
## Callbacks
194220

195221
TODO
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#pragma once
2+
#include "root.h"
3+
#include "IDLTypes.h"
4+
#include "JSDOMConvertBase.h"
5+
#include "ErrorCode.h"
6+
7+
namespace Bun {
8+
9+
enum class BindgenCustomEnforceRangeKind {
10+
Node,
11+
Web,
12+
};
13+
14+
// This type implements conversion for:
15+
// - t.*.validateInteger()
16+
// - t.*.enforceRange(a, b) when A, B is not the integer's ABI size.
17+
// - t.i32.validateInt32()
18+
// - t.u32.validateUInt32()
19+
template<
20+
typename NumericType,
21+
NumericType Min,
22+
NumericType Max,
23+
BindgenCustomEnforceRangeKind Kind>
24+
struct BindgenCustomEnforceRange : WebCore::IDLType<NumericType> {
25+
};
26+
27+
}
28+
29+
static String rangeErrorString(double value, double min, double max)
30+
{
31+
return makeString("Value "_s, value, " is outside the range ["_s, min, ", "_s, max, ']');
32+
}
33+
34+
namespace WebCore {
35+
36+
template<
37+
typename NumericType,
38+
NumericType Min,
39+
NumericType Max,
40+
Bun::BindgenCustomEnforceRangeKind Kind>
41+
struct Converter<Bun::BindgenCustomEnforceRange<NumericType, Min, Max, Kind>>
42+
: DefaultConverter<Bun::BindgenCustomEnforceRange<NumericType, Min, Max, Kind>> {
43+
template<typename ExceptionThrower = DefaultExceptionThrower>
44+
static inline NumericType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, ExceptionThrower&& exceptionThrower = ExceptionThrower())
45+
{
46+
auto scope = DECLARE_THROW_SCOPE(lexicalGlobalObject.vm());
47+
ASSERT(!scope.exception());
48+
double unrestricted;
49+
if constexpr (Kind == Bun::BindgenCustomEnforceRangeKind::Node) {
50+
// In Node.js, `validateNumber`, `validateInt32`, `validateUint32`,
51+
// and `validateInteger` all start with the following
52+
//
53+
// if (typeof value !== 'number')
54+
// throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
55+
//
56+
if (!value.isNumber()) {
57+
Bun::ERR::INVALID_ARG_TYPE(scope, &lexicalGlobalObject, exceptionThrower(), "number"_s, value);
58+
return 0;
59+
}
60+
unrestricted = value.asNumber();
61+
ASSERT(!scope.exception());
62+
63+
// Node also validates that integer types are integers
64+
if constexpr (std::is_integral_v<NumericType>) {
65+
if (unrestricted != std::round(unrestricted)) {
66+
// ERR_OUT_OF_RANGE "an integer"
67+
Bun::ERR::OUT_OF_RANGE(scope, &lexicalGlobalObject, exceptionThrower(), "an integer"_s, value);
68+
return 0;
69+
}
70+
} else {
71+
// When a range is specified (what this template is implementing),
72+
// Node also throws on NaN being out of range
73+
if (std::isnan(unrestricted)) {
74+
// ERR_OUT_OF_RANGE `>= ${min} && <= ${max}`
75+
Bun::ERR::OUT_OF_RANGE(scope, &lexicalGlobalObject, exceptionThrower(), Min, Max, value);
76+
return 0;
77+
}
78+
}
79+
} else {
80+
// WebIDL uses toNumber before applying range restrictions. This
81+
// allows something like `true` to pass for `t.f64.enforceRange(-10, 10)`,
82+
// but this behavior does not appear Node's validators.
83+
unrestricted = value.toNumber(&lexicalGlobalObject);
84+
RETURN_IF_EXCEPTION(scope, 0);
85+
86+
if constexpr (std::is_integral_v<NumericType>) {
87+
if (std::isnan(unrestricted) || std::isinf(unrestricted)) {
88+
throwTypeError(&lexicalGlobalObject, scope, rangeErrorString(unrestricted, Min, Max));
89+
return 0;
90+
}
91+
92+
// IDL uses trunc to convert the double to an integer.
93+
unrestricted = trunc(unrestricted);
94+
}
95+
}
96+
97+
bool inRange = unrestricted >= Min && unrestricted <= Max;
98+
if (!inRange) {
99+
if constexpr (Kind == Bun::BindgenCustomEnforceRangeKind::Node) {
100+
Bun::ERR::OUT_OF_RANGE(scope, &lexicalGlobalObject, exceptionThrower(), Min, Max, value);
101+
} else {
102+
// WebKit range exception
103+
throwTypeError(&lexicalGlobalObject, scope, rangeErrorString(unrestricted, Min, Max));
104+
}
105+
return 0;
106+
}
107+
108+
return static_cast<NumericType>(unrestricted);
109+
}
110+
};
111+
112+
}

src/bun.js/bindings/ErrorCode.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ WTF::String ERR_OUT_OF_RANGE(JSC::ThrowScope& scope, JSC::JSGlobalObject* global
302302

303303
namespace ERR {
304304

305-
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_type, JSC::JSValue val_actual_value)
305+
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value)
306306
{
307-
auto arg_kind = String(arg_name).startsWith("options."_s) ? "property"_s : "argument"_s;
307+
auto arg_kind = arg_name.startsWith("options."_s) ? "property"_s : "argument"_s;
308308
auto ty_first_char = expected_type[0];
309309
auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s;
310310

@@ -315,7 +315,7 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO
315315
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message));
316316
return {};
317317
}
318-
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, WTF::ASCIILiteral expected_type, JSC::JSValue val_actual_value)
318+
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value)
319319
{
320320
auto arg_name = val_arg_name.toWTFString(globalObject);
321321
RETURN_IF_EXCEPTION(throwScope, {});
@@ -332,7 +332,7 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO
332332
return {};
333333
}
334334

335-
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, size_t lower, size_t upper, JSC::JSValue actual)
335+
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, double lower, double upper, JSC::JSValue actual)
336336
{
337337
auto lowerStr = jsNumber(lower).toWTFString(globalObject);
338338
auto upperStr = jsNumber(upper).toWTFString(globalObject);
@@ -343,7 +343,7 @@ JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObjec
343343
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message));
344344
return {};
345345
}
346-
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t lower, size_t upper, JSC::JSValue actual)
346+
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, double lower, double upper, JSC::JSValue actual)
347347
{
348348
auto arg_name = arg_name_val.toWTFString(globalObject);
349349
RETURN_IF_EXCEPTION(throwScope, {});
@@ -356,7 +356,7 @@ JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObjec
356356
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message));
357357
return {};
358358
}
359-
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t bound_num, Bound bound, JSC::JSValue actual)
359+
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, double bound_num, Bound bound, JSC::JSValue actual)
360360
{
361361
auto arg_name = arg_name_val.toWTFString(globalObject);
362362
RETURN_IF_EXCEPTION(throwScope, {});

src/bun.js/bindings/ErrorCode.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ enum Bound {
7575

7676
namespace ERR {
7777

78-
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_type, JSC::JSValue val_actual_value);
79-
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, WTF::ASCIILiteral expected_type, JSC::JSValue val_actual_value);
80-
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, size_t lower, size_t upper, JSC::JSValue actual);
81-
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, size_t lower, size_t upper, JSC::JSValue actual);
82-
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t bound_num, Bound bound, JSC::JSValue actual);
78+
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value);
79+
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value);
80+
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, double lower, double upper, JSC::JSValue actual);
81+
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, double lower, double upper, JSC::JSValue actual);
82+
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, double bound_num, Bound bound, JSC::JSValue actual);
8383
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, const WTF::String& msg, JSC::JSValue actual);
8484
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name_val, const WTF::String& msg, JSC::JSValue actual);
8585
JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s);

src/bun.js/bindings/ErrorCode.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ export default [
5959
// Console
6060
["ERR_CONSOLE_WRITABLE_STREAM", TypeError, "TypeError"],
6161

62-
//NET
62+
// NET
6363
["ERR_SOCKET_CLOSED_BEFORE_CONNECTION", Error],
6464
["ERR_SOCKET_CLOSED", Error],
65-
//HTTP2
65+
66+
// HTTP2
6667
["ERR_INVALID_HTTP_TOKEN", TypeError],
6768
["ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED", TypeError],
6869
["ERR_HTTP2_SEND_FILE", Error],
@@ -87,4 +88,9 @@ export default [
8788
["ERR_HTTP2_SOCKET_UNBOUND", Error],
8889
["ERR_HTTP2_ERROR", Error],
8990
["ERR_HTTP2_OUT_OF_STREAMS", Error],
91+
92+
// AsyncHooks
93+
["ERR_ASYNC_TYPE", TypeError],
94+
["ERR_INVALID_ASYNC_ID", RangeError],
95+
["ERR_ASYNC_CALLBACK", TypeError],
9096
] as ErrorCodeMapping;

src/bun.js/bindings/bindings.cpp

+54-1
Original file line numberDiff line numberDiff line change
@@ -1896,7 +1896,6 @@ JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0,
18961896

18971897
result->putDirect(vm, vm.propertyNames->name, code, JSC::PropertyAttribute::DontEnum | 0);
18981898
} else {
1899-
19001899
result->putDirect(
19011900
vm, vm.propertyNames->name,
19021901
JSC::JSValue(jsString(vm, String("SystemError"_s))),
@@ -1930,6 +1929,60 @@ JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0,
19301929
return JSC::JSValue::encode(JSC::JSValue(result));
19311930
}
19321931

1932+
JSC__JSValue SystemError__toErrorInstanceWithInfoObject(const SystemError* arg0,
1933+
JSC__JSGlobalObject* globalObject)
1934+
{
1935+
ASSERT_NO_PENDING_EXCEPTION(globalObject);
1936+
SystemError err = *arg0;
1937+
1938+
JSC::VM& vm = globalObject->vm();
1939+
1940+
auto scope = DECLARE_THROW_SCOPE(vm);
1941+
1942+
auto codeString = err.code.toWTFString();
1943+
auto syscallString = err.syscall.toWTFString();
1944+
auto messageString = err.message.toWTFString();
1945+
1946+
JSC::JSValue message = JSC::jsString(vm, makeString("A system error occurred: "_s, syscallString, " returned "_s, codeString, " ("_s, messageString, ")"_s));
1947+
1948+
JSC::JSValue options = JSC::jsUndefined();
1949+
JSC::JSObject* result = JSC::ErrorInstance::create(globalObject, JSC::ErrorInstance::createStructure(vm, globalObject, globalObject->errorPrototype()), message, options);
1950+
JSC::JSObject* info = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 0);
1951+
1952+
auto clientData = WebCore::clientData(vm);
1953+
1954+
result->putDirect(
1955+
vm, vm.propertyNames->name,
1956+
JSC::JSValue(jsString(vm, String("SystemError"_s))),
1957+
JSC::PropertyAttribute::DontEnum | 0);
1958+
result->putDirect(
1959+
vm, clientData->builtinNames().codePublicName(),
1960+
JSC::JSValue(jsString(vm, String("ERR_SYSTEM_ERROR"_s))),
1961+
JSC::PropertyAttribute::DontEnum | 0);
1962+
1963+
info->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, codeString), JSC::PropertyAttribute::DontDelete | 0);
1964+
1965+
result->putDirect(vm, JSC::Identifier::fromString(vm, "info"_s), info, JSC::PropertyAttribute::DontDelete | 0);
1966+
1967+
auto syscallJsString = jsString(vm, syscallString);
1968+
result->putDirect(vm, clientData->builtinNames().syscallPublicName(), syscallJsString,
1969+
JSC::PropertyAttribute::DontDelete | 0);
1970+
info->putDirect(vm, clientData->builtinNames().syscallPublicName(), syscallJsString,
1971+
JSC::PropertyAttribute::DontDelete | 0);
1972+
1973+
info->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, codeString),
1974+
JSC::PropertyAttribute::DontDelete | 0);
1975+
info->putDirect(vm, vm.propertyNames->message, jsString(vm, messageString),
1976+
JSC::PropertyAttribute::DontDelete | 0);
1977+
1978+
info->putDirect(vm, clientData->builtinNames().errnoPublicName(), jsNumber(err.errno_),
1979+
JSC::PropertyAttribute::DontDelete | 0);
1980+
result->putDirect(vm, clientData->builtinNames().errnoPublicName(), JSC::JSValue(err.errno_),
1981+
JSC::PropertyAttribute::DontDelete | 0);
1982+
1983+
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::JSValue(result)));
1984+
}
1985+
19331986
JSC__JSValue
19341987
JSC__JSObject__create(JSC__JSGlobalObject* globalObject, size_t initialCapacity, void* arg2,
19351988
void (*ArgFn3)(void* arg0, JSC__JSObject* arg1, JSC__JSGlobalObject* arg2))

0 commit comments

Comments
 (0)