Skip to content

Commit

Permalink
test(node/url): add more node:test parallel cases (#16404)
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac authored Jan 17, 2025
1 parent db5e9bd commit b412e36
Show file tree
Hide file tree
Showing 22 changed files with 1,552 additions and 165 deletions.
50 changes: 47 additions & 3 deletions src/bun.js/bindings/BunObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -604,23 +604,67 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
url = WTF::URL(arg0.toWTFString(globalObject));
RETURN_IF_EXCEPTION(scope, {});
} else {
throwTypeError(globalObject, scope, "Argument must be a URL"_s);
Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "url"_s, "string"_s, arg0);
return {};
}
} else {
url = domURL->href();
}

/// cannot turn non-`file://` URLs into file paths
if (UNLIKELY(!url.protocolIsFile())) {
throwTypeError(globalObject, scope, "Argument must be a file URL"_s);
Bun::ERR::INVALID_URL_SCHEME(scope, globalObject, "file"_s);
return {};
}

// NOTE: On Windows, WTF::URL::fileSystemPath will handle UNC paths
// (`file:\\server\share\etc` -> `\\server\share\etc`), so hostname check only
// needs to happen on posix systems
#if !OS(WINDOWS)
// file://host/path is illegal if `host` is not `localhost`.
// Should be `file:///` instead
if (UNLIKELY(url.host().length() > 0 && url.host() != "localhost"_s)) {

#if OS(DARWIN)
Bun::ERR::INVALID_FILE_URL_HOST(scope, globalObject, "darwin"_s);
return {};
#else
Bun::ERR::INVALID_FILE_URL_HOST(scope, globalObject, "linux"_s);
return {};
#endif
}
#endif

// ban url-encoded slashes. '/' on posix, '/' and '\' on windows.
StringView p = url.path();
if (p.length() > 3) {
for (int i = 0; i < p.length() - 2; i++) {
if (p[i] == '%') {
const char second = p[i + 1];
const uint8_t third = p[i + 2] | 0x20;
#if OS(WINDOWS)
if (
(second == '2' && third == 102) || // 2f 2F '/'
(second == '5' && third == 99) // 5c 5C '\'
) {
Bun::ERR::INVALID_FILE_URL_PATH(scope, globalObject, "must not include encoded \\ or / characters"_s);
return {};
}
#else
if (second == '2' && third == 102) {
Bun::ERR::INVALID_FILE_URL_PATH(scope, globalObject, "must not include encoded / characters"_s);
return {};
}
#endif
}
}
}

auto fileSystemPath = url.fileSystemPath();

#if OS(WINDOWS)
if (!isAbsolutePath(fileSystemPath)) {
throwTypeError(globalObject, scope, "File URL path must be absolute"_s);
Bun::ERR::INVALID_FILE_URL_PATH(scope, globalObject, "must be an absolute path"_s);
return {};
}
#endif
Expand Down
28 changes: 27 additions & 1 deletion src/bun.js/bindings/ErrorCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,32 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal
return {};
}

JSC::EncodedJSValue INVALID_URL_SCHEME(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& expectedScheme)
{
auto message = makeString("The URL must be of scheme "_s, expectedScheme);
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_URL_SCHEME, message));
return {};
}
JSC::EncodedJSValue INVALID_FILE_URL_HOST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& platform)
{
auto message = makeString("File URL host must be \"localhost\" or empty on "_s, platform);
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_FILE_URL_HOST, message));
return {};
}
JSC::EncodedJSValue INVALID_FILE_URL_HOST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const ASCIILiteral platform)
{
auto message = makeString("File URL host must be \"localhost\" or empty on "_s, platform);
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_FILE_URL_HOST, message));
return {};
}
/// `File URL path {suffix}`
JSC::EncodedJSValue INVALID_FILE_URL_PATH(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const ASCIILiteral suffix)
{
auto message = makeString("File URL path "_s, suffix);
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_FILE_URL_PATH, message));
return {};
}

JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView encoding)
{
auto message = makeString("Unknown encoding: "_s, encoding);
Expand Down Expand Up @@ -655,7 +681,7 @@ JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope& throwScope, JS
return {};
}

}
} // namespace ERR

static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue arg0, JSValue arg1, JSValue arg2)
{
Expand Down
10 changes: 10 additions & 0 deletions src/bun.js/bindings/ErrorCode.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// To add a new error code, put it in ErrorCode.ts
#pragma once

#include "ZigGlobalObject.h"
Expand Down Expand Up @@ -86,6 +87,15 @@ JSC::EncodedJSValue ASSERTION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject*
JSC::EncodedJSValue CRYPTO_INVALID_CURVE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject);
JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope&, JSC::JSGlobalObject*, const WTF::String&);

// URL

/// `URL must be of scheme {expectedScheme}`
JSC::EncodedJSValue INVALID_URL_SCHEME(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& expectedScheme);
/// `File URL host must be "localhost" or empty on {platform}`
JSC::EncodedJSValue INVALID_FILE_URL_HOST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& platform);
/// `File URL path {suffix}`
JSC::EncodedJSValue INVALID_FILE_URL_PATH(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const ASCIILiteral suffix);

}

void throwBoringSSLError(JSC::VM& vm, JSC::ThrowScope& scope, JSGlobalObject* globalObject, int errorCode);
Expand Down
19 changes: 12 additions & 7 deletions src/bun.js/bindings/ErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
type ErrorCodeMapping = Array<
[
/** error.code */
string,
code: string,
/** Constructor **/
typeof TypeError | typeof RangeError | typeof Error | typeof SyntaxError,
ctor: typeof TypeError | typeof RangeError | typeof Error | typeof SyntaxError,
/** error.name. Defaults to `Constructor.name` (that is, mapping[1].name */
string?,
name?: string,
(typeof TypeError | typeof RangeError | typeof Error | typeof SyntaxError)?,
(typeof TypeError | typeof RangeError | typeof Error | typeof SyntaxError)?,
]
Expand Down Expand Up @@ -43,7 +43,6 @@ const errors: ErrorCodeMapping = [
["ERR_INVALID_STATE", Error, undefined, TypeError, RangeError],
["ERR_INVALID_THIS", TypeError],
["ERR_INVALID_URI", URIError],
["ERR_INVALID_URL", TypeError],
["ERR_IPC_CHANNEL_CLOSED", Error],
["ERR_IPC_DISCONNECTED", Error],
["ERR_IPC_ONE_PIPE", Error],
Expand All @@ -64,16 +63,16 @@ const errors: ErrorCodeMapping = [
["ERR_SOCKET_DGRAM_IS_CONNECTED", Error],
["ERR_SOCKET_DGRAM_NOT_CONNECTED", Error],
["ERR_SOCKET_DGRAM_NOT_RUNNING", Error],
["ERR_STREAM_PREMATURE_CLOSE", Error],
["ERR_STREAM_ALREADY_FINISHED", Error],
["ERR_STREAM_CANNOT_PIPE", Error],
["ERR_STREAM_DESTROYED", Error],
["ERR_STREAM_NULL_VALUES", TypeError],
["ERR_STREAM_PREMATURE_CLOSE", Error],
["ERR_STREAM_WRITE_AFTER_END", Error],
["ERR_STREAM_PUSH_AFTER_EOF", Error],
["ERR_STREAM_RELEASE_LOCK", Error, "AbortError"],
["ERR_STREAM_UNABLE_TO_PIPE", Error],
["ERR_STREAM_UNSHIFT_AFTER_END_EVENT", Error],
["ERR_STREAM_WRITE_AFTER_END", Error],
["ERR_STRING_TOO_LONG", Error],
["ERR_UNAVAILABLE_DURING_EXIT", Error],
["ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET", Error],
Expand Down Expand Up @@ -176,5 +175,11 @@ const errors: ErrorCodeMapping = [
["ERR_S3_INVALID_ENDPOINT", Error],
["ERR_S3_INVALID_SIGNATURE", Error],
["ERR_S3_INVALID_SESSION_TOKEN", Error],
] as const;

// URL
["ERR_INVALID_URL", TypeError],
["ERR_INVALID_URL_SCHEME", TypeError],
["ERR_INVALID_FILE_URL_HOST", TypeError],
["ERR_INVALID_FILE_URL_PATH", TypeError],
];
export default errors;
16 changes: 12 additions & 4 deletions src/bun.js/bindings/PathInlines.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
#define PLATFORM_SEP POSIX_PATH_SEP
#endif

#define IS_LETTER(byte) \
((byte >= 'a' && byte <= 'z') || (byte >= 'A' && byte <= 'Z'))
#define IS_SLASH(byte) \
(byte == '/' || byte == '\\')

ALWAYS_INLINE bool isAbsolutePath(WTF::String input)
{
#if OS(WINDOWS)
Expand All @@ -22,23 +27,23 @@ ALWAYS_INLINE bool isAbsolutePath(WTF::String input)
if (len < 1)
return false;
const auto bytes = input.span8().data();
if (bytes[0] == '/' || bytes[0] == '\\')
if (IS_SLASH(bytes[0]))
return true;
if (len < 2)
return false;
if (bytes[1] == ':' && (bytes[2] == '/' || bytes[2] == '\\'))
if (IS_LETTER(bytes[0]) && bytes[1] == ':' && IS_SLASH(bytes[2]))
return true;
return false;
} else {
auto len = input.length();
if (len < 1)
return false;
const auto bytes = input.span16().data();
if (bytes[0] == '/' || bytes[0] == '\\')
if (IS_SLASH(bytes[0]))
return true;
if (len < 2)
return false;
if (bytes[1] == ':' && (bytes[2] == '/' || bytes[2] == '\\'))
if (IS_LETTER(bytes[0]) && bytes[1] == ':' && IS_SLASH(bytes[2]))
return true;
return false;
}
Expand All @@ -47,6 +52,9 @@ ALWAYS_INLINE bool isAbsolutePath(WTF::String input)
#endif
}

