mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
node:vm compatibility (#19703)
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
"src/bun.js/bindings/v8/shim/*.cpp",
|
||||
"src/bake/*.cpp",
|
||||
"src/deps/*.cpp",
|
||||
"src/vm/*.cpp",
|
||||
"packages/bun-usockets/src/crypto/*.cpp"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -153,6 +153,9 @@ src/bun.js/bindings/NodeTLS.cpp
|
||||
src/bun.js/bindings/NodeURL.cpp
|
||||
src/bun.js/bindings/NodeValidator.cpp
|
||||
src/bun.js/bindings/NodeVM.cpp
|
||||
src/bun.js/bindings/NodeVMModule.cpp
|
||||
src/bun.js/bindings/NodeVMScript.cpp
|
||||
src/bun.js/bindings/NodeVMSourceTextModule.cpp
|
||||
src/bun.js/bindings/NoOpForTesting.cpp
|
||||
src/bun.js/bindings/ObjectBindings.cpp
|
||||
src/bun.js/bindings/objects.cpp
|
||||
@@ -466,3 +469,5 @@ src/bun.js/modules/NodeUtilTypesModule.cpp
|
||||
src/bun.js/modules/ObjectModule.cpp
|
||||
src/deps/libuwsockets.cpp
|
||||
src/io/io_darwin.cpp
|
||||
src/vm/Semaphore.cpp
|
||||
src/vm/SigintWatcher.cpp
|
||||
|
||||
@@ -744,7 +744,7 @@ target_include_directories(${bun} PRIVATE
|
||||
${NODEJS_HEADERS_PATH}/include
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
if(NOT WIN32)
|
||||
target_include_directories(${bun} PRIVATE ${CWD}/src/bun.js/bindings/libuv)
|
||||
endif()
|
||||
|
||||
@@ -877,7 +877,7 @@ if(NOT WIN32)
|
||||
-Wno-nullability-completeness
|
||||
-Werror
|
||||
)
|
||||
|
||||
|
||||
if(ENABLE_ASAN)
|
||||
target_compile_options(${bun} PUBLIC
|
||||
-fsanitize=address
|
||||
|
||||
@@ -875,8 +875,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionChdir, (JSC::JSGlobalObject * globalObj
|
||||
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result));
|
||||
}
|
||||
|
||||
static HashMap<String, int>* signalNameToNumberMap = nullptr;
|
||||
static HashMap<int, String>* signalNumberToNameMap = nullptr;
|
||||
static HashMap<String, int>* signalNameToNumberMap = nullptr;
|
||||
|
||||
// On windows, signals need to have a handle to the uv_signal_t. When sigaction is used, this is kept track globally for you.
|
||||
struct SignalHandleValue {
|
||||
@@ -1143,7 +1143,7 @@ static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& e
|
||||
{
|
||||
if (Bun__isMainThreadVM()) {
|
||||
// IPC handlers
|
||||
if (eventName.string() == "message"_s || eventName.string() == "disconnect"_s) {
|
||||
if (eventName == "message" || eventName == "disconnect") {
|
||||
auto* global = jsCast<GlobalObject*>(eventEmitter.scriptExecutionContext()->jsGlobalObject());
|
||||
auto& vm = JSC::getVM(global);
|
||||
auto messageListenerCount = eventEmitter.listenerCount(vm.propertyNames->message);
|
||||
|
||||
@@ -2224,6 +2224,26 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_CHILD_PROCESS_STDIO_MAXBUFFER, message));
|
||||
}
|
||||
|
||||
case Bun::ErrorCode::ERR_VM_MODULE_STATUS: {
|
||||
auto arg0 = callFrame->argument(1);
|
||||
auto str0 = arg0.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
auto message = makeString("Module status "_s, str0);
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_STATUS, message));
|
||||
}
|
||||
|
||||
case Bun::ErrorCode::ERR_VM_MODULE_LINK_FAILURE: {
|
||||
auto arg0 = callFrame->argument(1);
|
||||
auto message = arg0.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
auto cause = callFrame->argument(2);
|
||||
JSObject* error = createError(globalObject, ErrorCode::ERR_VM_MODULE_LINK_FAILURE, message);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
error->putDirect(vm, Identifier::fromString(vm, "cause"_s), cause);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
return JSC::JSValue::encode(error);
|
||||
}
|
||||
|
||||
case ErrorCode::ERR_IPC_DISCONNECTED:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s));
|
||||
case ErrorCode::ERR_SERVER_NOT_RUNNING:
|
||||
@@ -2332,6 +2352,14 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_SOCKET_ASSIGNED, "Socket already assigned"_s));
|
||||
case ErrorCode::ERR_STREAM_RELEASE_LOCK:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_STREAM_RELEASE_LOCK, "Stream reader cancelled via releaseLock()"_s));
|
||||
case ErrorCode::ERR_VM_MODULE_ALREADY_LINKED:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module has already been linked"_s));
|
||||
case ErrorCode::ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA, "Cached data cannot be created for a module which has been evaluated"_s));
|
||||
case ErrorCode::ERR_VM_MODULE_NOT_MODULE:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_NOT_MODULE, "Provided module is not an instance of Module"_s));
|
||||
case ErrorCode::ERR_VM_MODULE_DIFFERENT_CONTEXT:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_DIFFERENT_CONTEXT, "Linked modules must use the same context"_s));
|
||||
|
||||
default: {
|
||||
break;
|
||||
|
||||
@@ -249,8 +249,6 @@ const errors: ErrorCodeMapping = [
|
||||
["ERR_UNKNOWN_ENCODING", TypeError],
|
||||
["ERR_UNKNOWN_SIGNAL", TypeError],
|
||||
["ERR_USE_AFTER_CLOSE", Error],
|
||||
["ERR_VM_MODULE_CACHED_DATA_REJECTED", Error],
|
||||
["ERR_VM_MODULE_LINK_FAILURE", Error],
|
||||
["ERR_WASI_NOT_STARTED", Error],
|
||||
["ERR_WORKER_INIT_FAILED", Error],
|
||||
["ERR_WORKER_NOT_RUNNING", Error],
|
||||
@@ -283,6 +281,13 @@ const errors: ErrorCodeMapping = [
|
||||
["HPE_INVALID_EOF_STATE", Error],
|
||||
["HPE_INVALID_METHOD", Error],
|
||||
["HPE_INTERNAL", Error],
|
||||
["ERR_VM_MODULE_STATUS", Error],
|
||||
["ERR_VM_MODULE_ALREADY_LINKED", Error],
|
||||
["ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA", Error],
|
||||
["ERR_VM_MODULE_NOT_MODULE", Error],
|
||||
["ERR_VM_MODULE_DIFFERENT_CONTEXT", Error],
|
||||
["ERR_VM_MODULE_LINK_FAILURE", Error],
|
||||
["ERR_VM_MODULE_CACHED_DATA_REJECTED", Error],
|
||||
["HPE_INVALID_HEADER_TOKEN", Error],
|
||||
["HPE_HEADER_OVERFLOW", Error],
|
||||
];
|
||||
|
||||
@@ -89,6 +89,7 @@ void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
|
||||
RETURN_IF_EXCEPTION(throwScope, );
|
||||
if (mustResetContext) {
|
||||
globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined());
|
||||
RETURN_IF_EXCEPTION(throwScope, );
|
||||
}
|
||||
auto* drainFn = internalField(2).get().getObject();
|
||||
MarkedArgumentBuffer drainArgs;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,27 @@
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class NodeVMGlobalObject;
|
||||
class NodeVMContextOptions;
|
||||
class CompileFunctionOptions;
|
||||
|
||||
namespace NodeVM {
|
||||
|
||||
RefPtr<JSC::CachedBytecode> getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source);
|
||||
RefPtr<JSC::CachedBytecode> getBytecode(JSGlobalObject* globalObject, JSC::ModuleProgramExecutable* executable, const JSC::SourceCode& source);
|
||||
bool extractCachedData(JSValue cachedDataValue, WTF::Vector<uint8_t>& outCachedData);
|
||||
String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset);
|
||||
JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source);
|
||||
NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox);
|
||||
bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr<JSC::Exception> exception, ThrowScope& throwScope);
|
||||
std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey);
|
||||
NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSValue contextValue, bool canThrow);
|
||||
JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value);
|
||||
// For vm.compileFunction we need to return an anonymous function expression. This code is adapted from/inspired by JSC::constructFunction, which is used for function declarations.
|
||||
JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, CompileFunctionOptions&& options, JSC::SourceTaintedOrigin sourceTaintOrigin, JSC::JSScope* scope);
|
||||
|
||||
} // namespace NodeVM
|
||||
|
||||
// This class represents a sandboxed global object for vm contexts
|
||||
class NodeVMGlobalObject final : public Bun::GlobalScope {
|
||||
using Base = Bun::GlobalScope;
|
||||
@@ -23,16 +44,18 @@ public:
|
||||
static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction;
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
|
||||
static NodeVMGlobalObject* create(JSC::VM& vm, JSC::Structure* structure);
|
||||
static NodeVMGlobalObject* create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options);
|
||||
static Structure* createStructure(JSC::VM& vm, JSC::JSValue prototype);
|
||||
|
||||
DECLARE_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
void finishCreation(JSC::VM&, NodeVMContextOptions options);
|
||||
static void destroy(JSCell* cell);
|
||||
void setContextifiedObject(JSC::JSObject* contextifiedObject);
|
||||
JSC::JSObject* contextifiedObject() const { return m_sandbox.get(); }
|
||||
void clearContextifiedObject();
|
||||
void sigintReceived();
|
||||
|
||||
// Override property access to delegate to contextified object
|
||||
static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&);
|
||||
@@ -60,4 +83,39 @@ JSC_DECLARE_HOST_FUNCTION(vmModule_isContext);
|
||||
JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext);
|
||||
JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext);
|
||||
|
||||
class BaseVMOptions {
|
||||
public:
|
||||
String filename;
|
||||
OrdinalNumber lineOffset = OrdinalNumber::fromZeroBasedInt(0);
|
||||
OrdinalNumber columnOffset = OrdinalNumber::fromZeroBasedInt(0);
|
||||
bool failed = false;
|
||||
|
||||
BaseVMOptions() = default;
|
||||
BaseVMOptions(String filename);
|
||||
BaseVMOptions(String filename, OrdinalNumber lineOffset, OrdinalNumber columnOffset);
|
||||
|
||||
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg);
|
||||
bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool& outProduceCachedData);
|
||||
bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, WTF::Vector<uint8_t>& outCachedData);
|
||||
bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional<int64_t>& outTimeout);
|
||||
};
|
||||
|
||||
class CompileFunctionOptions : public BaseVMOptions {
|
||||
public:
|
||||
WTF::Vector<uint8_t> cachedData;
|
||||
JSGlobalObject* parsingContext = nullptr;
|
||||
JSValue contextExtensions;
|
||||
bool produceCachedData = false;
|
||||
|
||||
using BaseVMOptions::BaseVMOptions;
|
||||
|
||||
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg);
|
||||
};
|
||||
|
||||
class NodeVMContextOptions final {
|
||||
public:
|
||||
bool allowStrings = true;
|
||||
bool allowWasm = true;
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
395
src/bun.js/bindings/NodeVMModule.cpp
Normal file
395
src/bun.js/bindings/NodeVMModule.cpp
Normal file
@@ -0,0 +1,395 @@
|
||||
#include "NodeVMModule.h"
|
||||
#include "NodeVMSourceTextModule.h"
|
||||
|
||||
#include "ErrorCode.h"
|
||||
#include "JSDOMExceptionHandling.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
NodeVMModuleRequest::NodeVMModuleRequest(WTF::String specifier, WTF::HashMap<WTF::String, WTF::String> importAttributes)
|
||||
: m_specifier(WTFMove(specifier))
|
||||
, m_importAttributes(WTFMove(importAttributes))
|
||||
{
|
||||
}
|
||||
|
||||
void NodeVMModuleRequest::addImportAttribute(WTF::String key, WTF::String value)
|
||||
{
|
||||
m_importAttributes.set(WTFMove(key), WTFMove(value));
|
||||
}
|
||||
|
||||
JSArray* NodeVMModuleRequest::toJS(JSGlobalObject* globalObject) const
|
||||
{
|
||||
JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2);
|
||||
array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier));
|
||||
|
||||
JSObject* attributes = JSC::constructEmptyObject(globalObject);
|
||||
for (const auto& [key, value] : m_importAttributes) {
|
||||
attributes->putDirect(globalObject->vm(), JSC::Identifier::fromString(globalObject->vm(), key), JSC::jsString(globalObject->vm(), value),
|
||||
PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete);
|
||||
}
|
||||
array->putDirectIndex(globalObject, 1, attributes);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
NodeVMModule::NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context)
|
||||
: Base(vm, structure)
|
||||
, m_identifier(WTFMove(identifier))
|
||||
{
|
||||
if (context.isObject()) {
|
||||
m_context.set(vm, this, asObject(context));
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeVMModule::finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque<NodeVMSourceTextModule*>& stack, unsigned* dfsIndex)
|
||||
{
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(this)) {
|
||||
return thisObject->finishInstantiate(globalObject, stack, dfsIndex);
|
||||
// } else if (auto* thisObject = jsDynamicCast<NodeVMSyntheticModule*>(this)) {
|
||||
// return thisObject->finishInstantiate(globalObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JSValue NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(this)) {
|
||||
return thisObject->createModuleRecord(globalObject);
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
return JSC::jsUndefined();
|
||||
}
|
||||
|
||||
AbstractModuleRecord* NodeVMModule::moduleRecord(JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(this)) {
|
||||
return thisObject->moduleRecord(globalObject);
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args)
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSValue disambiguator = args.at(2);
|
||||
|
||||
if (disambiguator.isString()) {
|
||||
return NodeVMSourceTextModule::create(vm, globalObject, args);
|
||||
}
|
||||
|
||||
if (disambiguator.inherits(JSArray::info())) {
|
||||
// return NodeVMSyntheticModule::create(vm, globalObject, args);
|
||||
}
|
||||
|
||||
throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSModuleNamespaceObject* NodeVMModule::namespaceObject(JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
JSModuleNamespaceObject* object = m_namespaceObject.get();
|
||||
if (object) {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(this)) {
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
object = thisObject->moduleRecord(globalObject)->getModuleNamespace(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
if (object) {
|
||||
namespaceObject(vm, object);
|
||||
}
|
||||
} else {
|
||||
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("NodeVMModule::namespaceObject called on an unsupported module type (%s)", info()->className.characters());
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetNamespace);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetError);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleInstantiate);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleEvaluate);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord);
|
||||
|
||||
static const HashTableValue NodeVMModulePrototypeTableValues[] = {
|
||||
{ "identifier"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } },
|
||||
{ "getStatusCode"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatusCode, 0 } },
|
||||
{ "getStatus"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } },
|
||||
{ "getNamespace"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } },
|
||||
{ "getError"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } },
|
||||
{ "instantiate"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } },
|
||||
{ "evaluate"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleEvaluate, 2 } },
|
||||
{ "getModuleRequests"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetModuleRequests, 0 } },
|
||||
{ "link"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleLink, 2 } },
|
||||
{ "createCachedData"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateCachedData, 0 } },
|
||||
{ "setExport"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } },
|
||||
{ "createModuleRecord"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateModuleRecord, 0 } },
|
||||
};
|
||||
|
||||
NodeVMModulePrototype* NodeVMModulePrototype::create(VM& vm, Structure* structure)
|
||||
{
|
||||
NodeVMModulePrototype* prototype = new (NotNull, allocateCell<NodeVMModulePrototype>(vm)) NodeVMModulePrototype(vm, structure);
|
||||
prototype->finishCreation(vm);
|
||||
return prototype;
|
||||
}
|
||||
|
||||
Structure* NodeVMModulePrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
|
||||
{
|
||||
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
NodeVMModulePrototype::NodeVMModulePrototype(VM& vm, Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeVMModulePrototype::finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
reifyStaticProperties(vm, info(), NodeVMModulePrototypeTableValues, *this);
|
||||
this->structure()->setMayBePrototype(true);
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMSourceTextModule*>(JSC::JSValue::decode(thisValue));
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier()));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue());
|
||||
return JSValue::encode(JSC::jsNumber(static_cast<uint32_t>(thisObject->status())));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue());
|
||||
|
||||
using enum NodeVMModule::Status;
|
||||
switch (thisObject->status()) {
|
||||
case Unlinked:
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s)));
|
||||
case Linking:
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s)));
|
||||
case Linked:
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s)));
|
||||
case Evaluating:
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s)));
|
||||
case Evaluated:
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s)));
|
||||
case Errored:
|
||||
return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s)));
|
||||
default:
|
||||
return JSC::encodedJSUndefined();
|
||||
}
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue());
|
||||
return JSValue::encode(thisObject->namespaceObject(globalObject));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
|
||||
if (JSC::Exception* exception = thisObject->evaluationException()) {
|
||||
return JSValue::encode(exception->value());
|
||||
}
|
||||
throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_STATUS, "Module status must be errored"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMModule*>(callFrame->thisValue());
|
||||
|
||||
if (auto* sourceTextModule = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
|
||||
sourceTextModule->ensureModuleRecord(globalObject);
|
||||
}
|
||||
|
||||
const WTF::Vector<NodeVMModuleRequest>& requests = thisObject->moduleRequests();
|
||||
|
||||
JSArray* array = constructEmptyArray(globalObject, nullptr, requests.size());
|
||||
|
||||
for (unsigned i = 0; const NodeVMModuleRequest& request : requests) {
|
||||
array->putDirectIndex(globalObject, i++, request.toJS(globalObject));
|
||||
}
|
||||
|
||||
return JSValue::encode(array);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue timeoutValue = callFrame->argument(0);
|
||||
uint32_t timeout = 0;
|
||||
if (timeoutValue.isUInt32()) {
|
||||
timeout = timeoutValue.asUInt32();
|
||||
}
|
||||
|
||||
JSValue breakOnSigintValue = callFrame->argument(1);
|
||||
bool breakOnSigint = false;
|
||||
if (breakOnSigintValue.isBoolean()) {
|
||||
breakOnSigint = breakOnSigintValue.asBoolean();
|
||||
}
|
||||
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
|
||||
return JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint));
|
||||
// } else if (auto* thisObject = jsDynamicCast<NodeVMSyntheticModule*>(callFrame->thisValue())) {
|
||||
// return thisObject->link(globalObject, specifiers, moduleNatives);
|
||||
} else {
|
||||
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSArray* specifiers = jsDynamicCast<JSArray*>(callFrame->argument(0));
|
||||
JSArray* moduleNatives = jsDynamicCast<JSArray*>(callFrame->argument(1));
|
||||
|
||||
if (!specifiers) {
|
||||
return throwArgumentTypeError(*globalObject, scope, 0, "specifiers"_s, "Module"_s, "Module"_s, "Array"_s);
|
||||
}
|
||||
|
||||
if (!moduleNatives) {
|
||||
return throwArgumentTypeError(*globalObject, scope, 1, "moduleNatives"_s, "Module"_s, "Module"_s, "Array"_s);
|
||||
}
|
||||
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
|
||||
return JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives, callFrame->argument(2)));
|
||||
// return thisObject->link(globalObject, linker);
|
||||
// } else if (auto* thisObject = jsDynamicCast<NodeVMSyntheticModule*>(callFrame->thisValue())) {
|
||||
// return thisObject->link(globalObject, specifiers, moduleNatives);
|
||||
} else {
|
||||
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
// auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue());
|
||||
return JSC::encodedJSUndefined();
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
// auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue());
|
||||
return JSC::encodedJSUndefined();
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
|
||||
return JSValue::encode(thisObject->cachedData(globalObject));
|
||||
}
|
||||
|
||||
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMModule*>(callFrame->thisValue());
|
||||
return JSValue::encode(thisObject->createModuleRecord(globalObject));
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
auto* vmModule = jsCast<NodeVMModule*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(vmModule, info());
|
||||
Base::visitChildren(vmModule, visitor);
|
||||
|
||||
visitor.append(vmModule->m_namespaceObject);
|
||||
visitor.append(vmModule->m_context);
|
||||
|
||||
auto moduleNatives = vmModule->m_resolveCache.values();
|
||||
visitor.append(moduleNatives.begin(), moduleNatives.end());
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(NodeVMModule);
|
||||
|
||||
static EncodedJSValue
|
||||
constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {})
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
ArgList args(callFrame);
|
||||
|
||||
NodeVMModule* module = NodeVMModule::create(vm, globalObject, args);
|
||||
|
||||
return JSValue::encode(module);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(moduleConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
return constructModule(globalObject, callFrame);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(moduleConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
return constructModule(globalObject, callFrame, callFrame->newTarget());
|
||||
}
|
||||
|
||||
NodeVMModuleConstructor* NodeVMModuleConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype)
|
||||
{
|
||||
NodeVMModuleConstructor* ptr = new (NotNull, allocateCell<NodeVMModuleConstructor>(vm)) NodeVMModuleConstructor(vm, structure);
|
||||
ptr->finishCreation(vm, prototype);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
NodeVMModuleConstructor::NodeVMModuleConstructor(VM& vm, Structure* structure)
|
||||
: NodeVMModuleConstructor::Base(vm, structure, moduleConstructorCall, moduleConstructorConstruct)
|
||||
{
|
||||
}
|
||||
|
||||
JSC::Structure* NodeVMModuleConstructor::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info());
|
||||
}
|
||||
|
||||
void NodeVMModuleConstructor::finishCreation(VM& vm, JSObject* prototype)
|
||||
{
|
||||
Base::finishCreation(vm, 1, "Module"_s, PropertyAdditionMode::WithStructureTransition);
|
||||
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
|
||||
const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) };
|
||||
const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) };
|
||||
const JSC::ClassInfo NodeVMModuleConstructor::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModuleConstructor) };
|
||||
|
||||
} // namespace Bun
|
||||
123
src/bun.js/bindings/NodeVMModule.h
Normal file
123
src/bun.js/bindings/NodeVMModule.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "NodeVM.h"
|
||||
|
||||
#include "JavaScriptCore/AbstractModuleRecord.h"
|
||||
#include "JavaScriptCore/JSModuleNamespaceObject.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class NodeVMSourceTextModule;
|
||||
|
||||
class NodeVMModuleRequest final {
|
||||
public:
|
||||
NodeVMModuleRequest(WTF::String specifier, WTF::HashMap<WTF::String, WTF::String> importAttributes = {});
|
||||
|
||||
JSArray* toJS(JSGlobalObject* globalObject) const;
|
||||
void addImportAttribute(WTF::String key, WTF::String value);
|
||||
|
||||
const WTF::String& specifier() const { return m_specifier; }
|
||||
void specifier(WTF::String value) { m_specifier = value; }
|
||||
const WTF::HashMap<WTF::String, WTF::String>& importAttributes() const { return m_importAttributes; }
|
||||
|
||||
private:
|
||||
WTF::String m_specifier;
|
||||
WTF::HashMap<WTF::String, WTF::String> m_importAttributes;
|
||||
};
|
||||
|
||||
class NodeVMModule : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
|
||||
enum class Status : uint8_t {
|
||||
Unlinked,
|
||||
Linking,
|
||||
Linked,
|
||||
Evaluating,
|
||||
Evaluated,
|
||||
Errored
|
||||
};
|
||||
|
||||
enum class Type : uint8_t {
|
||||
SourceText,
|
||||
Synthetic,
|
||||
};
|
||||
|
||||
static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args);
|
||||
|
||||
const WTF::String& identifier() const { return m_identifier; }
|
||||
|
||||
Status status() const { return m_status; }
|
||||
void status(Status value) { m_status = value; }
|
||||
|
||||
JSModuleNamespaceObject* namespaceObject(JSC::JSGlobalObject* globalObject);
|
||||
void namespaceObject(JSC::VM& vm, JSModuleNamespaceObject* value) { m_namespaceObject.set(vm, this, value); }
|
||||
|
||||
const WTF::Vector<NodeVMModuleRequest>& moduleRequests() const { return m_moduleRequests; }
|
||||
void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); }
|
||||
|
||||
// Purposely not virtual. Dispatches to the correct subclass.
|
||||
bool finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque<NodeVMSourceTextModule*>& stack, unsigned* dfsIndex);
|
||||
|
||||
// Purposely not virtual. Dispatches to the correct subclass.
|
||||
JSValue createModuleRecord(JSC::JSGlobalObject* globalObject);
|
||||
|
||||
// Purposely not virtual. Dispatches to the correct subclass.
|
||||
AbstractModuleRecord* moduleRecord(JSC::JSGlobalObject* globalObject);
|
||||
|
||||
protected:
|
||||
WTF::String m_identifier;
|
||||
Status m_status = Status::Unlinked;
|
||||
mutable WriteBarrier<JSModuleNamespaceObject> m_namespaceObject;
|
||||
mutable WriteBarrier<JSObject> m_context;
|
||||
WTF::Vector<NodeVMModuleRequest> m_moduleRequests;
|
||||
mutable WTF::HashMap<WTF::String, WriteBarrier<JSObject>> m_resolveCache;
|
||||
|
||||
NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context);
|
||||
|
||||
DECLARE_EXPORT_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
};
|
||||
|
||||
class NodeVMModulePrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
|
||||
static NodeVMModulePrototype* create(VM& vm, Structure* structure);
|
||||
|
||||
DECLARE_INFO;
|
||||
template<typename CellType, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, Base);
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype);
|
||||
|
||||
private:
|
||||
NodeVMModulePrototype(VM& vm, Structure* structure);
|
||||
|
||||
void finishCreation(VM& vm);
|
||||
};
|
||||
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, NodeVMModulePrototype::Base);
|
||||
|
||||
class NodeVMModuleConstructor final : public JSC::InternalFunction {
|
||||
public:
|
||||
using Base = JSC::InternalFunction;
|
||||
|
||||
DECLARE_EXPORT_INFO;
|
||||
|
||||
static NodeVMModuleConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype);
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype);
|
||||
|
||||
private:
|
||||
NodeVMModuleConstructor(JSC::VM& vm, JSC::Structure* structure);
|
||||
|
||||
void finishCreation(JSC::VM&, JSC::JSObject* prototype);
|
||||
};
|
||||
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModuleConstructor, JSC::InternalFunction);
|
||||
|
||||
} // namespace Bun
|
||||
646
src/bun.js/bindings/NodeVMScript.cpp
Normal file
646
src/bun.js/bindings/NodeVMScript.cpp
Normal file
@@ -0,0 +1,646 @@
|
||||
#include "ErrorCode.h"
|
||||
#include "JavaScriptCore/Completion.h"
|
||||
#include "JavaScriptCore/JIT.h"
|
||||
#include "JavaScriptCore/JSWeakMap.h"
|
||||
#include "JavaScriptCore/JSWeakMapInlines.h"
|
||||
#include "JavaScriptCore/ProgramCodeBlock.h"
|
||||
#include "JavaScriptCore/SourceCodeKey.h"
|
||||
#include "NodeVMScript.h"
|
||||
|
||||
#include "../vm/SigintWatcher.h"
|
||||
|
||||
namespace Bun {
|
||||
using namespace NodeVM;
|
||||
|
||||
bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg)
|
||||
{
|
||||
bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
if (!optionsArg.isUndefined() && !optionsArg.isString()) {
|
||||
JSObject* options = asObject(optionsArg);
|
||||
|
||||
// Validate contextName and contextOrigin are strings
|
||||
if (JSValue contextNameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextName"_s))) {
|
||||
if (!contextNameOpt.isUndefined() && !contextNameOpt.isString()) {
|
||||
ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextName"_s, "string"_s, contextNameOpt);
|
||||
return false;
|
||||
}
|
||||
any = true;
|
||||
}
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) {
|
||||
if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) {
|
||||
ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextOrigin"_s, "string"_s, contextOriginOpt);
|
||||
return false;
|
||||
}
|
||||
any = true;
|
||||
}
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
if (validateTimeout(globalObject, vm, scope, options, this->timeout)) {
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
any = true;
|
||||
}
|
||||
|
||||
if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) {
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
any = true;
|
||||
}
|
||||
|
||||
if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) {
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
return any;
|
||||
}
|
||||
|
||||
static EncodedJSValue
|
||||
constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {})
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
ArgList args(callFrame);
|
||||
JSValue sourceArg = args.at(0);
|
||||
String sourceString = sourceArg.isUndefined() ? emptyString() : sourceArg.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());
|
||||
|
||||
JSValue optionsArg = args.at(1);
|
||||
ScriptOptions options(""_s);
|
||||
if (optionsArg.isString()) {
|
||||
options.filename = optionsArg.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
} else if (!options.fromJS(globalObject, vm, scope, optionsArg)) {
|
||||
RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
|
||||
}
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
Structure* structure = zigGlobalObject->NodeVMScriptStructure();
|
||||
if (zigGlobalObject->NodeVMScript() != newTarget) [[unlikely]] {
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
if (!newTarget) {
|
||||
throwTypeError(globalObject, scope, "Class constructor Script cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
structure = InternalFunction::createSubclassStructure(
|
||||
globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure());
|
||||
scope.release();
|
||||
}
|
||||
|
||||
SourceCode source(
|
||||
JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)),
|
||||
options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt());
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
const bool produceCachedData = options.produceCachedData;
|
||||
auto filename = options.filename;
|
||||
|
||||
NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options));
|
||||
|
||||
WTF::Vector<uint8_t>& cachedData = script->cachedData();
|
||||
|
||||
if (!cachedData.isEmpty()) {
|
||||
JSC::ProgramExecutable* executable = script->cachedExecutable();
|
||||
if (!executable) {
|
||||
executable = script->createExecutable();
|
||||
}
|
||||
ASSERT(executable);
|
||||
|
||||
JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? JSC::TaintedByWithScopeLexicallyScopedFeature : JSC::NoLexicallyScopedFeatures;
|
||||
JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt);
|
||||
Ref<JSC::CachedBytecode> cachedBytecode = JSC::CachedBytecode::create(std::span(cachedData), nullptr, {});
|
||||
JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock<UnlinkedProgramCodeBlock>(vm, key, WTFMove(cachedBytecode));
|
||||
|
||||
if (!unlinkedBlock) {
|
||||
script->cachedDataRejected(TriState::True);
|
||||
} else {
|
||||
JSC::JSScope* jsScope = globalObject->globalScope();
|
||||
JSC::CodeBlock* codeBlock = nullptr;
|
||||
{
|
||||
// JSC::ProgramCodeBlock::create() requires GC to be deferred.
|
||||
DeferGC deferGC(vm);
|
||||
codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope);
|
||||
}
|
||||
JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail);
|
||||
if (compilationResult != JSC::CompilationResult::CompilationFailed) {
|
||||
executable->installCode(codeBlock);
|
||||
script->cachedDataRejected(TriState::False);
|
||||
} else {
|
||||
script->cachedDataRejected(TriState::True);
|
||||
}
|
||||
}
|
||||
} else if (produceCachedData) {
|
||||
script->cacheBytecode();
|
||||
// TODO(@heimskr): is there ever a case where bytecode production fails?
|
||||
script->cachedDataProduced(true);
|
||||
}
|
||||
|
||||
return JSValue::encode(script);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(scriptConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
return constructScript(globalObject, callFrame);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(scriptConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
return constructScript(globalObject, callFrame, callFrame->newTarget());
|
||||
}
|
||||
|
||||
JSC::ProgramExecutable* NodeVMScript::createExecutable()
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject());
|
||||
m_cachedExecutable.set(vm, this, JSC::ProgramExecutable::create(globalObject(), m_source));
|
||||
return m_cachedExecutable.get();
|
||||
}
|
||||
|
||||
void NodeVMScript::cacheBytecode()
|
||||
{
|
||||
if (!m_cachedExecutable) {
|
||||
createExecutable();
|
||||
}
|
||||
|
||||
m_cachedBytecode = getBytecode(globalObject(), m_cachedExecutable.get(), m_source);
|
||||
m_cachedDataProduced = m_cachedBytecode != nullptr;
|
||||
}
|
||||
|
||||
JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer()
|
||||
{
|
||||
if (!m_options.produceCachedData) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_cachedBytecodeBuffer) {
|
||||
if (!m_cachedBytecode) {
|
||||
cacheBytecode();
|
||||
}
|
||||
|
||||
ASSERT(m_cachedBytecode);
|
||||
|
||||
std::span<const uint8_t> bytes = m_cachedBytecode->span();
|
||||
m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes));
|
||||
}
|
||||
|
||||
ASSERT(m_cachedBytecodeBuffer);
|
||||
return m_cachedBytecodeBuffer.get();
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(NodeVMScript);
|
||||
|
||||
template<typename Visitor>
|
||||
void NodeVMScript::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
NodeVMScript* thisObject = jsCast<NodeVMScript*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
visitor.append(thisObject->m_cachedExecutable);
|
||||
visitor.append(thisObject->m_cachedBytecodeBuffer);
|
||||
}
|
||||
|
||||
NodeVMScriptConstructor::NodeVMScriptConstructor(VM& vm, Structure* structure)
|
||||
: NodeVMScriptConstructor::Base(vm, structure, scriptConstructorCall, scriptConstructorConstruct)
|
||||
{
|
||||
}
|
||||
|
||||
NodeVMScriptConstructor* NodeVMScriptConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype)
|
||||
{
|
||||
NodeVMScriptConstructor* ptr = new (NotNull, allocateCell<NodeVMScriptConstructor>(vm)) NodeVMScriptConstructor(vm, structure);
|
||||
ptr->finishCreation(vm, prototype);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void NodeVMScriptConstructor::finishCreation(VM& vm, JSObject* prototype)
|
||||
{
|
||||
Base::finishCreation(vm, 1, "Script"_s, PropertyAdditionMode::WithStructureTransition);
|
||||
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
|
||||
NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source, ScriptOptions options)
|
||||
{
|
||||
NodeVMScript* ptr = new (NotNull, allocateCell<NodeVMScript>(vm)) NodeVMScript(vm, structure, source, WTFMove(options));
|
||||
ptr->finishCreation(vm);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void NodeVMScript::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
|
||||
void NodeVMScript::destroy(JSCell* cell)
|
||||
{
|
||||
static_cast<NodeVMScript*>(cell)->NodeVMScript::~NodeVMScript();
|
||||
}
|
||||
|
||||
static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, NodeVMScript* script, std::optional<double> timeout)
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
|
||||
if (vm.hasTerminationRequest()) {
|
||||
vm.clearHasTerminationRequest();
|
||||
if (script->getSigintReceived()) {
|
||||
script->setSigintReceived(false);
|
||||
throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s);
|
||||
} else if (timeout) {
|
||||
throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *timeout, "ms"_s));
|
||||
} else {
|
||||
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to SIGINT nor to timeout");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void setupWatchdog(VM& vm, double timeout, double* oldTimeout, double* newTimeout)
|
||||
{
|
||||
JSC::JSLockHolder locker(vm);
|
||||
JSC::Watchdog& dog = vm.ensureWatchdog();
|
||||
dog.enteredVM();
|
||||
|
||||
Seconds oldLimit = dog.getTimeLimit();
|
||||
|
||||
if (oldTimeout) {
|
||||
*oldTimeout = oldLimit.milliseconds();
|
||||
}
|
||||
|
||||
if (oldLimit.isInfinity() || timeout < oldLimit.milliseconds()) {
|
||||
dog.setTimeLimit(WTF::Seconds::fromMilliseconds(timeout));
|
||||
} else {
|
||||
timeout = oldLimit.milliseconds();
|
||||
}
|
||||
|
||||
if (newTimeout) {
|
||||
*newTimeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false)
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
RunningScriptOptions options;
|
||||
if (allowStringInPlaceOfOptions && optionsArg.isString()) {
|
||||
options.filename = optionsArg.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
} else if (!options.fromJS(globalObject, vm, scope, optionsArg)) {
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
options = {};
|
||||
}
|
||||
|
||||
// Set the contextified object before evaluating
|
||||
globalObject->setContextifiedObject(contextifiedObject);
|
||||
|
||||
NakedPtr<JSC::Exception> exception;
|
||||
JSValue result {};
|
||||
auto run = [&] {
|
||||
result = JSC::evaluate(globalObject, script->source(), globalObject, exception);
|
||||
};
|
||||
|
||||
std::optional<double> oldLimit, newLimit;
|
||||
|
||||
if (options.timeout) {
|
||||
setupWatchdog(vm, *options.timeout, &oldLimit.emplace(), &newLimit.emplace());
|
||||
}
|
||||
|
||||
script->setSigintReceived(false);
|
||||
|
||||
if (options.breakOnSigint) {
|
||||
auto holder = SigintWatcher::hold(globalObject, script);
|
||||
run();
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
|
||||
if (options.timeout) {
|
||||
vm.watchdog()->setTimeLimit(WTF::Seconds::fromMilliseconds(*oldLimit));
|
||||
}
|
||||
|
||||
if (checkForTermination(globalObject, scope, script, newLimit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
script->setSigintReceived(false);
|
||||
|
||||
if (exception) [[unlikely]] {
|
||||
if (handleException(globalObject, vm, exception, scope)) {
|
||||
return {};
|
||||
}
|
||||
JSC::throwException(globalObject, scope, exception.get());
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSValue::encode(result);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue thisValue = callFrame->thisValue();
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
JSValue optionsArg = callFrame->argument(0);
|
||||
|
||||
RunningScriptOptions options;
|
||||
if (!options.fromJS(globalObject, vm, scope, optionsArg)) {
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
options = {};
|
||||
}
|
||||
|
||||
NakedPtr<JSC::Exception> exception;
|
||||
JSValue result {};
|
||||
auto run = [&] {
|
||||
result = JSC::evaluate(globalObject, script->source(), globalObject, exception);
|
||||
};
|
||||
|
||||
std::optional<double> oldLimit, newLimit;
|
||||
|
||||
if (options.timeout) {
|
||||
setupWatchdog(vm, *options.timeout, &oldLimit.emplace(), &newLimit.emplace());
|
||||
}
|
||||
|
||||
script->setSigintReceived(false);
|
||||
|
||||
if (options.breakOnSigint) {
|
||||
auto holder = SigintWatcher::hold(globalObject, script);
|
||||
vm.ensureTerminationException();
|
||||
run();
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
|
||||
if (options.timeout) {
|
||||
vm.watchdog()->setTimeLimit(WTF::Seconds::fromMilliseconds(*oldLimit));
|
||||
}
|
||||
|
||||
if (checkForTermination(globalObject, scope, script, newLimit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
script->setSigintReceived(false);
|
||||
|
||||
if (exception) [[unlikely]] {
|
||||
if (handleException(globalObject, vm, exception, scope)) {
|
||||
return {};
|
||||
}
|
||||
JSC::throwException(globalObject, scope, exception.get());
|
||||
return {};
|
||||
}
|
||||
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
return JSValue::encode(result);
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSValue thisValue = JSValue::decode(thisValueEncoded);
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
const auto& url = script->source().provider()->sourceMappingURLDirective();
|
||||
return JSValue::encode(jsString(vm, url));
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSValue thisValue = JSValue::decode(thisValueEncoded);
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
if (auto* buffer = script->getBytecodeBuffer()) {
|
||||
return JSValue::encode(buffer);
|
||||
}
|
||||
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSValue thisValue = JSValue::decode(thisValueEncoded);
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
return JSValue::encode(jsBoolean(script->cachedDataProduced()));
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSValue thisValue = JSValue::decode(thisValueEncoded);
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
switch (script->cachedDataRejected()) {
|
||||
case TriState::True:
|
||||
return JSValue::encode(jsBoolean(true));
|
||||
case TriState::False:
|
||||
return JSValue::encode(jsBoolean(false));
|
||||
default:
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue thisValue = callFrame->thisValue();
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
const JSC::SourceCode& source = script->source();
|
||||
return createCachedData(globalObject, source);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue thisValue = callFrame->thisValue();
|
||||
auto* script = jsDynamicCast<NodeVMScript*>(thisValue);
|
||||
if (!script) [[unlikely]] {
|
||||
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
|
||||
}
|
||||
|
||||
ArgList args(callFrame);
|
||||
JSValue contextArg = args.at(0);
|
||||
NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, contextArg, true);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
JSObject* context = asObject(contextArg);
|
||||
ASSERT(nodeVmGlobalObject != nullptr);
|
||||
|
||||
return runInContext(nodeVmGlobalObject, script, context, args.at(1));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = JSC::getVM(globalObject);
|
||||
NodeVMScript* script = jsDynamicCast<NodeVMScript*>(callFrame->thisValue());
|
||||
JSValue contextObjectValue = callFrame->argument(0);
|
||||
// TODO: options
|
||||
// JSValue optionsObjectValue = callFrame->argument(1);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (!script) {
|
||||
throwTypeError(globalObject, scope, "this.runInContext is not a function"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (contextObjectValue.isUndefined()) {
|
||||
contextObjectValue = JSC::constructEmptyObject(globalObject);
|
||||
}
|
||||
|
||||
if (!contextObjectValue || !contextObjectValue.isObject()) [[unlikely]] {
|
||||
throwTypeError(globalObject, scope, "Context must be an object"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
// we don't care about options for now
|
||||
// TODO: options
|
||||
// bool didThrow = false;
|
||||
|
||||
auto* zigGlobal = defaultGlobalObject(globalObject);
|
||||
JSObject* context = asObject(contextObjectValue);
|
||||
auto* targetContext = NodeVMGlobalObject::create(vm,
|
||||
zigGlobal->NodeVMGlobalObjectStructure(),
|
||||
{});
|
||||
|
||||
return runInContext(targetContext, script, context, callFrame->argument(1));
|
||||
}
|
||||
|
||||
class NodeVMScriptPrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
|
||||
static NodeVMScriptPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
|
||||
{
|
||||
NodeVMScriptPrototype* ptr = new (NotNull, allocateCell<NodeVMScriptPrototype>(vm)) NodeVMScriptPrototype(vm, structure);
|
||||
ptr->finishCreation(vm);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
template<typename CellType, SubspaceAccess>
|
||||
static GCClient::IsoSubspace* subspaceFor(VM& vm)
|
||||
{
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, Base);
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
|
||||
{
|
||||
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
NodeVMScriptPrototype(VM& vm, Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(VM&);
|
||||
};
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, NodeVMScriptPrototype::Base);
|
||||
|
||||
static const struct HashTableValue scriptPrototypeTableValues[] = {
|
||||
{ "createCachedData"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptCreateCachedData, 1 } },
|
||||
{ "runInContext"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInContext, 2 } },
|
||||
{ "runInNewContext"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInNewContext, 2 } },
|
||||
{ "runInThisContext"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInThisContext, 2 } },
|
||||
{ "sourceMapURL"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetSourceMapURL, nullptr } },
|
||||
{ "cachedData"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedData, nullptr } },
|
||||
{ "cachedDataProduced"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataProduced, nullptr } },
|
||||
{ "cachedDataRejected"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } },
|
||||
};
|
||||
|
||||
void NodeVMScriptPrototype::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
reifyStaticProperties(vm, NodeVMScript::info(), scriptPrototypeTableValues, *this);
|
||||
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
||||
}
|
||||
|
||||
JSObject* NodeVMScript::createPrototype(VM& vm, JSGlobalObject* globalObject)
|
||||
{
|
||||
return NodeVMScriptPrototype::create(vm, globalObject, NodeVMScriptPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
|
||||
}
|
||||
|
||||
const ClassInfo NodeVMScript::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScript) };
|
||||
const ClassInfo NodeVMScriptPrototype::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptPrototype) };
|
||||
const ClassInfo NodeVMScriptConstructor::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptConstructor) };
|
||||
|
||||
bool RunningScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg)
|
||||
{
|
||||
bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
if (!optionsArg.isUndefined() && !optionsArg.isString()) {
|
||||
JSObject* options = asObject(optionsArg);
|
||||
|
||||
if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) {
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
if (!displayErrorsOpt.isUndefined()) {
|
||||
if (!displayErrorsOpt.isBoolean()) {
|
||||
ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt);
|
||||
return false;
|
||||
}
|
||||
this->displayErrors = displayErrorsOpt.asBoolean();
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (validateTimeout(globalObject, vm, scope, options, this->timeout)) {
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
any = true;
|
||||
}
|
||||
|
||||
if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) {
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
if (!breakOnSigintOpt.isUndefined()) {
|
||||
if (!breakOnSigintOpt.isBoolean()) {
|
||||
ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt);
|
||||
return false;
|
||||
}
|
||||
this->breakOnSigint = breakOnSigintOpt.asBoolean();
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return any;
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
113
src/bun.js/bindings/NodeVMScript.h
Normal file
113
src/bun.js/bindings/NodeVMScript.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include "NodeVM.h"
|
||||
|
||||
#include "../vm/SigintReceiver.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class ScriptOptions : public BaseVMOptions {
|
||||
public:
|
||||
std::optional<int64_t> timeout = std::nullopt;
|
||||
bool produceCachedData = false;
|
||||
WTF::Vector<uint8_t> cachedData;
|
||||
|
||||
using BaseVMOptions::BaseVMOptions;
|
||||
|
||||
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg);
|
||||
};
|
||||
|
||||
class NodeVMScriptConstructor final : public JSC::InternalFunction {
|
||||
public:
|
||||
using Base = JSC::InternalFunction;
|
||||
|
||||
static NodeVMScriptConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype);
|
||||
|
||||
DECLARE_EXPORT_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
NodeVMScriptConstructor(JSC::VM& vm, JSC::Structure* structure);
|
||||
|
||||
void finishCreation(JSC::VM&, JSC::JSObject* prototype);
|
||||
};
|
||||
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction);
|
||||
|
||||
class NodeVMScript final : public JSC::JSDestructibleObject, public SigintReceiver {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
|
||||
static NodeVMScript* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options);
|
||||
|
||||
DECLARE_EXPORT_INFO;
|
||||
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<NodeVMScript, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForNodeVMScript.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMScript = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForNodeVMScript.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMScript = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
static void destroy(JSC::JSCell*);
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject);
|
||||
|
||||
JSC::ProgramExecutable* createExecutable();
|
||||
void cacheBytecode();
|
||||
JSC::JSUint8Array* getBytecodeBuffer();
|
||||
|
||||
const JSC::SourceCode& source() const { return m_source; }
|
||||
WTF::Vector<uint8_t>& cachedData() { return m_options.cachedData; }
|
||||
RefPtr<JSC::CachedBytecode> cachedBytecode() const { return m_cachedBytecode; }
|
||||
JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); }
|
||||
bool cachedDataProduced() const { return m_cachedDataProduced; }
|
||||
void cachedDataProduced(bool value) { m_cachedDataProduced = value; }
|
||||
TriState cachedDataRejected() const { return m_cachedDataRejected; }
|
||||
void cachedDataRejected(TriState value) { m_cachedDataRejected = value; }
|
||||
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
private:
|
||||
JSC::SourceCode m_source;
|
||||
RefPtr<JSC::CachedBytecode> m_cachedBytecode;
|
||||
JSC::WriteBarrier<JSC::JSUint8Array> m_cachedBytecodeBuffer;
|
||||
JSC::WriteBarrier<JSC::ProgramExecutable> m_cachedExecutable;
|
||||
ScriptOptions m_options;
|
||||
bool m_cachedDataProduced = false;
|
||||
TriState m_cachedDataRejected = TriState::Indeterminate;
|
||||
|
||||
NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options)
|
||||
: Base(vm, structure)
|
||||
, m_source(source)
|
||||
, m_options(WTFMove(options))
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
|
||||
class RunningScriptOptions : public BaseVMOptions {
|
||||
public:
|
||||
bool displayErrors = true;
|
||||
std::optional<int64_t> timeout = std::nullopt;
|
||||
bool breakOnSigint = false;
|
||||
|
||||
using BaseVMOptions::BaseVMOptions;
|
||||
|
||||
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg);
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
461
src/bun.js/bindings/NodeVMSourceTextModule.cpp
Normal file
461
src/bun.js/bindings/NodeVMSourceTextModule.cpp
Normal file
@@ -0,0 +1,461 @@
|
||||
#include "NodeVMSourceTextModule.h"
|
||||
|
||||
#include "ErrorCode.h"
|
||||
#include "JSDOMExceptionHandling.h"
|
||||
|
||||
#include "wtf/Scope.h"
|
||||
|
||||
#include "JavaScriptCore/JIT.h"
|
||||
#include "JavaScriptCore/JSModuleRecord.h"
|
||||
#include "JavaScriptCore/JSPromise.h"
|
||||
#include "JavaScriptCore/JSSourceCode.h"
|
||||
#include "JavaScriptCore/ModuleAnalyzer.h"
|
||||
#include "JavaScriptCore/ModuleProgramCodeBlock.h"
|
||||
#include "JavaScriptCore/Parser.h"
|
||||
#include "JavaScriptCore/SourceCodeKey.h"
|
||||
#include "JavaScriptCore/Watchdog.h"
|
||||
|
||||
#include "../vm/SigintWatcher.h"
|
||||
|
||||
namespace Bun {
|
||||
using namespace NodeVM;
|
||||
|
||||
NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* globalObject, ArgList args)
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue identifierValue = args.at(0);
|
||||
if (!identifierValue.isString()) {
|
||||
throwArgumentTypeError(*globalObject, scope, 0, "identifier"_s, "Module"_s, "Module"_s, "string"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSValue contextValue = args.at(1);
|
||||
if (contextValue.isUndefined()) {
|
||||
contextValue = globalObject;
|
||||
} else if (!contextValue.isObject()) {
|
||||
throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSValue sourceTextValue = args.at(2);
|
||||
if (!sourceTextValue.isString()) {
|
||||
throwArgumentTypeError(*globalObject, scope, 2, "sourceText"_s, "Module"_s, "Module"_s, "string"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSValue lineOffsetValue = args.at(3);
|
||||
if (!lineOffsetValue.isUInt32AsAnyInt()) {
|
||||
throwArgumentTypeError(*globalObject, scope, 3, "lineOffset"_s, "Module"_s, "Module"_s, "number"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSValue columnOffsetValue = args.at(4);
|
||||
if (!columnOffsetValue.isUInt32AsAnyInt()) {
|
||||
throwArgumentTypeError(*globalObject, scope, 4, "columnOffset"_s, "Module"_s, "Module"_s, "number"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSValue cachedDataValue = args.at(5);
|
||||
WTF::Vector<uint8_t> cachedData;
|
||||
if (!cachedDataValue.isUndefined() && !extractCachedData(cachedDataValue, cachedData)) {
|
||||
Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataValue);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t lineOffset = lineOffsetValue.toUInt32(globalObject);
|
||||
uint32_t columnOffset = columnOffsetValue.toUInt32(globalObject);
|
||||
|
||||
Ref<StringSourceProvider> sourceProvider = StringSourceProvider::create(sourceTextValue.toWTFString(globalObject), SourceOrigin {}, String {}, SourceTaintedOrigin::Untainted,
|
||||
TextPosition { OrdinalNumber::fromZeroBasedInt(lineOffset), OrdinalNumber::fromZeroBasedInt(columnOffset) }, SourceProviderSourceType::Module);
|
||||
|
||||
SourceCode sourceCode(WTFMove(sourceProvider), lineOffset, columnOffset);
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
NodeVMSourceTextModule* ptr = new (NotNull, allocateCell<NodeVMSourceTextModule>(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), contextValue, WTFMove(sourceCode));
|
||||
ptr->finishCreation(vm);
|
||||
|
||||
if (cachedData.isEmpty()) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
ModuleProgramExecutable* executable = ModuleProgramExecutable::tryCreate(globalObject, ptr->sourceCode());
|
||||
if (!executable) {
|
||||
// If an exception is already being thrown, don't throw another one.
|
||||
// ModuleProgramExecutable::tryCreate() sometimes throws on failure, but sometimes it doesn't.
|
||||
if (!scope.exception()) {
|
||||
throwSyntaxError(globalObject, scope, "Failed to create cached executable"_s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ptr->m_cachedExecutable.set(vm, ptr, executable);
|
||||
LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures;
|
||||
SourceCodeKey key(ptr->sourceCode(), {}, SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, DerivedContextType::None, EvalContextType::None, false, {}, std::nullopt);
|
||||
Ref<CachedBytecode> cachedBytecode = CachedBytecode::create(std::span(cachedData), nullptr, {});
|
||||
UnlinkedModuleProgramCodeBlock* unlinkedBlock = decodeCodeBlock<UnlinkedModuleProgramCodeBlock>(vm, key, WTFMove(cachedBytecode));
|
||||
|
||||
if (unlinkedBlock) {
|
||||
JSScope* jsScope = globalObject->globalScope();
|
||||
CodeBlock* codeBlock = nullptr;
|
||||
{
|
||||
// JSC::ProgramCodeBlock::create() requires GC to be deferred.
|
||||
DeferGC deferGC(vm);
|
||||
codeBlock = ModuleProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope);
|
||||
}
|
||||
if (codeBlock) {
|
||||
CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail);
|
||||
if (compilationResult != CompilationResult::CompilationFailed) {
|
||||
executable->installCode(codeBlock);
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_CACHED_DATA_REJECTED, "cachedData buffer was rejected"_s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NodeVMSourceTextModule::destroy(JSCell* cell)
|
||||
{
|
||||
static_cast<NodeVMSourceTextModule*>(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule();
|
||||
}
|
||||
|
||||
JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject)
|
||||
{
|
||||
if (m_moduleRequestsArray) {
|
||||
return m_moduleRequestsArray.get();
|
||||
}
|
||||
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
ParserError parserError;
|
||||
|
||||
std::unique_ptr<ModuleProgramNode> node = parseRootNode<ModuleProgramNode>(vm, m_sourceCode,
|
||||
ImplementationVisibility::Public,
|
||||
JSParserBuiltinMode::NotBuiltin,
|
||||
StrictModeLexicallyScopedFeature,
|
||||
JSParserScriptMode::Module,
|
||||
SourceParseMode::ModuleAnalyzeMode,
|
||||
parserError);
|
||||
|
||||
if (parserError.isValid()) {
|
||||
throwException(globalObject, scope, parserError.toErrorObject(globalObject, m_sourceCode));
|
||||
return {};
|
||||
}
|
||||
|
||||
ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, node->varDeclarations(), node->lexicalVariables(), AllFeatures);
|
||||
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
ASSERT(node != nullptr);
|
||||
|
||||
JSModuleRecord* moduleRecord = nullptr;
|
||||
|
||||
if (auto result = analyzer.analyze(*node)) {
|
||||
moduleRecord = *result;
|
||||
} else {
|
||||
auto [type, message] = result.error();
|
||||
throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_LINK_FAILURE, message);
|
||||
return {};
|
||||
}
|
||||
|
||||
m_moduleRecord.set(vm, this, moduleRecord);
|
||||
m_moduleRequests.clear();
|
||||
|
||||
const auto& requests = moduleRecord->requestedModules();
|
||||
|
||||
if (requests.isEmpty()) {
|
||||
return constructEmptyArray(globalObject, nullptr, 0);
|
||||
}
|
||||
|
||||
JSArray* requestsArray = constructEmptyArray(globalObject, nullptr, requests.size());
|
||||
|
||||
const auto& builtinNames = WebCore::clientData(vm)->builtinNames();
|
||||
const Identifier& specifierIdentifier = builtinNames.specifierPublicName();
|
||||
const Identifier& attributesIdentifier = builtinNames.attributesPublicName();
|
||||
const Identifier& hostDefinedImportTypeIdentifier = builtinNames.hostDefinedImportTypePublicName();
|
||||
|
||||
WTF::Vector<ImportAttributesListNode*, 8> attributesNodes;
|
||||
attributesNodes.reserveInitialCapacity(requests.size());
|
||||
|
||||
for (StatementNode* statement = node->statements()->firstStatement(); statement; statement = statement->next()) {
|
||||
// Assumption: module declarations occur here in the same order they occur in `requestedModules`.
|
||||
if (statement->isModuleDeclarationNode()) {
|
||||
ModuleDeclarationNode* moduleDeclaration = static_cast<ModuleDeclarationNode*>(statement);
|
||||
if (moduleDeclaration->isImportDeclarationNode()) {
|
||||
ImportDeclarationNode* importDeclaration = static_cast<ImportDeclarationNode*>(moduleDeclaration);
|
||||
ASSERT_WITH_MESSAGE(attributesNodes.size() < requests.size(), "More attributes nodes than requests");
|
||||
ASSERT_WITH_MESSAGE(importDeclaration->moduleName()->moduleName().string().string() == WTF::String(*requests.at(attributesNodes.size()).m_specifier), "Module name mismatch");
|
||||
attributesNodes.append(importDeclaration->attributesList());
|
||||
} else if (moduleDeclaration->hasAttributesList()) {
|
||||
// Necessary to make the indices of `attributesNodes` and `requests` match up
|
||||
attributesNodes.append(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_WITH_MESSAGE(attributesNodes.size() == requests.size(), "Attributes node count doesn't match request count (%zu != %zu)", attributesNodes.size(), requests.size());
|
||||
|
||||
for (unsigned i = 0; i < requests.size(); ++i) {
|
||||
const auto& request = requests[i];
|
||||
|
||||
JSString* specifierValue = JSC::jsString(vm, WTF::String(*request.m_specifier));
|
||||
|
||||
JSObject* requestObject = constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
|
||||
requestObject->putDirect(vm, specifierIdentifier, specifierValue);
|
||||
|
||||
WTF::String attributesTypeString = "unknown"_str;
|
||||
|
||||
WTF::HashMap<WTF::String, WTF::String> attributeMap;
|
||||
JSObject* attributesObject = constructEmptyObject(globalObject);
|
||||
|
||||
if (request.m_attributes) {
|
||||
JSValue attributesType {};
|
||||
switch (request.m_attributes->type()) {
|
||||
using AttributeType = decltype(request.m_attributes->type());
|
||||
using enum AttributeType;
|
||||
case None:
|
||||
attributesTypeString = "none"_str;
|
||||
attributesType = JSC::jsString(vm, attributesTypeString);
|
||||
break;
|
||||
case JavaScript:
|
||||
attributesTypeString = "javascript"_str;
|
||||
attributesType = JSC::jsString(vm, attributesTypeString);
|
||||
break;
|
||||
case WebAssembly:
|
||||
attributesTypeString = "webassembly"_str;
|
||||
attributesType = JSC::jsString(vm, attributesTypeString);
|
||||
break;
|
||||
case JSON:
|
||||
attributesTypeString = "json"_str;
|
||||
attributesType = JSC::jsString(vm, attributesTypeString);
|
||||
break;
|
||||
default:
|
||||
attributesType = JSC::jsNumber(static_cast<uint8_t>(request.m_attributes->type()));
|
||||
break;
|
||||
}
|
||||
|
||||
attributeMap.set("type"_s, WTFMove(attributesTypeString));
|
||||
attributesObject->putDirect(vm, JSC::Identifier::fromString(vm, "type"_s), attributesType);
|
||||
|
||||
if (const String& hostDefinedImportType = request.m_attributes->hostDefinedImportType(); !hostDefinedImportType.isEmpty()) {
|
||||
attributesObject->putDirect(vm, hostDefinedImportTypeIdentifier, JSC::jsString(vm, hostDefinedImportType));
|
||||
attributeMap.set("hostDefinedImportType"_s, hostDefinedImportType);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImportAttributesListNode* attributesNode = attributesNodes.at(i)) {
|
||||
for (auto [key, value] : attributesNode->attributes()) {
|
||||
attributeMap.set(key->string(), value->string());
|
||||
attributesObject->putDirect(vm, *key, JSC::jsString(vm, value->string()));
|
||||
}
|
||||
}
|
||||
|
||||
requestObject->putDirect(vm, attributesIdentifier, attributesObject);
|
||||
addModuleRequest({ WTF::String(*request.m_specifier), WTFMove(attributeMap) });
|
||||
requestsArray->putDirectIndex(globalObject, i, requestObject);
|
||||
}
|
||||
|
||||
m_moduleRequestsArray.set(vm, this, requestsArray);
|
||||
return requestsArray;
|
||||
}
|
||||
|
||||
void NodeVMSourceTextModule::ensureModuleRecord(JSGlobalObject* globalObject)
|
||||
{
|
||||
if (!m_moduleRecord) {
|
||||
createModuleRecord(globalObject);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractModuleRecord* NodeVMSourceTextModule::moduleRecord(JSGlobalObject* globalObject)
|
||||
{
|
||||
ensureModuleRecord(globalObject);
|
||||
return m_moduleRecord.get();
|
||||
}
|
||||
|
||||
JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives, JSValue scriptFetcher)
|
||||
{
|
||||
const unsigned length = specifiers->getArrayLength();
|
||||
|
||||
ASSERT(length == moduleNatives->getArrayLength());
|
||||
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (m_status != Status::Unlinked) {
|
||||
throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_STATUS, "Module must be unlinked before linking"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSModuleRecord* record = m_moduleRecord.get();
|
||||
|
||||
if (length != 0) {
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
JSValue specifierValue = specifiers->getDirectIndex(globalObject, i);
|
||||
JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i);
|
||||
|
||||
ASSERT(specifierValue.isString());
|
||||
ASSERT(moduleNativeValue.isObject());
|
||||
|
||||
WTF::String specifier = specifierValue.toWTFString(globalObject);
|
||||
JSObject* moduleNative = moduleNativeValue.getObject();
|
||||
AbstractModuleRecord* resolvedRecord = jsCast<NodeVMModule*>(moduleNative)->moduleRecord(globalObject);
|
||||
|
||||
record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord);
|
||||
m_resolveCache.set(WTFMove(specifier), WriteBarrier<JSObject> { vm, this, moduleNative });
|
||||
}
|
||||
}
|
||||
|
||||
if (NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false)) {
|
||||
globalObject = nodeVmGlobalObject;
|
||||
}
|
||||
|
||||
Synchronousness sync = record->link(globalObject, scriptFetcher);
|
||||
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (sync == Synchronousness::Async) {
|
||||
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking");
|
||||
}
|
||||
|
||||
status(Status::Linked);
|
||||
return JSC::jsUndefined();
|
||||
}
|
||||
|
||||
JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint)
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (m_status != Status::Linked && m_status != Status::Evaluated && m_status != Status::Errored) {
|
||||
throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_STATUS, "Module must be linked, evaluated or errored before evaluating"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSModuleRecord* record = m_moduleRecord.get();
|
||||
JSValue result {};
|
||||
|
||||
NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false);
|
||||
|
||||
if (nodeVmGlobalObject) {
|
||||
globalObject = nodeVmGlobalObject;
|
||||
}
|
||||
|
||||
auto run = [&] {
|
||||
status(Status::Evaluating);
|
||||
|
||||
for (const auto& request : record->requestedModules()) {
|
||||
if (auto iter = m_resolveCache.find(WTF::String(*request.m_specifier)); iter != m_resolveCache.end()) {
|
||||
if (auto* dependency = jsDynamicCast<NodeVMSourceTextModule*>(iter->value.get())) {
|
||||
if (dependency->status() == Status::Linked) {
|
||||
JSValue dependencyResult = dependency->evaluate(globalObject, timeout, breakOnSigint);
|
||||
RELEASE_ASSERT_WITH_MESSAGE(jsDynamicCast<JSC::JSPromise*>(dependencyResult) == nullptr, "TODO(@heimskr): implement async support for node:vm SourceTextModule dependencies");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast<int32_t>(JSGenerator::ResumeMode::NormalMode)));
|
||||
};
|
||||
|
||||
setSigintReceived(false);
|
||||
|
||||
if (timeout != 0) {
|
||||
JSC::JSLockHolder locker(vm);
|
||||
JSC::Watchdog& dog = vm.ensureWatchdog();
|
||||
dog.enteredVM();
|
||||
dog.setTimeLimit(WTF::Seconds::fromMilliseconds(timeout));
|
||||
}
|
||||
|
||||
if (breakOnSigint) {
|
||||
auto holder = SigintWatcher::hold(nodeVmGlobalObject, this);
|
||||
run();
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
|
||||
if (timeout != 0) {
|
||||
vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit);
|
||||
}
|
||||
|
||||
if (vm.hasPendingTerminationException()) {
|
||||
scope.clearException();
|
||||
vm.clearHasTerminationRequest();
|
||||
if (getSigintReceived()) {
|
||||
setSigintReceived(false);
|
||||
throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s);
|
||||
} else {
|
||||
throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, timeout, "ms"_s));
|
||||
}
|
||||
} else {
|
||||
setSigintReceived(false);
|
||||
}
|
||||
|
||||
if (JSC::Exception* exception = scope.exception()) {
|
||||
status(Status::Errored);
|
||||
m_evaluationException.set(vm, this, exception);
|
||||
return {};
|
||||
}
|
||||
|
||||
status(Status::Evaluated);
|
||||
return result;
|
||||
}
|
||||
|
||||
RefPtr<CachedBytecode> NodeVMSourceTextModule::bytecode(JSGlobalObject* globalObject)
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (!m_bytecode) {
|
||||
if (!m_cachedExecutable) {
|
||||
ModuleProgramExecutable* executable = ModuleProgramExecutable::tryCreate(globalObject, m_sourceCode);
|
||||
if (!executable) {
|
||||
if (!scope.exception()) {
|
||||
throwSyntaxError(globalObject, scope, "Failed to create cached executable"_s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
m_cachedExecutable.set(vm, this, executable);
|
||||
}
|
||||
m_bytecode = getBytecode(globalObject, m_cachedExecutable.get(), m_sourceCode);
|
||||
}
|
||||
|
||||
return m_bytecode;
|
||||
}
|
||||
|
||||
JSUint8Array* NodeVMSourceTextModule::cachedData(JSGlobalObject* globalObject)
|
||||
{
|
||||
if (!m_cachedBytecodeBuffer) {
|
||||
RefPtr<CachedBytecode> cachedBytecode = bytecode(globalObject);
|
||||
std::span<const uint8_t> bytes = cachedBytecode->span();
|
||||
m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject, bytes));
|
||||
}
|
||||
|
||||
return m_cachedBytecodeBuffer.get();
|
||||
}
|
||||
|
||||
JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject)
|
||||
{
|
||||
return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void NodeVMSourceTextModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
auto* vmModule = jsCast<NodeVMSourceTextModule*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(vmModule, info());
|
||||
Base::visitChildren(vmModule, visitor);
|
||||
|
||||
visitor.append(vmModule->m_moduleRecord);
|
||||
visitor.append(vmModule->m_moduleRequestsArray);
|
||||
visitor.append(vmModule->m_cachedExecutable);
|
||||
visitor.append(vmModule->m_cachedBytecodeBuffer);
|
||||
visitor.append(vmModule->m_evaluationException);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(NodeVMSourceTextModule);
|
||||
|
||||
const JSC::ClassInfo NodeVMSourceTextModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) };
|
||||
|
||||
} // namespace Bun
|
||||
73
src/bun.js/bindings/NodeVMSourceTextModule.h
Normal file
73
src/bun.js/bindings/NodeVMSourceTextModule.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "NodeVM.h"
|
||||
#include "NodeVMModule.h"
|
||||
|
||||
#include "../vm/SigintReceiver.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class NodeVMSourceTextModule final : public NodeVMModule, public SigintReceiver {
|
||||
public:
|
||||
using Base = NodeVMModule;
|
||||
|
||||
static NodeVMSourceTextModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args);
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<NodeVMSourceTextModule, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSourceTextModule.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSourceTextModule = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForNodeVMSourceTextModule.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject);
|
||||
static void destroy(JSC::JSCell* cell);
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
JSValue createModuleRecord(JSGlobalObject* globalObject);
|
||||
void ensureModuleRecord(JSGlobalObject* globalObject);
|
||||
bool hasModuleRecord() const { return !!m_moduleRecord; }
|
||||
AbstractModuleRecord* moduleRecord(JSGlobalObject* globalObject);
|
||||
JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives, JSValue scriptFetcher);
|
||||
JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint);
|
||||
RefPtr<CachedBytecode> bytecode(JSGlobalObject* globalObject);
|
||||
JSUint8Array* cachedData(JSGlobalObject* globalObject);
|
||||
Exception* evaluationException() const { return m_evaluationException.get(); }
|
||||
|
||||
const SourceCode& sourceCode() const { return m_sourceCode; }
|
||||
ModuleProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); }
|
||||
|
||||
DECLARE_EXPORT_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
private:
|
||||
WriteBarrier<JSModuleRecord> m_moduleRecord;
|
||||
WriteBarrier<JSArray> m_moduleRequestsArray;
|
||||
WriteBarrier<ModuleProgramExecutable> m_cachedExecutable;
|
||||
WriteBarrier<JSUint8Array> m_cachedBytecodeBuffer;
|
||||
WriteBarrier<Exception> m_evaluationException;
|
||||
RefPtr<CachedBytecode> m_bytecode;
|
||||
SourceCode m_sourceCode;
|
||||
|
||||
NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context, SourceCode sourceCode)
|
||||
: Base(vm, structure, WTFMove(identifier), context)
|
||||
, m_sourceCode(WTFMove(sourceCode))
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
@@ -1317,7 +1317,7 @@ void GlobalObject::promiseRejectionTracker(JSGlobalObject* obj, JSC::JSPromise*
|
||||
// obj, prom, reject == JSC::JSPromiseRejectionOperation::Reject ? 0 : 1);
|
||||
|
||||
// Do this in C++ for now
|
||||
auto* globalObj = reinterpret_cast<GlobalObject*>(obj);
|
||||
auto* globalObj = static_cast<GlobalObject*>(obj);
|
||||
switch (operation) {
|
||||
case JSPromiseRejectionOperation::Reject:
|
||||
globalObj->m_aboutToBeNotifiedRejectedPromises.append(JSC::Strong<JSPromise>(obj->vm(), promise));
|
||||
@@ -1759,7 +1759,7 @@ extern "C" JSC::EncodedJSValue Bun__createUint8ArrayForCopy(JSC::JSGlobalObject*
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* subclassStructure = isBuffer ? reinterpret_cast<Zig::GlobalObject*>(globalObject)->JSBufferSubclassStructure() : globalObject->typedArrayStructureWithTypedArrayType<TypeUint8>();
|
||||
auto* subclassStructure = isBuffer ? static_cast<Zig::GlobalObject*>(globalObject)->JSBufferSubclassStructure() : globalObject->typedArrayStructureWithTypedArrayType<TypeUint8>();
|
||||
JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, subclassStructure, len);
|
||||
|
||||
if (!array) [[unlikely]] {
|
||||
@@ -1869,7 +1869,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionDispatchEvent, (JSGlobalObject * lexicalGloba
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(getterSubtleCrypto, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
|
||||
{
|
||||
return JSValue::encode(reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->subtleCrypto());
|
||||
return JSValue::encode(static_cast<Zig::GlobalObject*>(lexicalGlobalObject)->subtleCrypto());
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue ExpectMatcherUtils_createSigleton(JSC::JSGlobalObject* lexicalGlobalObject);
|
||||
@@ -2235,7 +2235,7 @@ static inline JSC::EncodedJSValue ZigGlobalObject__readableStreamToArrayBufferBo
|
||||
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToArrayBuffer(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue);
|
||||
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToArrayBuffer(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
|
||||
{
|
||||
return ZigGlobalObject__readableStreamToArrayBufferBody(reinterpret_cast<Zig::GlobalObject*>(globalObject), readableStreamValue);
|
||||
return ZigGlobalObject__readableStreamToArrayBufferBody(static_cast<Zig::GlobalObject*>(globalObject), readableStreamValue);
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToBytes(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue);
|
||||
@@ -2378,7 +2378,7 @@ JSC_DEFINE_HOST_FUNCTION(functionReadableStreamToArrayBuffer, (JSGlobalObject *
|
||||
}
|
||||
|
||||
auto readableStreamValue = callFrame->uncheckedArgument(0);
|
||||
return ZigGlobalObject__readableStreamToArrayBufferBody(reinterpret_cast<Zig::GlobalObject*>(globalObject), JSValue::encode(readableStreamValue));
|
||||
return ZigGlobalObject__readableStreamToArrayBufferBody(static_cast<Zig::GlobalObject*>(globalObject), JSValue::encode(readableStreamValue));
|
||||
}
|
||||
|
||||
JSC_DECLARE_HOST_FUNCTION(functionReadableStreamToBytes);
|
||||
@@ -2393,7 +2393,7 @@ JSC_DEFINE_HOST_FUNCTION(functionReadableStreamToBytes, (JSGlobalObject * global
|
||||
}
|
||||
|
||||
auto readableStreamValue = callFrame->uncheckedArgument(0);
|
||||
return ZigGlobalObject__readableStreamToBytes(reinterpret_cast<Zig::GlobalObject*>(globalObject), JSValue::encode(readableStreamValue));
|
||||
return ZigGlobalObject__readableStreamToBytes(static_cast<Zig::GlobalObject*>(globalObject), JSValue::encode(readableStreamValue));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotask, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
@@ -2531,7 +2531,7 @@ void GlobalObject::createCallSitesFromFrames(Zig::GlobalObject* globalObject, JS
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject);
|
||||
GlobalObject* globalObject = static_cast<GlobalObject*>(lexicalGlobalObject);
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
@@ -2615,7 +2615,7 @@ JSC_DEFINE_CUSTOM_SETTER(errorInstanceLazyStackCustomSetter, (JSGlobalObject * g
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject);
|
||||
GlobalObject* globalObject = static_cast<GlobalObject*>(lexicalGlobalObject);
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
@@ -2837,7 +2837,7 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
|
||||
m_commonJSModuleObjectStructure.initLater(
|
||||
[](const Initializer<Structure>& init) {
|
||||
init.set(Bun::createCommonJSModuleStructure(reinterpret_cast<Zig::GlobalObject*>(init.owner)));
|
||||
init.set(Bun::createCommonJSModuleStructure(static_cast<Zig::GlobalObject*>(init.owner)));
|
||||
});
|
||||
|
||||
m_JSSocketAddressDTOStructure.initLater(
|
||||
@@ -2883,7 +2883,7 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
|
||||
init.set(
|
||||
createMemoryFootprintStructure(
|
||||
init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
|
||||
init.vm, static_cast<Zig::GlobalObject*>(init.owner)));
|
||||
});
|
||||
|
||||
m_errorConstructorPrepareStackTraceInternalValue.initLater(
|
||||
@@ -2906,14 +2906,14 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
|
||||
m_JSBufferSubclassStructure.initLater(
|
||||
[](const Initializer<Structure>& init) {
|
||||
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto* globalObject = static_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto* baseStructure = globalObject->typedArrayStructureWithTypedArrayType<JSC::TypeUint8>();
|
||||
JSC::Structure* subclassStructure = JSC::InternalFunction::createSubclassStructure(globalObject, globalObject->JSBufferConstructor(), baseStructure);
|
||||
init.set(subclassStructure);
|
||||
});
|
||||
m_JSResizableOrGrowableSharedBufferSubclassStructure.initLater(
|
||||
[](const Initializer<Structure>& init) {
|
||||
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto* globalObject = static_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto* baseStructure = globalObject->resizableOrGrowableSharedTypedArrayStructureWithTypedArrayType<JSC::TypeUint8>();
|
||||
JSC::Structure* subclassStructure = JSC::InternalFunction::createSubclassStructure(globalObject, globalObject->JSBufferConstructor(), baseStructure);
|
||||
init.set(subclassStructure);
|
||||
@@ -3045,17 +3045,17 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
|
||||
m_ServerRouteListStructure.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
|
||||
init.set(Bun::createServerRouteListStructure(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
|
||||
init.set(Bun::createServerRouteListStructure(init.vm, static_cast<Zig::GlobalObject*>(init.owner)));
|
||||
});
|
||||
|
||||
m_JSBunRequestParamsPrototype.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSObject>::Initializer& init) {
|
||||
init.set(Bun::createJSBunRequestParamsPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
|
||||
init.set(Bun::createJSBunRequestParamsPrototype(init.vm, static_cast<Zig::GlobalObject*>(init.owner)));
|
||||
});
|
||||
|
||||
m_JSBunRequestStructure.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
|
||||
init.set(Bun::createJSBunRequestStructure(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
|
||||
init.set(Bun::createJSBunRequestStructure(init.vm, static_cast<Zig::GlobalObject*>(init.owner)));
|
||||
});
|
||||
|
||||
m_NapiHandleScopeImplStructure.initLater([](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
|
||||
@@ -3078,7 +3078,7 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
|
||||
m_subtleCryptoObject.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
|
||||
auto& global = *reinterpret_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto& global = *static_cast<Zig::GlobalObject*>(init.owner);
|
||||
|
||||
if (!global.m_subtleCrypto) {
|
||||
global.m_subtleCrypto = &WebCore::SubtleCrypto::create(global.scriptExecutionContext()).leakRef();
|
||||
@@ -3124,13 +3124,13 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
|
||||
m_performanceObject.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
|
||||
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto* globalObject = static_cast<Zig::GlobalObject*>(init.owner);
|
||||
init.set(toJS(init.owner, globalObject, globalObject->performance().get()).getObject());
|
||||
});
|
||||
|
||||
m_processEnvObject.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
|
||||
init.set(Bun::createEnvironmentVariablesMap(reinterpret_cast<Zig::GlobalObject*>(init.owner)).getObject());
|
||||
init.set(Bun::createEnvironmentVariablesMap(static_cast<Zig::GlobalObject*>(init.owner)).getObject());
|
||||
});
|
||||
|
||||
m_processObject.initLater(
|
||||
@@ -3289,7 +3289,7 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
|
||||
m_JSCryptoKey.initLater(
|
||||
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
|
||||
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(init.owner);
|
||||
Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(init.owner);
|
||||
auto* prototype = JSCryptoKey::createPrototype(init.vm, *globalObject);
|
||||
auto* structure = JSCryptoKey::createStructure(init.vm, init.owner, JSValue(prototype));
|
||||
init.set(structure);
|
||||
@@ -3547,7 +3547,7 @@ JSC_DEFINE_CUSTOM_GETTER(functionLazyNavigatorGetter,
|
||||
(JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
|
||||
JSC::PropertyName))
|
||||
{
|
||||
return JSC::JSValue::encode(reinterpret_cast<Zig::GlobalObject*>(globalObject)->navigatorObject());
|
||||
return JSC::JSValue::encode(static_cast<Zig::GlobalObject*>(globalObject)->navigatorObject());
|
||||
}
|
||||
|
||||
JSC::GCClient::IsoSubspace* GlobalObject::subspaceForImpl(JSC::VM& vm)
|
||||
@@ -3960,7 +3960,7 @@ void GlobalObject::reload()
|
||||
|
||||
extern "C" void JSC__JSGlobalObject__reload(JSC::JSGlobalObject* arg0)
|
||||
{
|
||||
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0);
|
||||
Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(arg0);
|
||||
globalObject->reload();
|
||||
}
|
||||
|
||||
@@ -3976,7 +3976,7 @@ JSC::Identifier GlobalObject::moduleLoaderResolve(JSGlobalObject* jsGlobalObject
|
||||
JSModuleLoader* loader, JSValue key,
|
||||
JSValue referrer, JSValue origin)
|
||||
{
|
||||
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(jsGlobalObject);
|
||||
Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(jsGlobalObject);
|
||||
|
||||
ErrorableString res;
|
||||
res.success = false;
|
||||
@@ -4032,7 +4032,7 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j
|
||||
JSValue parameters,
|
||||
const SourceOrigin& sourceOrigin)
|
||||
{
|
||||
auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(jsGlobalObject);
|
||||
auto* globalObject = static_cast<Zig::GlobalObject*>(jsGlobalObject);
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSC::Identifier resolvedIdentifier;
|
||||
@@ -4197,7 +4197,7 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb
|
||||
memset(&res.result, 0, sizeof res.result);
|
||||
|
||||
JSValue result = Bun::fetchESMSourceCodeAsync(
|
||||
reinterpret_cast<Zig::GlobalObject*>(globalObject),
|
||||
static_cast<Zig::GlobalObject*>(globalObject),
|
||||
moduleKeyJS,
|
||||
&res,
|
||||
&moduleKeyBun,
|
||||
|
||||
@@ -189,10 +189,10 @@ public:
|
||||
static void reportUncaughtExceptionAtEventLoop(JSGlobalObject*, JSC::Exception*);
|
||||
static JSGlobalObject* deriveShadowRealmGlobalObject(JSGlobalObject* globalObject);
|
||||
static JSC::JSInternalPromise* moduleLoaderImportModule(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSString* moduleNameValue, JSC::JSValue parameters, const JSC::SourceOrigin&);
|
||||
static JSC::Identifier moduleLoaderResolve(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue keyValue, JSC::JSValue referrerValue, JSC::JSValue);
|
||||
static JSC::JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue);
|
||||
static JSC::JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSModuleRecord*, JSC::JSValue);
|
||||
static JSC::JSValue moduleLoaderEvaluate(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue);
|
||||
static JSC::Identifier moduleLoaderResolve(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue key, JSC::JSValue referrer, JSC::JSValue origin);
|
||||
static JSC::JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue key, JSC::JSValue parameters, JSC::JSValue script);
|
||||
static JSC::JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue key, JSC::JSModuleRecord*, JSC::JSValue val);
|
||||
static JSC::JSValue moduleLoaderEvaluate(JSGlobalObject*, JSC::JSModuleLoader*, JSValue key, JSValue moduleRecordValue, JSValue scriptFetcher, JSValue sentValue, JSValue resumeMode);
|
||||
|
||||
static ScriptExecutionStatus scriptExecutionStatus(JSGlobalObject*, JSObject*);
|
||||
static void promiseRejectionTracker(JSGlobalObject*, JSC::JSPromise*, JSC::JSPromiseRejectionOperation);
|
||||
@@ -250,6 +250,14 @@ public:
|
||||
JSC::JSObject* NodeVMScript() const { return m_NodeVMScriptClassStructure.constructorInitializedOnMainThread(this); }
|
||||
JSC::JSValue NodeVMScriptPrototype() const { return m_NodeVMScriptClassStructure.prototypeInitializedOnMainThread(this); }
|
||||
|
||||
JSC::Structure* NodeVMSourceTextModuleStructure() const { return m_NodeVMSourceTextModuleClassStructure.getInitializedOnMainThread(this); }
|
||||
JSC::JSObject* NodeVMSourceTextModule() const { return m_NodeVMSourceTextModuleClassStructure.constructorInitializedOnMainThread(this); }
|
||||
JSC::JSValue NodeVMSourceTextModulePrototype() const { return m_NodeVMSourceTextModuleClassStructure.prototypeInitializedOnMainThread(this); }
|
||||
|
||||
JSC::Structure* NodeVMSyntheticModuleStructure() const { return m_NodeVMSyntheticModuleClassStructure.getInitializedOnMainThread(this); }
|
||||
JSC::JSObject* NodeVMSyntheticModule() const { return m_NodeVMSyntheticModuleClassStructure.constructorInitializedOnMainThread(this); }
|
||||
JSC::JSValue NodeVMSyntheticModulePrototype() const { return m_NodeVMSyntheticModuleClassStructure.prototypeInitializedOnMainThread(this); }
|
||||
|
||||
JSC::JSMap* readableStreamNativeMap() const { return m_lazyReadableStreamPrototypeMap.getInitializedOnMainThread(this); }
|
||||
JSC::JSMap* requireMap() const { return m_requireMap.getInitializedOnMainThread(this); }
|
||||
JSC::JSMap* esmRegistryMap() const { return m_esmRegistryMap.getInitializedOnMainThread(this); }
|
||||
@@ -519,6 +527,8 @@ public:
|
||||
V(private, LazyClassStructure, m_callSiteStructure) \
|
||||
V(public, LazyClassStructure, m_JSBufferClassStructure) \
|
||||
V(public, LazyClassStructure, m_NodeVMScriptClassStructure) \
|
||||
V(public, LazyClassStructure, m_NodeVMSourceTextModuleClassStructure) \
|
||||
V(public, LazyClassStructure, m_NodeVMSyntheticModuleClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSX509CertificateClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSSignClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSVerifyClassStructure) \
|
||||
|
||||
@@ -35,7 +35,10 @@ public:
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNapiExternal;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForRequireResolveFunction;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBundlerPlugin;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMGlobalObject;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMScript;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMSourceTextModule;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMSyntheticModule;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSCommonJSModule;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSCommonJSExtensions;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSMockImplementation;
|
||||
@@ -64,7 +67,6 @@ public:
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFunctionTemplate;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForV8Function;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNodeHTTPServerSocket;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMGlobalObject;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSS3Bucket;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSS3File;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSX509Certificate;
|
||||
|
||||
@@ -35,7 +35,10 @@ public:
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForImportMeta;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForRequireResolveFunction;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForBundlerPlugin;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMGlobalObject;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMScript;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMSourceTextModule;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMSyntheticModule;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSCommonJSModule;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSCommonJSExtensions;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSMockImplementation;
|
||||
@@ -61,7 +64,6 @@ public:
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSMIMEParams;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForV8Function;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSNodeHTTPServerSocket;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMGlobalObject;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSS3Bucket;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSS3File;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSX509Certificate;
|
||||
|
||||
6
src/js/builtins.d.ts
vendored
6
src/js/builtins.d.ts
vendored
@@ -787,6 +787,12 @@ declare function $ERR_HTTP_BODY_NOT_ALLOWED(): Error;
|
||||
declare function $ERR_HTTP_SOCKET_ASSIGNED(): Error;
|
||||
declare function $ERR_DIR_CLOSED(): Error;
|
||||
declare function $ERR_INVALID_MIME_SYNTAX(production: string, str: string, invalidIndex: number | -1): TypeError;
|
||||
declare function $ERR_VM_MODULE_STATUS(reason: string): Error;
|
||||
declare function $ERR_VM_MODULE_ALREADY_LINKED(): Error;
|
||||
declare function $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(): Error;
|
||||
declare function $ERR_VM_MODULE_NOT_MODULE(): Error;
|
||||
declare function $ERR_VM_MODULE_DIFFERENT_CONTEXT(): Error;
|
||||
declare function $ERR_VM_MODULE_LINK_FAILURE(message: string, cause: Error): Error;
|
||||
|
||||
/**
|
||||
* Convert a function to a class-like object.
|
||||
|
||||
@@ -49,6 +49,7 @@ using namespace JSC;
|
||||
macro(assignToStream) \
|
||||
macro(associatedReadableByteStreamController) \
|
||||
macro(atimeMs) \
|
||||
macro(attributes) \
|
||||
macro(autoAllocateChunkSize) \
|
||||
macro(backpressure) \
|
||||
macro(backpressureChangePromise) \
|
||||
@@ -132,6 +133,7 @@ using namespace JSC;
|
||||
macro(headers) \
|
||||
macro(highWaterMark) \
|
||||
macro(host) \
|
||||
macro(hostDefinedImportType) \
|
||||
macro(hostname) \
|
||||
macro(href) \
|
||||
macro(httpOnly) \
|
||||
@@ -234,6 +236,7 @@ using namespace JSC;
|
||||
macro(signal) \
|
||||
macro(sink) \
|
||||
macro(size) \
|
||||
macro(specifier) \
|
||||
macro(start) \
|
||||
macro(startAlgorithm) \
|
||||
macro(startConsumingStream) \
|
||||
|
||||
@@ -94,7 +94,29 @@ const arrayToSafePromiseIterable = (promises, mapFn) =>
|
||||
),
|
||||
);
|
||||
const PromiseAll = Promise.all;
|
||||
const PromiseResolve = Promise.resolve.bind(Promise);
|
||||
const SafePromiseAll = (promises, mapFn) => PromiseAll(arrayToSafePromiseIterable(promises, mapFn));
|
||||
const SafePromiseAllReturnArrayLike = (promises, mapFn) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const { length } = promises;
|
||||
|
||||
const returnVal = Array(length);
|
||||
ObjectSetPrototypeOf(returnVal, null);
|
||||
if (length === 0) resolve(returnVal);
|
||||
|
||||
let pendingPromises = length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const promise = mapFn != null ? mapFn(promises[i], i) : promises[i];
|
||||
PromisePrototypeThen.$call(
|
||||
PromiseResolve(promise),
|
||||
result => {
|
||||
returnVal[i] = result;
|
||||
if (--pendingPromises === 0) resolve(returnVal);
|
||||
},
|
||||
reject,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
Array,
|
||||
@@ -113,6 +135,7 @@ export default {
|
||||
},
|
||||
),
|
||||
SafePromiseAll,
|
||||
SafePromiseAllReturnArrayLike,
|
||||
SafeSet: makeSafe(
|
||||
Set,
|
||||
class SafeSet extends Set {
|
||||
|
||||
@@ -2,6 +2,7 @@ const { hideFromStack } = require("internal/shared");
|
||||
|
||||
const RegExpPrototypeExec = RegExp.prototype.exec;
|
||||
const ArrayIsArray = Array.isArray;
|
||||
const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
|
||||
/**
|
||||
@@ -63,7 +64,13 @@ function validateLinkHeaderValue(hints) {
|
||||
`must be an array or string of format "</styles.css>; rel=preload; as=style"`,
|
||||
);
|
||||
}
|
||||
hideFromStack(validateLinkHeaderValue);
|
||||
|
||||
function validateInternalField(object, fieldKey, className) {
|
||||
if (typeof object !== "object" || object === null || !ObjectPrototypeHasOwnProperty.$call(object, fieldKey)) {
|
||||
throw $ERR_INVALID_ARG_TYPE("this", className, object);
|
||||
}
|
||||
}
|
||||
hideFromStack(validateLinkHeaderValue, validateInternalField);
|
||||
|
||||
export default {
|
||||
/** (value, name) */
|
||||
@@ -107,4 +114,6 @@ export default {
|
||||
/** `(value, name, oneOf)` */
|
||||
validateOneOf: $newCppFunction("NodeValidator.cpp", "jsFunction_validateOneOf", 0),
|
||||
isUint8Array: value => value instanceof Uint8Array,
|
||||
/** `(object, fieldKey, className)` */
|
||||
validateInternalField,
|
||||
};
|
||||
|
||||
@@ -1,14 +1,75 @@
|
||||
// Hardcoded module "node:vm"
|
||||
const { SafePromiseAllReturnArrayLike } = require("internal/primordials");
|
||||
const { throwNotImplemented } = require("internal/shared");
|
||||
const {
|
||||
validateObject,
|
||||
validateString,
|
||||
validateUint32,
|
||||
validateBoolean,
|
||||
validateInt32,
|
||||
validateBuffer,
|
||||
validateFunction,
|
||||
} = require("internal/validators");
|
||||
const util = require("node:util");
|
||||
|
||||
const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding");
|
||||
|
||||
const ObjectFreeze = Object.freeze;
|
||||
const ObjectDefineProperty = Object.defineProperty;
|
||||
const ArrayPrototypeMap = Array.prototype.map;
|
||||
const PromisePrototypeThen = Promise.prototype.then;
|
||||
const PromiseResolve = Promise.resolve.bind(Promise);
|
||||
const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||
const ObjectSetPrototypeOf = Object.setPrototypeOf;
|
||||
const ObjectGetPrototypeOf = Object.getPrototypeOf;
|
||||
const SymbolToStringTag = Symbol.toStringTag;
|
||||
|
||||
const { createContext, isContext, Script, runInNewContext, runInThisContext, compileFunction } = vm;
|
||||
const kPerContextModuleId = Symbol("kPerContextModuleId");
|
||||
const kNative = Symbol("kNative");
|
||||
const kContext = Symbol("kContext");
|
||||
const kLink = Symbol("kLink");
|
||||
const kDependencySpecifiers = Symbol("kDependencySpecifiers");
|
||||
const kNoError = Symbol("kNoError");
|
||||
|
||||
const kEmptyObject = Object.freeze(Object.create(null));
|
||||
|
||||
const {
|
||||
Script,
|
||||
Module: ModuleNative,
|
||||
createContext,
|
||||
isContext,
|
||||
// runInNewContext: moduleRunInNewContext,
|
||||
// runInThisContext: moduleRunInThisContext,
|
||||
compileFunction,
|
||||
isModuleNamespaceObject,
|
||||
kUnlinked,
|
||||
kLinked,
|
||||
kEvaluated,
|
||||
kErrored,
|
||||
} = vm;
|
||||
|
||||
function runInContext(code, context, options) {
|
||||
return new Script(code, options).runInContext(context);
|
||||
validateContext(context);
|
||||
if (typeof options === "string") {
|
||||
options = { filename: options };
|
||||
}
|
||||
return new Script(code, options).runInContext(context, options);
|
||||
}
|
||||
|
||||
function runInThisContext(code, options) {
|
||||
if (typeof options === "string") {
|
||||
options = { filename: options };
|
||||
}
|
||||
return new Script(code, options).runInThisContext(options);
|
||||
}
|
||||
|
||||
function runInNewContext(code, contextObject, options) {
|
||||
if (typeof options === "string") {
|
||||
options = { filename: options };
|
||||
}
|
||||
contextObject = createContext(contextObject, options);
|
||||
return createScript(code, options).runInNewContext(contextObject, options);
|
||||
}
|
||||
|
||||
function createScript(code, options) {
|
||||
@@ -19,15 +80,315 @@ function measureMemory() {
|
||||
throwNotImplemented("node:vm measureMemory");
|
||||
}
|
||||
|
||||
class Module {
|
||||
constructor() {
|
||||
throwNotImplemented("node:vm.Module");
|
||||
function validateContext(contextifiedObject) {
|
||||
if (!isContext(contextifiedObject)) {
|
||||
const error = new Error('The "contextifiedObject" argument must be an vm.Context');
|
||||
error.code = "ERR_INVALID_ARG_TYPE";
|
||||
error.name = "TypeError";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceTextModule {
|
||||
constructor() {
|
||||
throwNotImplemented("node:vm.SourceTextModule");
|
||||
function validateModule(module, typename = "Module") {
|
||||
if (!isModule(module)) {
|
||||
const error = new Error('The "this" argument must be an instance of ' + typename);
|
||||
error.code = "ERR_INVALID_ARG_TYPE";
|
||||
error.name = "TypeError";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let globalModuleId = 0;
|
||||
const defaultModuleName = "vm:module";
|
||||
|
||||
class Module {
|
||||
constructor(options) {
|
||||
if (new.target === Module) {
|
||||
throw new TypeError("Module is not a constructor");
|
||||
}
|
||||
|
||||
const { context, sourceText, syntheticExportNames, syntheticEvaluationSteps } = options;
|
||||
|
||||
if (context !== undefined) {
|
||||
validateObject(context, "context");
|
||||
if (!isContext(context)) {
|
||||
throw $ERR_INVALID_ARG_TYPE("options.context", "vm.Context", context);
|
||||
}
|
||||
}
|
||||
|
||||
let { identifier } = options;
|
||||
if (identifier !== undefined) {
|
||||
validateString(identifier, "options.identifier");
|
||||
} else if (context === undefined) {
|
||||
identifier = `${defaultModuleName}(${globalModuleId++})`;
|
||||
} else if (context[kPerContextModuleId] !== undefined) {
|
||||
const curId = context[kPerContextModuleId];
|
||||
identifier = `${defaultModuleName}(${curId})`;
|
||||
context[kPerContextModuleId] += 1;
|
||||
} else {
|
||||
identifier = `${defaultModuleName}(0)`;
|
||||
ObjectDefineProperty(context, kPerContextModuleId, {
|
||||
__proto__: null,
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
let registry = { __proto__: null };
|
||||
if (sourceText !== undefined) {
|
||||
this[kNative] = new ModuleNative(
|
||||
identifier,
|
||||
context,
|
||||
sourceText,
|
||||
options.lineOffset,
|
||||
options.columnOffset,
|
||||
options.cachedData,
|
||||
);
|
||||
registry = {
|
||||
__proto__: null,
|
||||
initializeImportMeta: options.initializeImportMeta,
|
||||
importModuleDynamically: options.importModuleDynamically
|
||||
? importModuleDynamicallyWrap(options.importModuleDynamically)
|
||||
: undefined,
|
||||
};
|
||||
// This will take precedence over the referrer as the object being
|
||||
// passed into the callbacks.
|
||||
registry.callbackReferrer = this;
|
||||
// const { registerModule } = require("internal/modules/esm/utils");
|
||||
// registerModule(this[kNative], registry);
|
||||
} else {
|
||||
$assert(syntheticEvaluationSteps);
|
||||
this[kNative] = new ModuleNative(identifier, context, syntheticExportNames, syntheticEvaluationSteps);
|
||||
}
|
||||
|
||||
this[kContext] = context;
|
||||
}
|
||||
|
||||
get identifier() {
|
||||
validateModule(this);
|
||||
return this[kNative].identifier;
|
||||
}
|
||||
|
||||
get context() {
|
||||
validateModule(this);
|
||||
return this[kContext];
|
||||
}
|
||||
|
||||
get status() {
|
||||
validateModule(this);
|
||||
return this[kNative].getStatus();
|
||||
}
|
||||
|
||||
get namespace() {
|
||||
validateModule(this);
|
||||
if (this[kNative].getStatusCode() < kLinked) {
|
||||
throw $ERR_VM_MODULE_STATUS("must not be unlinked or linking");
|
||||
}
|
||||
|
||||
return this[kNative].getNamespace();
|
||||
}
|
||||
|
||||
get error() {
|
||||
validateModule(this);
|
||||
if (this[kNative].getStatusCode() !== kErrored) {
|
||||
throw $ERR_VM_MODULE_STATUS("must be errored");
|
||||
}
|
||||
|
||||
return this[kNative].getError();
|
||||
}
|
||||
|
||||
async link(linker) {
|
||||
validateModule(this);
|
||||
validateFunction(linker, "linker");
|
||||
|
||||
if (this[kNative].getStatusCode() === kLinked) {
|
||||
throw $ERR_VM_MODULE_ALREADY_LINKED();
|
||||
}
|
||||
|
||||
if (this[kNative].getStatusCode() !== kUnlinked) {
|
||||
throw $ERR_VM_MODULE_STATUS("must be unlinked");
|
||||
}
|
||||
|
||||
await this[kLink](linker);
|
||||
this[kNative].instantiate();
|
||||
}
|
||||
|
||||
async evaluate(options = kEmptyObject) {
|
||||
validateModule(this);
|
||||
validateObject(options, "options");
|
||||
|
||||
let timeout = options.timeout;
|
||||
if (timeout === undefined) {
|
||||
timeout = -1;
|
||||
} else {
|
||||
validateUint32(timeout, "options.timeout", true);
|
||||
}
|
||||
const { breakOnSigint = false } = options;
|
||||
validateBoolean(breakOnSigint, "options.breakOnSigint");
|
||||
const status = this[kNative].getStatusCode();
|
||||
if (status !== kLinked && status !== kEvaluated && status !== kErrored) {
|
||||
throw $ERR_VM_MODULE_STATUS("must be one of linked, evaluated, or errored");
|
||||
}
|
||||
await this[kNative].evaluate(timeout, breakOnSigint);
|
||||
}
|
||||
|
||||
[util.inspect.custom](depth, options) {
|
||||
validateModule(this);
|
||||
if (typeof depth === "number" && depth < 0) return this;
|
||||
|
||||
const constructor = getConstructorOf(this) || Module;
|
||||
const o = { __proto__: { constructor } };
|
||||
o.status = this.status;
|
||||
o.identifier = this.identifier;
|
||||
o.context = this.context;
|
||||
|
||||
ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this));
|
||||
ObjectDefineProperty(o, SymbolToStringTag, {
|
||||
__proto__: null,
|
||||
value: constructor.name,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
return util.inspect(o, { ...options, customInspect: false });
|
||||
}
|
||||
}
|
||||
|
||||
class SourceTextModule extends Module {
|
||||
#error: any = kNoError;
|
||||
#statusOverride: any;
|
||||
|
||||
constructor(sourceText, options = kEmptyObject) {
|
||||
validateString(sourceText, "sourceText");
|
||||
validateObject(options, "options");
|
||||
|
||||
const {
|
||||
lineOffset = 0,
|
||||
columnOffset = 0,
|
||||
initializeImportMeta,
|
||||
importModuleDynamically,
|
||||
context,
|
||||
identifier,
|
||||
cachedData,
|
||||
} = options;
|
||||
|
||||
validateInt32(lineOffset, "options.lineOffset");
|
||||
validateInt32(columnOffset, "options.columnOffset");
|
||||
|
||||
if (initializeImportMeta !== undefined) {
|
||||
validateFunction(initializeImportMeta, "options.initializeImportMeta");
|
||||
}
|
||||
|
||||
if (importModuleDynamically !== undefined) {
|
||||
validateFunction(importModuleDynamically, "options.importModuleDynamically");
|
||||
}
|
||||
|
||||
if (cachedData !== undefined) {
|
||||
validateBuffer(cachedData, "options.cachedData");
|
||||
}
|
||||
|
||||
super({
|
||||
sourceText,
|
||||
context,
|
||||
identifier,
|
||||
lineOffset,
|
||||
columnOffset,
|
||||
cachedData,
|
||||
initializeImportMeta,
|
||||
importModuleDynamically,
|
||||
});
|
||||
|
||||
this[kDependencySpecifiers] = undefined;
|
||||
}
|
||||
|
||||
async [kLink](linker) {
|
||||
validateModule(this, "SourceTextModule");
|
||||
|
||||
if (this[kNative].getStatusCode() >= kLinked) {
|
||||
throw $ERR_VM_MODULE_ALREADY_LINKED();
|
||||
}
|
||||
|
||||
this.#statusOverride = "linking";
|
||||
const moduleRequests = this[kNative].createModuleRecord();
|
||||
|
||||
// Iterates the module requests and links with the linker.
|
||||
// Specifiers should be aligned with the moduleRequests array in order.
|
||||
const specifiers = Array(moduleRequests.length);
|
||||
const modulePromises = Array(moduleRequests.length);
|
||||
// Iterates with index to avoid calling into userspace with `Symbol.iterator`.
|
||||
for (let idx = 0; idx < moduleRequests.length; idx++) {
|
||||
const { specifier, attributes } = moduleRequests[idx];
|
||||
|
||||
const linkerResult = linker(specifier, this, {
|
||||
attributes,
|
||||
assert: attributes,
|
||||
});
|
||||
|
||||
const modulePromise = PromisePrototypeThen.$call(PromiseResolve(linkerResult), async mod => {
|
||||
if (!isModule(mod)) {
|
||||
throw $ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
if (mod.context !== this.context) {
|
||||
throw $ERR_VM_MODULE_DIFFERENT_CONTEXT();
|
||||
}
|
||||
if (mod.status === "errored") {
|
||||
throw $ERR_VM_MODULE_LINK_FAILURE(`request for '${specifier}' resolved to an errored mod`, mod.error);
|
||||
}
|
||||
if (mod.status === "unlinked") {
|
||||
await mod[kLink](linker);
|
||||
}
|
||||
return mod[kNative];
|
||||
});
|
||||
modulePromises[idx] = modulePromise;
|
||||
specifiers[idx] = specifier;
|
||||
}
|
||||
|
||||
try {
|
||||
const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises);
|
||||
this[kNative].link(specifiers, moduleNatives, 0);
|
||||
} catch (e) {
|
||||
this.#error = e;
|
||||
throw e;
|
||||
} finally {
|
||||
this.#statusOverride = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get dependencySpecifiers() {
|
||||
validateModule(this, "SourceTextModule");
|
||||
this[kDependencySpecifiers] ??= ObjectFreeze(
|
||||
ArrayPrototypeMap.$call(this[kNative].getModuleRequests(), request => request[0]),
|
||||
);
|
||||
return this[kDependencySpecifiers];
|
||||
}
|
||||
|
||||
get status() {
|
||||
validateModule(this, "SourceTextModule");
|
||||
if (this.#error !== kNoError) {
|
||||
return "errored";
|
||||
}
|
||||
if (this.#statusOverride) {
|
||||
return this.#statusOverride;
|
||||
}
|
||||
return super.status;
|
||||
}
|
||||
|
||||
get error() {
|
||||
validateModule(this, "SourceTextModule");
|
||||
if (this.#error !== kNoError) {
|
||||
return this.#error;
|
||||
}
|
||||
return super.error;
|
||||
}
|
||||
|
||||
createCachedData() {
|
||||
validateModule(this, "SourceTextModule");
|
||||
const { status } = this;
|
||||
if (status === "evaluating" || status === "evaluated" || status === "errored") {
|
||||
throw $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA();
|
||||
}
|
||||
return this[kNative].createCachedData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +404,38 @@ const constants = {
|
||||
DONT_CONTEXTIFY: Symbol("vm_context_no_contextify"),
|
||||
};
|
||||
|
||||
function isModule(object) {
|
||||
return typeof object === "object" && object !== null && ObjectPrototypeHasOwnProperty.$call(object, kNative);
|
||||
}
|
||||
|
||||
function importModuleDynamicallyWrap(importModuleDynamically) {
|
||||
const importModuleDynamicallyWrapper = async (...args) => {
|
||||
const m: any = importModuleDynamically.$apply(this, args);
|
||||
if (isModuleNamespaceObject(m)) {
|
||||
return m;
|
||||
}
|
||||
if (!isModule(m)) {
|
||||
throw $ERR_VM_MODULE_NOT_MODULE();
|
||||
}
|
||||
if (m.status === "errored") {
|
||||
throw m.error;
|
||||
}
|
||||
return m.namespace;
|
||||
};
|
||||
return importModuleDynamicallyWrapper;
|
||||
}
|
||||
|
||||
function getConstructorOf(obj) {
|
||||
while (obj) {
|
||||
const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor");
|
||||
if (descriptor !== undefined && typeof descriptor.value === "function" && descriptor.value.name !== "") {
|
||||
return descriptor.value;
|
||||
}
|
||||
|
||||
obj = ObjectGetPrototypeOf(obj);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createContext,
|
||||
runInContext,
|
||||
|
||||
51
src/vm/Semaphore.cpp
Normal file
51
src/vm/Semaphore.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "Semaphore.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
Semaphore::Semaphore(unsigned int value)
|
||||
{
|
||||
#if OS(WINDOWS)
|
||||
uv_sem_init(&m_semaphore, value);
|
||||
#elif OS(DARWIN)
|
||||
semaphore_create(mach_task_self(), &m_semaphore, SYNC_POLICY_FIFO, value);
|
||||
#else
|
||||
sem_init(&m_semaphore, 0, value);
|
||||
#endif
|
||||
}
|
||||
|
||||
Semaphore::~Semaphore()
|
||||
{
|
||||
#if OS(WINDOWS)
|
||||
uv_sem_destroy(&m_semaphore);
|
||||
#elif OS(DARWIN)
|
||||
semaphore_destroy(mach_task_self(), m_semaphore);
|
||||
#else
|
||||
sem_destroy(&m_semaphore);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Semaphore::signal()
|
||||
{
|
||||
#if OS(WINDOWS)
|
||||
uv_sem_post(&m_semaphore);
|
||||
return true;
|
||||
#elif OS(DARWIN)
|
||||
return semaphore_signal(m_semaphore) == KERN_SUCCESS;
|
||||
#else
|
||||
return sem_post(&m_semaphore) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Semaphore::wait()
|
||||
{
|
||||
#if OS(WINDOWS)
|
||||
uv_sem_wait(&m_semaphore);
|
||||
return true;
|
||||
#elif OS(DARWIN)
|
||||
return semaphore_wait(m_semaphore) == KERN_SUCCESS;
|
||||
#else
|
||||
return sem_wait(&m_semaphore) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
34
src/vm/Semaphore.h
Normal file
34
src/vm/Semaphore.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
|
||||
#if OS(WINDOWS)
|
||||
#include <uv.h>
|
||||
#elif OS(DARWIN)
|
||||
#include <mach/task.h>
|
||||
#include <mach/semaphore.h>
|
||||
#else
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class Semaphore {
|
||||
public:
|
||||
Semaphore(unsigned int value);
|
||||
~Semaphore();
|
||||
|
||||
bool signal();
|
||||
bool wait();
|
||||
|
||||
private:
|
||||
#if OS(WINDOWS)
|
||||
uv_sem_t m_semaphore;
|
||||
#elif OS(DARWIN)
|
||||
semaphore_t m_semaphore;
|
||||
#else
|
||||
sem_t m_semaphore;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
23
src/vm/SigintReceiver.h
Normal file
23
src/vm/SigintReceiver.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class SigintReceiver {
|
||||
public:
|
||||
SigintReceiver() = default;
|
||||
|
||||
void setSigintReceived(bool value = true)
|
||||
{
|
||||
m_sigintReceived = value;
|
||||
}
|
||||
|
||||
bool getSigintReceived()
|
||||
{
|
||||
return m_sigintReceived;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_sigintReceived = false;
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
208
src/vm/SigintWatcher.cpp
Normal file
208
src/vm/SigintWatcher.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include "NodeVM.h"
|
||||
#include "SigintWatcher.h"
|
||||
|
||||
#if OS(WINDOWS)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
extern "C" void Bun__onPosixSignal(int signalNumber);
|
||||
extern "C" void Bun__ensureSignalHandler();
|
||||
|
||||
namespace Bun {
|
||||
|
||||
#if OS(WINDOWS)
|
||||
static BOOL WindowsCtrlHandler(DWORD signal)
|
||||
{
|
||||
if (signal == CTRL_C_EVENT) {
|
||||
SigintWatcher::get().signalReceived();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
SigintWatcher::SigintWatcher()
|
||||
: m_semaphore(1)
|
||||
{
|
||||
m_globalObjects.reserveInitialCapacity(16);
|
||||
}
|
||||
|
||||
SigintWatcher::~SigintWatcher()
|
||||
{
|
||||
uninstall();
|
||||
}
|
||||
|
||||
void SigintWatcher::install()
|
||||
{
|
||||
#if OS(WINDOWS)
|
||||
SetConsoleCtrlHandler(WindowsCtrlHandler, true);
|
||||
#else
|
||||
Bun__ensureSignalHandler();
|
||||
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(struct sigaction));
|
||||
|
||||
action.sa_handler = [](int signalNumber) {
|
||||
get().signalReceived();
|
||||
};
|
||||
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaddset(&action.sa_mask, SIGINT);
|
||||
action.sa_flags = 0;
|
||||
|
||||
sigaction(SIGINT, &action, nullptr);
|
||||
#endif
|
||||
|
||||
if (m_installed.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_thread = WTF::Thread::create("SigintWatcher"_s, [this] {
|
||||
while (m_installed.load()) {
|
||||
bool success = m_semaphore.wait();
|
||||
if (!m_installed) {
|
||||
return;
|
||||
}
|
||||
ASSERT(success);
|
||||
if (m_waiting.test_and_set()) {
|
||||
m_waiting.clear();
|
||||
#if !OS(WINDOWS)
|
||||
if (!signalAll()) {
|
||||
Bun__onPosixSignal(SIGINT);
|
||||
}
|
||||
#else
|
||||
signalAll();
|
||||
#endif
|
||||
} else {
|
||||
m_waiting.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SigintWatcher::uninstall()
|
||||
{
|
||||
if (m_installed.exchange(false)) {
|
||||
WTF::Thread* currentThread = WTF::Thread::currentMayBeNull();
|
||||
ASSERT(!currentThread || m_thread->uid() != currentThread->uid());
|
||||
|
||||
#if OS(WINDOWS)
|
||||
SetConsoleCtrlHandler(WindowsCtrlHandler, false);
|
||||
#else
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(struct sigaction));
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaddset(&action.sa_mask, SIGINT);
|
||||
action.sa_flags = SA_RESTART;
|
||||
sigaction(SIGINT, &action, nullptr);
|
||||
#endif
|
||||
|
||||
m_semaphore.signal();
|
||||
m_thread->waitForCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
void SigintWatcher::signalReceived()
|
||||
{
|
||||
if (!m_waiting.test_and_set()) {
|
||||
bool success = m_semaphore.signal();
|
||||
ASSERT(success);
|
||||
}
|
||||
}
|
||||
|
||||
void SigintWatcher::registerGlobalObject(JSGlobalObject* globalObject)
|
||||
{
|
||||
if (globalObject == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
WTF::Locker lock(m_globalObjectsMutex);
|
||||
m_globalObjects.appendIfNotContains(globalObject);
|
||||
}
|
||||
|
||||
void SigintWatcher::unregisterGlobalObject(JSGlobalObject* globalObject)
|
||||
{
|
||||
if (globalObject == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
WTF::Locker lock(m_globalObjectsMutex);
|
||||
|
||||
auto iter = std::find(m_globalObjects.begin(), m_globalObjects.end(), globalObject);
|
||||
if (iter == m_globalObjects.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::swap(*iter, m_globalObjects.last());
|
||||
m_globalObjects.removeLast();
|
||||
}
|
||||
|
||||
void SigintWatcher::registerReceiver(SigintReceiver* module)
|
||||
{
|
||||
if (module == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
WTF::Locker lock(m_receiversMutex);
|
||||
m_receivers.appendIfNotContains(module);
|
||||
}
|
||||
|
||||
void SigintWatcher::unregisterReceiver(SigintReceiver* module)
|
||||
{
|
||||
WTF::Locker lock(m_receiversMutex);
|
||||
|
||||
auto iter = std::find(m_receivers.begin(), m_receivers.end(), module);
|
||||
if (iter == m_receivers.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::swap(*iter, m_receivers.last());
|
||||
m_receivers.removeLast();
|
||||
}
|
||||
|
||||
void SigintWatcher::ref()
|
||||
{
|
||||
if (m_refCount++ == 0) {
|
||||
install();
|
||||
}
|
||||
}
|
||||
|
||||
void SigintWatcher::deref()
|
||||
{
|
||||
ASSERT(m_refCount > 0);
|
||||
if (--m_refCount == 0) {
|
||||
uninstall();
|
||||
}
|
||||
}
|
||||
|
||||
SigintWatcher& SigintWatcher::get()
|
||||
{
|
||||
static SigintWatcher instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool SigintWatcher::signalAll()
|
||||
{
|
||||
{
|
||||
WTF::Locker lock(m_receiversMutex);
|
||||
for (auto* receiver : m_receivers) {
|
||||
receiver->setSigintReceived();
|
||||
}
|
||||
}
|
||||
|
||||
WTF::Locker lock(m_globalObjectsMutex);
|
||||
|
||||
if (m_globalObjects.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (JSGlobalObject* globalObject : m_globalObjects) {
|
||||
globalObject->vm().notifyNeedTermination();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
110
src/vm/SigintWatcher.h
Normal file
110
src/vm/SigintWatcher.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
|
||||
#include "Semaphore.h"
|
||||
#include "SigintReceiver.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
template<typename T>
|
||||
concept SigintHoldable = std::derived_from<T, JSC::JSGlobalObject> || std::derived_from<T, SigintReceiver>;
|
||||
|
||||
class SigintWatcher {
|
||||
public:
|
||||
SigintWatcher();
|
||||
~SigintWatcher();
|
||||
|
||||
void install();
|
||||
void uninstall();
|
||||
void signalReceived();
|
||||
void registerGlobalObject(JSC::JSGlobalObject* globalObject);
|
||||
void unregisterGlobalObject(JSC::JSGlobalObject* globalObject);
|
||||
void registerReceiver(SigintReceiver* module);
|
||||
void unregisterReceiver(SigintReceiver* module);
|
||||
/** Installs the signal handler if it's not already installed and increments the ref count. */
|
||||
void ref();
|
||||
/** Decrements the ref count and uninstalls the signal handler if the ref count reaches 0. */
|
||||
void deref();
|
||||
|
||||
static SigintWatcher& get();
|
||||
|
||||
class GlobalObjectHolder {
|
||||
public:
|
||||
template<typename... Ts>
|
||||
ALWAYS_INLINE GlobalObjectHolder(Ts*... held)
|
||||
{
|
||||
(assign(held), ...);
|
||||
}
|
||||
|
||||
~GlobalObjectHolder()
|
||||
{
|
||||
for (auto* receiver : m_receivers) {
|
||||
get().unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
if (m_globalObject) {
|
||||
get().unregisterGlobalObject(m_globalObject);
|
||||
get().deref();
|
||||
}
|
||||
}
|
||||
|
||||
GlobalObjectHolder(const GlobalObjectHolder&) = delete;
|
||||
GlobalObjectHolder(GlobalObjectHolder&& other)
|
||||
: m_globalObject(std::exchange(other.m_globalObject, nullptr))
|
||||
, m_receivers(WTFMove(other.m_receivers))
|
||||
{
|
||||
}
|
||||
|
||||
GlobalObjectHolder& operator=(const GlobalObjectHolder&) = delete;
|
||||
GlobalObjectHolder& operator=(GlobalObjectHolder&& other)
|
||||
{
|
||||
m_globalObject = std::exchange(other.m_globalObject, nullptr);
|
||||
m_receivers = WTFMove(other.m_receivers);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ALWAYS_INLINE assign(SigintHoldable auto* ptr)
|
||||
{
|
||||
using T = std::remove_pointer_t<decltype(ptr)>;
|
||||
if constexpr (std::derived_from<T, JSC::JSGlobalObject>) {
|
||||
if ((m_globalObject = ptr)) {
|
||||
get().ref();
|
||||
get().registerGlobalObject(m_globalObject);
|
||||
}
|
||||
} else if constexpr (std::derived_from<T, SigintReceiver>) {
|
||||
m_receivers.append(ptr);
|
||||
get().registerReceiver(ptr);
|
||||
} else {
|
||||
static_assert(false, "Invalid held type");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
JSC::JSGlobalObject* m_globalObject = nullptr;
|
||||
WTF::Vector<SigintReceiver*, 4> m_receivers;
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
ALWAYS_INLINE static GlobalObjectHolder hold(Ts*... held)
|
||||
{
|
||||
return { held... };
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<WTF::Thread> m_thread;
|
||||
std::atomic_bool m_installed = false;
|
||||
std::atomic_flag m_waiting {};
|
||||
Semaphore m_semaphore;
|
||||
WTF::Lock m_globalObjectsMutex;
|
||||
WTF::Lock m_receiversMutex;
|
||||
WTF::Vector<JSC::JSGlobalObject*> m_globalObjects;
|
||||
WTF::Vector<SigintReceiver*> m_receivers;
|
||||
uint32_t m_refCount = 0;
|
||||
|
||||
bool signalAll();
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
350
test/js/node/test/parallel/test-vm-basic.js
Normal file
350
test/js/node/test/parallel/test-vm-basic.js
Normal file
@@ -0,0 +1,350 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
// vm.runInNewContext
|
||||
{
|
||||
const sandbox = {};
|
||||
const result = vm.runInNewContext(
|
||||
'foo = "bar"; this.typeofProcess = typeof process; typeof Object;',
|
||||
sandbox
|
||||
);
|
||||
assert.deepStrictEqual(sandbox, {
|
||||
foo: 'bar',
|
||||
typeofProcess: 'undefined',
|
||||
});
|
||||
assert.strictEqual(result, 'function');
|
||||
}
|
||||
|
||||
// vm.runInContext
|
||||
{
|
||||
const sandbox = { foo: 'bar' };
|
||||
const context = vm.createContext(sandbox);
|
||||
const result = vm.runInContext(
|
||||
'baz = foo; this.typeofProcess = typeof process; typeof Object;',
|
||||
context
|
||||
);
|
||||
assert.deepStrictEqual(sandbox, {
|
||||
foo: 'bar',
|
||||
baz: 'bar',
|
||||
typeofProcess: 'undefined'
|
||||
});
|
||||
assert.strictEqual(result, 'function');
|
||||
}
|
||||
|
||||
// vm.runInThisContext
|
||||
{
|
||||
const result = vm.runInThisContext(
|
||||
'vmResult = "foo"; Object.prototype.toString.call(process);'
|
||||
);
|
||||
assert.strictEqual(global.vmResult, 'foo');
|
||||
assert.strictEqual(result, '[object process]');
|
||||
delete global.vmResult;
|
||||
}
|
||||
|
||||
// vm.runInNewContext
|
||||
{
|
||||
const result = vm.runInNewContext(
|
||||
'vmResult = "foo"; typeof process;'
|
||||
);
|
||||
assert.strictEqual(global.vmResult, undefined);
|
||||
assert.strictEqual(result, 'undefined');
|
||||
}
|
||||
|
||||
// vm.createContext
|
||||
{
|
||||
const sandbox = {};
|
||||
const context = vm.createContext(sandbox);
|
||||
assert.strictEqual(sandbox, context);
|
||||
}
|
||||
|
||||
// Run script with filename
|
||||
{
|
||||
const script = 'throw new Error("boom")';
|
||||
const filename = 'test-boom-error';
|
||||
const context = vm.createContext();
|
||||
|
||||
function checkErr(err) {
|
||||
return err.stack.startsWith('test-boom-error:1');
|
||||
}
|
||||
|
||||
assert.throws(() => vm.runInContext(script, context, filename), checkErr);
|
||||
assert.throws(() => vm.runInNewContext(script, context, filename), checkErr);
|
||||
assert.throws(() => vm.runInThisContext(script, filename), checkErr);
|
||||
}
|
||||
|
||||
// Invalid arguments
|
||||
[null, 'string'].forEach((input) => {
|
||||
assert.throws(() => {
|
||||
vm.createContext({}, input);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "options" argument must be of type object.' +
|
||||
common.invalidArgTypeHelper(input)
|
||||
});
|
||||
});
|
||||
|
||||
['name', 'origin'].forEach((propertyName) => {
|
||||
assert.throws(() => {
|
||||
vm.createContext({}, { [propertyName]: null });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: `The "options.${propertyName}" property must be of type string. ` +
|
||||
'Received null'
|
||||
});
|
||||
});
|
||||
|
||||
['contextName', 'contextOrigin'].forEach((propertyName) => {
|
||||
assert.throws(() => {
|
||||
vm.runInNewContext('', {}, { [propertyName]: null });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: `The "options.${propertyName}" property must be of type string. ` +
|
||||
'Received null'
|
||||
});
|
||||
});
|
||||
|
||||
// vm.compileFunction
|
||||
{
|
||||
assert.strictEqual(
|
||||
vm.compileFunction('console.log("Hello, World!")').toString(),
|
||||
'function () {\nconsole.log("Hello, World!")\n}'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
vm.compileFunction(
|
||||
'return p + q + r + s + t',
|
||||
['p', 'q', 'r', 's', 't']
|
||||
)('ab', 'cd', 'ef', 'gh', 'ij'),
|
||||
'abcdefghij'
|
||||
);
|
||||
|
||||
vm.compileFunction('return'); // Should not throw on 'return'
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction(
|
||||
'});\n\n(function() {\nconsole.log(1);\n})();\n\n(function() {'
|
||||
);
|
||||
}, {
|
||||
name: 'SyntaxError',
|
||||
message: "Unexpected token '}'"
|
||||
});
|
||||
|
||||
// Tests for failed argument validation
|
||||
assert.throws(() => vm.compileFunction(), {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "code" argument must be of type string. ' +
|
||||
'Received undefined'
|
||||
});
|
||||
|
||||
vm.compileFunction(''); // Should pass without params or options
|
||||
|
||||
assert.throws(() => vm.compileFunction('', null), {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "params" argument must be an instance of Array. ' +
|
||||
'Received null'
|
||||
});
|
||||
|
||||
// vm.compileFunction('', undefined, null);
|
||||
|
||||
const optionTypes = {
|
||||
'filename': 'string',
|
||||
'columnOffset': 'number',
|
||||
'lineOffset': 'number',
|
||||
'cachedData': 'Buffer, TypedArray, or DataView',
|
||||
'produceCachedData': 'boolean',
|
||||
};
|
||||
|
||||
for (const option in optionTypes) {
|
||||
const typeErrorMessage = `The "options.${option}" property must be ` +
|
||||
(option === 'cachedData' ? 'an instance of' : 'of type');
|
||||
assert.throws(() => {
|
||||
vm.compileFunction('', undefined, { [option]: null });
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: typeErrorMessage +
|
||||
` ${optionTypes[option]}. Received null`
|
||||
});
|
||||
}
|
||||
|
||||
// Testing for context-based failures
|
||||
[Boolean(), Number(), null, String(), Symbol(), {}].forEach(
|
||||
(value) => {
|
||||
assert.throws(() => {
|
||||
vm.compileFunction('', undefined, { parsingContext: value });
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "options.parsingContext" property must be an instance ' +
|
||||
`of Context.${common.invalidArgTypeHelper(value)}`
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Testing for non Array type-based failures
|
||||
[Boolean(), Number(), null, Object(), Symbol(), {}].forEach(
|
||||
(value) => {
|
||||
assert.throws(() => {
|
||||
vm.compileFunction('', value);
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "params" argument must be an instance of Array.' +
|
||||
common.invalidArgTypeHelper(value)
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
vm.compileFunction(
|
||||
'return a;',
|
||||
undefined,
|
||||
{ contextExtensions: [{ a: 5 }] }
|
||||
)(),
|
||||
5
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction('', undefined, { contextExtensions: null });
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "options.contextExtensions" property must be an instance of' +
|
||||
' Array. Received null'
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction('', undefined, { contextExtensions: [0] });
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "options.contextExtensions[0]" property must be of type ' +
|
||||
'object. Received type number (0)'
|
||||
});
|
||||
|
||||
const oldLimit = Error.stackTraceLimit;
|
||||
// Setting value to run the last three tests
|
||||
Error.stackTraceLimit = 1;
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction('throw new Error("Sample Error")')();
|
||||
}, {
|
||||
message: 'Sample Error',
|
||||
stack: 'Error: Sample Error\n at <anonymous> (file:///:2:16)' // from <anonymous>:1:7 to match Bun's stack trace formatting
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction(
|
||||
'throw new Error("Sample Error")',
|
||||
[],
|
||||
{ lineOffset: 3 }
|
||||
)();
|
||||
}, {
|
||||
message: 'Sample Error',
|
||||
stack: 'Error: Sample Error\n at <anonymous> (evalmachine.<anonymous>:5:16)' // modified from <anonymous>:4:7 to match Bun's stack trace formatting
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction(
|
||||
'throw new Error("Sample Error")',
|
||||
[],
|
||||
{ columnOffset: 3 }
|
||||
)();
|
||||
}, {
|
||||
message: 'Sample Error',
|
||||
stack: 'Error: Sample Error\n at <anonymous> (evalmachine.<anonymous>:2:16)' // modified from <anonymous>:1:10 to match Bun's stack trace formatting
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
vm.compileFunction(
|
||||
'return varInContext',
|
||||
[],
|
||||
{
|
||||
parsingContext: vm.createContext({ varInContext: 'abc' })
|
||||
}
|
||||
)(),
|
||||
'abc'
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
vm.compileFunction(
|
||||
'return varInContext',
|
||||
[]
|
||||
)();
|
||||
}, {
|
||||
message: 'varInContext is not defined',
|
||||
stack: 'ReferenceError: varInContext is not defined\n at <anonymous> (file:///:2:20)' // modified from <anonymous>:1:1 to match Bun's stack trace formatting
|
||||
});
|
||||
|
||||
assert.notDeepStrictEqual(
|
||||
vm.compileFunction(
|
||||
'return global',
|
||||
[],
|
||||
{
|
||||
parsingContext: vm.createContext({ global: {} })
|
||||
}
|
||||
)(),
|
||||
global
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
vm.compileFunction(
|
||||
'return global',
|
||||
[]
|
||||
)(),
|
||||
global
|
||||
);
|
||||
|
||||
{
|
||||
const source = 'console.log("Hello, World!")';
|
||||
// Test compileFunction produceCachedData option
|
||||
const result = vm.compileFunction(source, [], {
|
||||
produceCachedData: true,
|
||||
});
|
||||
|
||||
assert.ok(result.cachedDataProduced);
|
||||
assert.ok(result.cachedData.length > 0);
|
||||
|
||||
// Test compileFunction cachedData consumption
|
||||
const result2 = vm.compileFunction(source, [], {
|
||||
cachedData: result.cachedData
|
||||
});
|
||||
assert.strictEqual(result2.cachedDataRejected, false);
|
||||
|
||||
const result3 = vm.compileFunction('console.log("wrong source")', [], {
|
||||
cachedData: result.cachedData
|
||||
});
|
||||
assert.strictEqual(result3.cachedDataRejected, true);
|
||||
}
|
||||
|
||||
// Resetting value
|
||||
Error.stackTraceLimit = oldLimit;
|
||||
}
|
||||
30
test/js/node/test/parallel/test-vm-module-cached-data.js
Normal file
30
test/js/node/test/parallel/test-vm-module-cached-data.js
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
// Flags: --experimental-vm-modules
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const { SourceTextModule } = require('vm');
|
||||
|
||||
{
|
||||
const m = new SourceTextModule('const a = 1');
|
||||
const cachedData = m.createCachedData();
|
||||
|
||||
new SourceTextModule('const a = 1', { cachedData });
|
||||
|
||||
assert.throws(() => {
|
||||
new SourceTextModule('differentSource', { cachedData });
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_CACHED_DATA_REJECTED',
|
||||
});
|
||||
}
|
||||
|
||||
assert.rejects(async () => {
|
||||
const m = new SourceTextModule('const a = 1');
|
||||
await m.link(() => {});
|
||||
m.evaluate();
|
||||
m.createCachedData();
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA',
|
||||
}).then(common.mustCall());
|
||||
276
test/js/node/test/parallel/test-vm-module-errors.js
Normal file
276
test/js/node/test/parallel/test-vm-module-errors.js
Normal file
@@ -0,0 +1,276 @@
|
||||
'use strict';
|
||||
|
||||
// Flags: --experimental-vm-modules
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { SourceTextModule, createContext, Module } = require('vm');
|
||||
|
||||
async function createEmptyLinkedModule() {
|
||||
const m = new SourceTextModule('');
|
||||
await m.link(common.mustNotCall());
|
||||
return m;
|
||||
}
|
||||
|
||||
async function checkArgType() {
|
||||
assert.throws(() => {
|
||||
new SourceTextModule();
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError'
|
||||
});
|
||||
|
||||
for (const invalidOptions of [
|
||||
0, 1, null, true, 'str', () => {}, { identifier: 0 }, Symbol.iterator,
|
||||
{ context: null }, { context: 'hucairz' }, { context: {} },
|
||||
]) {
|
||||
assert.throws(() => {
|
||||
new SourceTextModule('', invalidOptions);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError'
|
||||
});
|
||||
}
|
||||
|
||||
for (const invalidLinker of [
|
||||
0, 1, undefined, null, true, 'str', {}, Symbol.iterator,
|
||||
]) {
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('');
|
||||
await m.link(invalidLinker);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check methods/properties can only be used under a specific state.
|
||||
async function checkModuleState() {
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('');
|
||||
await m.link(common.mustNotCall());
|
||||
assert.strictEqual(m.status, 'linked');
|
||||
await m.link(common.mustNotCall());
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_ALREADY_LINKED'
|
||||
});
|
||||
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('');
|
||||
m.link(common.mustNotCall());
|
||||
assert.strictEqual(m.status, 'linking');
|
||||
await m.link(common.mustNotCall());
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_STATUS'
|
||||
});
|
||||
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('');
|
||||
await m.evaluate();
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_STATUS',
|
||||
message: 'Module status must be one of linked, evaluated, or errored'
|
||||
});
|
||||
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('');
|
||||
await m.evaluate(false);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "options" argument must be of type object. ' +
|
||||
'Received type boolean (false)'
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
const m = new SourceTextModule('');
|
||||
m.error; // eslint-disable-line no-unused-expressions
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_STATUS',
|
||||
message: 'Module status must be errored'
|
||||
});
|
||||
|
||||
await assert.rejects(async () => {
|
||||
const m = await createEmptyLinkedModule();
|
||||
await m.evaluate();
|
||||
m.error; // eslint-disable-line no-unused-expressions
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_STATUS',
|
||||
message: 'Module status must be errored'
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
const m = new SourceTextModule('');
|
||||
m.namespace; // eslint-disable-line no-unused-expressions
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_STATUS',
|
||||
message: 'Module status must not be unlinked or linking'
|
||||
});
|
||||
}
|
||||
|
||||
// Check link() fails when the returned module is not valid.
|
||||
async function checkLinking() {
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('import "foo";');
|
||||
try {
|
||||
await m.link(common.mustCall(() => ({})));
|
||||
} catch (err) {
|
||||
assert.strictEqual(m.status, 'errored');
|
||||
throw err;
|
||||
}
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE'
|
||||
});
|
||||
|
||||
await assert.rejects(async () => {
|
||||
const c = createContext({ a: 1 });
|
||||
const foo = new SourceTextModule('', { context: c });
|
||||
await foo.link(common.mustNotCall());
|
||||
const bar = new SourceTextModule('import "foo";');
|
||||
try {
|
||||
await bar.link(common.mustCall(() => foo));
|
||||
} catch (err) {
|
||||
assert.strictEqual(bar.status, 'errored');
|
||||
throw err;
|
||||
}
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT'
|
||||
});
|
||||
|
||||
const error = new Error();
|
||||
await assert.rejects(async () => {
|
||||
globalThis.error = error;
|
||||
const erroredModule = new SourceTextModule('throw error;');
|
||||
await erroredModule.link(common.mustNotCall());
|
||||
try {
|
||||
await erroredModule.evaluate();
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
delete globalThis.error;
|
||||
|
||||
assert.strictEqual(erroredModule.status, 'errored');
|
||||
|
||||
const rootModule = new SourceTextModule('import "errored";');
|
||||
await rootModule.link(common.mustCall(() => erroredModule));
|
||||
}, {
|
||||
code: 'ERR_VM_MODULE_LINK_FAILURE',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
assert.throws(() => {
|
||||
new SourceTextModule('', {
|
||||
importModuleDynamically: 'hucairz'
|
||||
});
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "options.importModuleDynamically" property must be of type ' +
|
||||
"function. Received type string ('hucairz')"
|
||||
});
|
||||
|
||||
// Check the JavaScript engine deals with exceptions correctly
|
||||
async function checkExecution() {
|
||||
await (async () => {
|
||||
const m = new SourceTextModule('import { nonexistent } from "module";');
|
||||
|
||||
// There is no code for this exception since it is thrown by the JavaScript
|
||||
// engine.
|
||||
await assert.rejects(() => {
|
||||
return m.link(common.mustCall(() => new SourceTextModule('')));
|
||||
}, SyntaxError);
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const m = new SourceTextModule('throw new Error();');
|
||||
await m.link(common.mustNotCall());
|
||||
try {
|
||||
await m.evaluate();
|
||||
} catch (err) {
|
||||
assert.strictEqual(m.error, err);
|
||||
assert.strictEqual(m.status, 'errored');
|
||||
return;
|
||||
}
|
||||
assert.fail('Missing expected exception');
|
||||
})();
|
||||
}
|
||||
|
||||
// Check for error thrown when breakOnSigint is not a boolean for evaluate()
|
||||
async function checkInvalidOptionForEvaluate() {
|
||||
await assert.rejects(async () => {
|
||||
const m = new SourceTextModule('export const a = 1; export let b = 2');
|
||||
await m.evaluate({ breakOnSigint: 'a-string' });
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
message:
|
||||
'The "options.breakOnSigint" property must be of type boolean. ' +
|
||||
"Received type string ('a-string')",
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
|
||||
{
|
||||
['link', 'evaluate'].forEach(async (method) => {
|
||||
await assert.rejects(async () => {
|
||||
await Module.prototype[method]();
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "this" argument must be an instance of Module/
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkInvalidCachedData() {
|
||||
[true, false, 'foo', {}, Array, function() {}].forEach((invalidArg) => {
|
||||
const message = 'The "options.cachedData" property must be of ' +
|
||||
'type Buffer, TypedArray, or DataView.' +
|
||||
common.invalidArgTypeHelper(invalidArg);
|
||||
assert.throws(
|
||||
() => new SourceTextModule('import "foo";', { cachedData: invalidArg }),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function checkGettersErrors() {
|
||||
const expectedError = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "this" argument must be an instance of (?:Module|SourceTextModule)/,
|
||||
};
|
||||
const getters = ['identifier', 'context', 'namespace', 'status', 'error'];
|
||||
getters.forEach((getter) => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
Module.prototype[getter];
|
||||
}, expectedError);
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
SourceTextModule.prototype[getter];
|
||||
}, expectedError);
|
||||
});
|
||||
// `dependencySpecifiers` getter is just part of SourceTextModule
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
SourceTextModule.prototype.dependencySpecifiers;
|
||||
}, expectedError);
|
||||
}
|
||||
|
||||
const finished = common.mustCall();
|
||||
|
||||
(async function main() {
|
||||
await checkArgType();
|
||||
await checkModuleState();
|
||||
await checkLinking();
|
||||
await checkExecution();
|
||||
await checkInvalidOptionForEvaluate();
|
||||
checkInvalidCachedData();
|
||||
checkGettersErrors();
|
||||
finished();
|
||||
})().then(common.mustCall());
|
||||
168
test/js/node/test/parallel/test-vm-module-link.js
Normal file
168
test/js/node/test/parallel/test-vm-module-link.js
Normal file
@@ -0,0 +1,168 @@
|
||||
'use strict';
|
||||
|
||||
// Flags: --experimental-vm-modules --harmony-import-attributes
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { SourceTextModule } = require('vm');
|
||||
|
||||
async function simple() {
|
||||
const foo = new SourceTextModule('export default 5;');
|
||||
await foo.link(common.mustNotCall());
|
||||
|
||||
globalThis.fiveResult = undefined;
|
||||
const bar = new SourceTextModule('import five from "foo"; fiveResult = five');
|
||||
|
||||
assert.deepStrictEqual(bar.dependencySpecifiers, ['foo']);
|
||||
|
||||
await bar.link(common.mustCall((specifier, module) => {
|
||||
assert.strictEqual(module, bar);
|
||||
assert.strictEqual(specifier, 'foo');
|
||||
return foo;
|
||||
}));
|
||||
|
||||
await bar.evaluate();
|
||||
assert.strictEqual(globalThis.fiveResult, 5);
|
||||
delete globalThis.fiveResult;
|
||||
}
|
||||
|
||||
async function invalidLinkValue() {
|
||||
const invalidValues = [
|
||||
undefined,
|
||||
null,
|
||||
{},
|
||||
SourceTextModule.prototype,
|
||||
];
|
||||
|
||||
for (const value of invalidValues) {
|
||||
const module = new SourceTextModule('import "foo"');
|
||||
await assert.rejects(module.link(() => value), {
|
||||
code: 'ERR_VM_MODULE_NOT_MODULE',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function depth() {
|
||||
const foo = new SourceTextModule('export default 5');
|
||||
await foo.link(common.mustNotCall());
|
||||
|
||||
async function getProxy(parentName, parentModule) {
|
||||
const mod = new SourceTextModule(`
|
||||
import ${parentName} from '${parentName}';
|
||||
export default ${parentName};
|
||||
`);
|
||||
await mod.link(common.mustCall((specifier, module) => {
|
||||
assert.strictEqual(module, mod);
|
||||
assert.strictEqual(specifier, parentName);
|
||||
return parentModule;
|
||||
}));
|
||||
return mod;
|
||||
}
|
||||
|
||||
const bar = await getProxy('foo', foo);
|
||||
const baz = await getProxy('bar', bar);
|
||||
const barz = await getProxy('baz', baz);
|
||||
|
||||
await barz.evaluate();
|
||||
|
||||
assert.strictEqual(barz.namespace.default, 5);
|
||||
}
|
||||
|
||||
async function circular() {
|
||||
const foo = new SourceTextModule(`
|
||||
import getFoo from 'bar';
|
||||
export let foo = 42;
|
||||
export default getFoo();
|
||||
`);
|
||||
const bar = new SourceTextModule(`
|
||||
import { foo } from 'foo';
|
||||
export default function getFoo() {
|
||||
return foo;
|
||||
}
|
||||
`);
|
||||
await foo.link(common.mustCall(async (specifier, module) => {
|
||||
if (specifier === 'bar') {
|
||||
assert.strictEqual(module, foo);
|
||||
return bar;
|
||||
}
|
||||
assert.strictEqual(specifier, 'foo');
|
||||
assert.strictEqual(module, bar);
|
||||
assert.strictEqual(foo.status, 'linking');
|
||||
return foo;
|
||||
}, 2));
|
||||
|
||||
assert.strictEqual(bar.status, 'linked');
|
||||
|
||||
await foo.evaluate();
|
||||
assert.strictEqual(foo.namespace.default, 42);
|
||||
}
|
||||
|
||||
async function circular2() {
|
||||
const sourceMap = {
|
||||
'root': `
|
||||
import * as a from './a.mjs';
|
||||
import * as b from './b.mjs';
|
||||
if (!('fromA' in a))
|
||||
throw new Error();
|
||||
if (!('fromB' in a))
|
||||
throw new Error();
|
||||
if (!('fromA' in b))
|
||||
throw new Error();
|
||||
if (!('fromB' in b))
|
||||
throw new Error();
|
||||
`,
|
||||
'./a.mjs': `
|
||||
export * from './b.mjs';
|
||||
export let fromA;
|
||||
`,
|
||||
'./b.mjs': `
|
||||
export * from './a.mjs';
|
||||
export let fromB;
|
||||
`
|
||||
};
|
||||
const moduleMap = new Map();
|
||||
const rootModule = new SourceTextModule(sourceMap.root, {
|
||||
identifier: 'vm:root',
|
||||
});
|
||||
async function link(specifier, referencingModule) {
|
||||
if (moduleMap.has(specifier)) {
|
||||
return moduleMap.get(specifier);
|
||||
}
|
||||
const mod = new SourceTextModule(sourceMap[specifier], {
|
||||
identifier: new URL(specifier, 'file:///').href,
|
||||
});
|
||||
moduleMap.set(specifier, mod);
|
||||
return mod;
|
||||
}
|
||||
await rootModule.link(link);
|
||||
await rootModule.evaluate();
|
||||
}
|
||||
|
||||
async function asserts() {
|
||||
const m = new SourceTextModule(`
|
||||
import "foo" with { n1: 'v1', n2: 'v2' };
|
||||
`, { identifier: 'm' });
|
||||
await m.link((s, r, p) => {
|
||||
assert.strictEqual(s, 'foo');
|
||||
assert.strictEqual(r.identifier, 'm');
|
||||
assert.strictEqual(p.attributes.n1, 'v1');
|
||||
assert.strictEqual(p.assert.n1, 'v1');
|
||||
assert.strictEqual(p.attributes.n2, 'v2');
|
||||
assert.strictEqual(p.assert.n2, 'v2');
|
||||
return new SourceTextModule('');
|
||||
});
|
||||
}
|
||||
|
||||
const finished = common.mustCall();
|
||||
|
||||
(async function main() {
|
||||
await simple();
|
||||
await invalidLinkValue();
|
||||
await depth();
|
||||
await circular();
|
||||
await circular2();
|
||||
await asserts();
|
||||
finished();
|
||||
})().then(common.mustCall());
|
||||
52
test/js/node/test/parallel/test-vm-sigint.js
Normal file
52
test/js/node/test/parallel/test-vm-sigint.js
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows) {
|
||||
// No way to send CTRL_C_EVENT to processes from JS right now.
|
||||
common.skip('platform not supported');
|
||||
}
|
||||
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const method = process.argv[3];
|
||||
const listeners = +process.argv[4];
|
||||
assert.ok(method);
|
||||
assert.ok(Number.isInteger(listeners));
|
||||
|
||||
const script = `process.send('${method}'); while(true) {}`;
|
||||
const args = method === 'runInContext' ?
|
||||
[vm.createContext({ process })] :
|
||||
[];
|
||||
const options = { breakOnSigint: true };
|
||||
|
||||
for (let i = 0; i < listeners; i++)
|
||||
process.on('SIGINT', common.mustNotCall());
|
||||
|
||||
assert.throws(
|
||||
() => { vm[method](script, ...args, options); },
|
||||
{
|
||||
code: 'ERR_SCRIPT_EXECUTION_INTERRUPTED',
|
||||
message: 'Script execution was interrupted by `SIGINT`'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const method of ['runInThisContext', 'runInContext']) {
|
||||
for (const listeners of [0, 1, 2]) {
|
||||
const args = [__filename, 'child', method, listeners];
|
||||
const child = spawn(process.execPath, args, {
|
||||
stdio: [null, 'pipe', 'inherit', 'ipc']
|
||||
});
|
||||
|
||||
child.on('message', common.mustCall(() => {
|
||||
process.kill(child.pid, 'SIGINT');
|
||||
}));
|
||||
|
||||
child.on('close', common.mustCall((code, signal) => {
|
||||
assert.strictEqual(signal, null);
|
||||
assert.strictEqual(code, 0);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Flags: --experimental-vm-modules
|
||||
'use strict';
|
||||
|
||||
// https://github.com/nodejs/node/issues/3020
|
||||
// Promises used to allow code to escape the timeout
|
||||
// set for runInContext, runInNewContext, and runInThisContext.
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
const NS_PER_MS = 1000000n;
|
||||
|
||||
const hrtime = process.hrtime.bigint;
|
||||
|
||||
function loop() {
|
||||
const start = hrtime();
|
||||
while (1) {
|
||||
const current = hrtime();
|
||||
const span = (current - start) / NS_PER_MS;
|
||||
if (span >= 2000n) {
|
||||
throw new Error(
|
||||
`escaped timeout at ${span} milliseconds!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.rejects(async () => {
|
||||
const module = new vm.SourceTextModule(
|
||||
'Promise.resolve().then(() => loop()); loop();',
|
||||
{
|
||||
context: vm.createContext({
|
||||
hrtime,
|
||||
loop
|
||||
}, { microtaskMode: 'afterEvaluate' })
|
||||
});
|
||||
await module.link(common.mustNotCall());
|
||||
await module.evaluate({ timeout: 5 });
|
||||
}, {
|
||||
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||
message: 'Script execution timed out after 5ms'
|
||||
}).then(common.mustCall());
|
||||
39
test/js/node/test/parallel/test-vm-timeout-escape-promise.js
Normal file
39
test/js/node/test/parallel/test-vm-timeout-escape-promise.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
// https://github.com/nodejs/node/issues/3020
|
||||
// Promises used to allow code to escape the timeout
|
||||
// set for runInContext, runInNewContext, and runInThisContext.
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
const NS_PER_MS = 1000000n;
|
||||
|
||||
const hrtime = process.hrtime.bigint;
|
||||
|
||||
function loop() {
|
||||
const start = hrtime();
|
||||
while (1) {
|
||||
const current = hrtime();
|
||||
const span = (current - start) / NS_PER_MS;
|
||||
if (span >= 2000n) {
|
||||
throw new Error(
|
||||
`escaped timeout at ${span} milliseconds!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.throws(() => {
|
||||
vm.runInNewContext(
|
||||
'Promise.resolve().then(() => loop()); loop();',
|
||||
{
|
||||
hrtime,
|
||||
loop
|
||||
},
|
||||
{ timeout: 5, microtaskMode: 'afterEvaluate' }
|
||||
);
|
||||
}, {
|
||||
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||
message: 'Script execution timed out after 5ms'
|
||||
});
|
||||
81
test/js/node/test/parallel/test-vm-timeout.js
Normal file
81
test/js/node/test/parallel/test-vm-timeout.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
|
||||
// Timeout of 100ms executing endless loop
|
||||
assert.throws(
|
||||
function() {
|
||||
vm.runInThisContext('while(true) {}', { timeout: 100 });
|
||||
},
|
||||
{
|
||||
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||
message: 'Script execution timed out after 100ms'
|
||||
});
|
||||
|
||||
// Timeout of 1000ms, script finishes first
|
||||
vm.runInThisContext('', { timeout: 1000 });
|
||||
|
||||
// Nested vm timeouts, inner timeout propagates out
|
||||
assert.throws(
|
||||
function() {
|
||||
const context = {
|
||||
log: console.log,
|
||||
runInVM: function(timeout) {
|
||||
vm.runInNewContext('while(true) {}', context, { timeout });
|
||||
}
|
||||
};
|
||||
vm.runInNewContext('runInVM(10)', context, { timeout: 10000 });
|
||||
throw new Error('Test 5 failed');
|
||||
},
|
||||
{
|
||||
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||
message: 'Script execution timed out after 10ms'
|
||||
});
|
||||
|
||||
// Nested vm timeouts, outer timeout is shorter and fires first.
|
||||
assert.throws(
|
||||
function() {
|
||||
const context = {
|
||||
runInVM: function(timeout) {
|
||||
vm.runInNewContext('while(true) {}', context, { timeout });
|
||||
}
|
||||
};
|
||||
vm.runInNewContext('runInVM(10000)', context, { timeout: 100 });
|
||||
throw new Error('Test 6 failed');
|
||||
},
|
||||
{
|
||||
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||
message: 'Script execution timed out after 100ms'
|
||||
});
|
||||
|
||||
// Nested vm timeouts, inner script throws an error.
|
||||
assert.throws(function() {
|
||||
const context = {
|
||||
runInVM: function(timeout) {
|
||||
vm.runInNewContext('throw new Error(\'foobar\')', context, { timeout });
|
||||
}
|
||||
};
|
||||
vm.runInNewContext('runInVM(10000)', context, { timeout: 100000 });
|
||||
}, /foobar/);
|
||||
Reference in New Issue
Block a user