#undef IS_LETTER
#undef IS_SLASH

extern "C" BunString ResolvePath__joinAbsStringBufCurrentPlatformBunString(JSC::JSGlobalObject*, BunString);

/// CWD is determined by the global object's current cwd.
Expand Down
1 change: 0 additions & 1 deletion src/bun.js/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#include "root.h"

#include "ZigGlobalObject.h"
Expand Down
26 changes: 26 additions & 0 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6346,3 +6346,29 @@ extern "C" EncodedJSValue Bun__JSObject__getCodePropertyVMInquiry(JSC::JSGlobalO

return JSValue::encode(slot.getPureResult());
}

using StackCodeType = JSC::StackVisitor::Frame::CodeType;
CPP_DECL bool Bun__util__isInsideNodeModules(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
{
JSC::VM& vm = globalObject->vm();
bool inNodeModules = false;
JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
if (Zig::isImplementationVisibilityPrivate(visitor) || visitor->isNativeCalleeFrame()) {
return WTF::IterationStatus::Continue;
}

if (visitor->hasLineAndColumnInfo()) {
String sourceURL = Zig::sourceURL(visitor);
if (sourceURL.startsWith("node:"_s) || sourceURL.startsWith("bun:"_s))
return WTF::IterationStatus::Continue;
if (sourceURL.contains("node_modules"_s))
inNodeModules = true;

return WTF::IterationStatus::Done;
}

return WTF::IterationStatus::Continue;
});

return inNodeModules;
}
15 changes: 14 additions & 1 deletion src/bun.js/node/node_util_binding.zig
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,20 @@ pub fn extractedSplitNewLinesFastPathStringsOnly(globalThis: *JSC.JSGlobalObject
};
}

fn split(comptime encoding: bun.strings.EncodingNonAscii, globalThis: *JSC.JSGlobalObject, allocator: Allocator, str: *const bun.String) bun.JSError!JSC.JSValue {
extern fn Bun__util__isInsideNodeModules(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bool;
/// Walks the call stack from bottom to top, returning `true` when it finds a
/// frame within a `node_modules` directory.
pub fn isInsideNodeModules(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const res = Bun__util__isInsideNodeModules(globalObject, callframe);
return JSC.JSValue.jsBoolean(res);
}

fn split(
comptime encoding: bun.strings.EncodingNonAscii,
globalThis: *JSC.JSGlobalObject,
allocator: Allocator,
str: *const bun.String,
) bun.JSError!JSC.JSValue {
var fallback = std.heap.stackFallback(1024, allocator);
const alloc = fallback.get();
const Char = switch (encoding) {
Expand Down
1 change: 1 addition & 0 deletions src/codegen/generate-node-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ let zig = ``;
enumHeader = `
// clang-format off
// Generated by: src/codegen/generate-node-errors.ts
// Input: src/bun.js/bindings/ErrorCode.ts
#pragma once
#include <cstdint>
Expand Down
28 changes: 25 additions & 3 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,15 @@ declare function $getByIdDirectPrivate<T = any>(obj: any, key: string): T;
declare function $getByValWithThis(target: any, receiver: any, propertyKey: string): void;
/** gets the prototype of an object */
declare function $getPrototypeOf(value: any): any;
/** gets an internal property on a promise
/**
* Gets an internal property on a promise
*
* You can pass
* - $promiseFieldFlags - get a number with flags
* - $promiseFieldReactionsOrResult - get the result (like Bun.peek)
* - {@link $promiseFieldFlags} - get a number with flags
* - {@link $promiseFieldReactionsOrResult} - get the result (like {@link Bun.peek})
*
* @param promise the promise to get the field from
* @param key an internal field id.
*/
declare function $getPromiseInternalField<K extends PromiseFieldType, V>(
promise: Promise<V>,
Expand All @@ -99,6 +103,19 @@ declare function $getMapIteratorInternalField(): TODO;
declare function $getSetIteratorInternalField(): TODO;
declare function $getProxyInternalField(): TODO;
declare function $idWithProfile(): TODO;
/**
* True for object-like `JSCell`s. That is, this is roughly equivalent to this
* JS code:
* ```js
* typeof obj === "object" && obj !== null
* ```
*
* @param obj The object to check
* @returns `true` if `obj` is an object-like `JSCell`
*
* @see [JSCell.h](https://github.com/oven-sh/WebKit/blob/main/Source/JavaScriptCore/runtime/JSCell.h)
* @see [JIT implementation](https://github.com/oven-sh/WebKit/blob/433f7598bf3537a295d0af5ffd83b9a307abec4e/Source/JavaScriptCore/jit/JITOpcodes.cpp#L311)
*/
declare function $isObject(obj: unknown): obj is object;
declare function $isArray(obj: unknown): obj is any[];
declare function $isCallable(fn: unknown): fn is CallableFunction;
Expand Down Expand Up @@ -559,6 +576,11 @@ declare interface Function {
path: string;
}

interface String {
$charCodeAt: String["charCodeAt"];
// add others as needed
}

declare var $Buffer: {
new (a: any, b?: any, c?: any): Buffer;
};
Expand Down
5 changes: 5 additions & 0 deletions src/js/internal/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const isInsideNodeModules: () => boolean = $newZigFunction("node_util_binding.zig", "isInsideNodeModules", 0);

export default {
isInsideNodeModules,
};
Loading

0 comments on commit b412e36

Please sign in to comment.