Compare commits

...

9 Commits

Author SHA1 Message Date
Kai Tamkun
96acbdbc2d oops 2025-04-25 16:27:20 -07:00
Kai Tamkun
1980ba9124 Protect SigintWatcher::m_globalObjects with a mutex 2025-04-25 16:24:55 -07:00
Kai Tamkun
3f75a5dbdc Avoid erasing before the last element of a vector 2025-04-25 16:21:47 -07:00
Kai Tamkun
68ce0dd3f6 Initial work on breakOnSigint support 2025-04-25 16:16:53 -07:00
Kai Tamkun
ab870c5bd8 Add test-vm-codegen.js (currently modified pending clarification from JSC team) 2025-04-24 14:07:27 -07:00
Kai Tamkun
76af83a2eb Fix accidental requirement of contextCodeGeneration property 2025-04-23 18:54:35 -07:00
Kai Tamkun
a5a055af94 Add support for contextCodeGeneration 2025-04-23 18:50:04 -07:00
Kai Tamkun
fccc8b8902 node:vm: add support for codeGeneration.strings and codeGeneration.wasm 2025-04-23 18:24:55 -07:00
Kai Tamkun
0d171c42fa Initial work on improving node:vm support 2025-04-23 18:16:33 -07:00
11 changed files with 482 additions and 55 deletions

View File

@@ -637,6 +637,7 @@ file(GLOB BUN_CXX_SOURCES ${CONFIGURE_DEPENDS}
${CWD}/src/bun.js/bindings/v8/shim/*.cpp
${CWD}/src/bake/*.cpp
${CWD}/src/deps/*.cpp
${CWD}/src/vm/*.cpp
${BUN_USOCKETS_SOURCE}/src/crypto/*.cpp
)
@@ -797,7 +798,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()

View File

@@ -804,8 +804,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 std::optional<HashMap<String, int>> signalNameToNumberMap;
static std::optional<HashMap<int, String>> signalNumberToNameMap;
// 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 {
@@ -860,7 +860,7 @@ static void loadSignalNumberMap()
static std::once_flag signalNameToNumberMapOnceFlag;
std::call_once(signalNameToNumberMapOnceFlag, [] {
auto signalNames = getSignalNames();
signalNameToNumberMap = new HashMap<String, int>();
signalNameToNumberMap.emplace();
signalNameToNumberMap->reserveInitialCapacity(31);
#if OS(WINDOWS)
// libuv supported signals
@@ -1072,7 +1072,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);
@@ -1101,7 +1101,7 @@ static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& e
static std::once_flag signalNumberToNameMapOnceFlag;
std::call_once(signalNumberToNameMapOnceFlag, [] {
auto signalNames = getSignalNames();
signalNumberToNameMap = new HashMap<int, String>();
signalNumberToNameMap.emplace();
signalNumberToNameMap->reserveInitialCapacity(31);
signalNumberToNameMap->add(SIGHUP, signalNames[0]);
signalNumberToNameMap->add(SIGINT, signalNames[1]);

View File

@@ -76,20 +76,26 @@ bool JSNextTickQueue::isEmpty()
void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
auto throwScope = DECLARE_THROW_SCOPE(vm);
bool mustResetContext = false;
if (isEmpty()) {
RETURN_IF_EXCEPTION(throwScope, );
vm.drainMicrotasks();
RETURN_IF_EXCEPTION(throwScope, );
mustResetContext = true;
}
if (!isEmpty()) {
RETURN_IF_EXCEPTION(throwScope, );
if (mustResetContext) {
globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined());
RETURN_IF_EXCEPTION(throwScope, );
}
auto* drainFn = internalField(2).get().getObject();
auto throwScope = DECLARE_THROW_SCOPE(vm);
RETURN_IF_EXCEPTION(throwScope, );
MarkedArgumentBuffer drainArgs;
JSC::call(globalObject, drainFn, drainArgs, "Failed to drain next tick queue"_s);
RETURN_IF_EXCEPTION(throwScope, );
}
}

View File

@@ -43,6 +43,8 @@
#include "JavaScriptCore/JSCInlines.h"
#include "../vm/SigintWatcher.h"
namespace Bun {
using namespace WebCore;
@@ -51,8 +53,7 @@ using namespace WebCore;
/// This code is adapted/inspired from JSC::constructFunction, which is used for function declarations.
static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr);
static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset);
NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox);
static std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey);
/// For some reason Node has this error message with a grammar error and we have to match it so the tests pass:
/// `The "<name>" argument must be an vm.Context`
@@ -149,10 +150,10 @@ template<typename, JSC::SubspaceAccess mode> JSC::GCClient::IsoSubspace* NodeVMG
[](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; });
}
NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure)
NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options)
{
auto* cell = new (NotNull, JSC::allocateCell<NodeVMGlobalObject>(vm)) NodeVMGlobalObject(vm, structure);
cell->finishCreation(vm);
cell->finishCreation(vm, options);
return cell;
}
@@ -162,9 +163,12 @@ Structure* NodeVMGlobalObject::createStructure(JSC::VM& vm, JSC::JSValue prototy
return JSC::Structure::create(vm, nullptr, prototype, JSC::TypeInfo(JSC::GlobalObjectType, StructureFlags & ~IsImmutablePrototypeExoticObject), info());
}
void NodeVMGlobalObject::finishCreation(JSC::VM&)
void NodeVMGlobalObject::finishCreation(JSC::VM&, NodeVMContextOptions options)
{
Base::finishCreation(vm());
setEvalEnabled(options.allowStrings, "Code generation from strings disallowed for this context"_s);
setWebAssemblyEnabled(options.allowWasm, "Wasm code generation disallowed by embedder"_s);
vm().ensureTerminationException();
}
void NodeVMGlobalObject::destroy(JSCell* cell)
@@ -174,6 +178,7 @@ void NodeVMGlobalObject::destroy(JSCell* cell)
NodeVMGlobalObject::~NodeVMGlobalObject()
{
SigintWatcher::get().unregisterGlobalObject(this);
}
void NodeVMGlobalObject::setContextifiedObject(JSC::JSObject* contextifiedObject)
@@ -186,6 +191,11 @@ void NodeVMGlobalObject::clearContextifiedObject()
m_sandbox.clear();
}
void NodeVMGlobalObject::sigintReceived()
{
vm().notifyNeedTermination();
}
bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
// if (!propertyName.isSymbol())
@@ -779,6 +789,34 @@ static bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr<Excep
return false;
}
extern "C" void Bun__ensureSignalHandler();
static void ensureSigintHandler()
{
#if !OS(WINDOWS)
Bun__ensureSignalHandler();
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
// Set the handler in the action struct
action.sa_handler = [](int signalNumber) {
SigintWatcher::get().signalReceived();
};
// Clear the sa_mask
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGINT);
action.sa_flags = SA_RESTART;
sigaction(SIGINT, &action, nullptr);
SigintWatcher::get().install();
#else
static_assert(false, "TODO(@heimskr): implement sigint handler on Windows");
#endif
}
static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false)
{
@@ -797,6 +835,11 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM
// Set the contextified object before evaluating
globalObject->setContextifiedObject(contextifiedObject);
if (options.breakOnSigint) {
ensureSigintHandler();
SigintWatcher::get().registerGlobalObject(globalObject);
}
NakedPtr<Exception> exception;
JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception);
@@ -894,6 +937,10 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject,
options = {};
}
if (options.breakOnSigint) {
ensureSigintHandler();
}
NakedPtr<Exception> exception;
JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception);
@@ -942,9 +989,18 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject
JSObject* sandbox = asObject(contextArg);
JSValue contextOptionsArg = callFrame->argument(2);
NodeVMContextOptions contextOptions {};
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration")) {
return *encodedException;
}
// Create context and run code
auto* context = NodeVMGlobalObject::create(vm,
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure());
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(),
contextOptions);
context->setContextifiedObject(sandbox);
@@ -1148,7 +1204,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject,
auto* zigGlobal = defaultGlobalObject(globalObject);
JSObject* context = asObject(contextObjectValue);
auto* targetContext = NodeVMGlobalObject::create(
vm, zigGlobal->NodeVMGlobalObjectStructure());
vm, zigGlobal->NodeVMGlobalObjectStructure(), {});
return runInContext(targetContext, script, context, callFrame->argument(1));
}
@@ -1158,21 +1214,6 @@ Structure* createNodeVMGlobalObjectStructure(JSC::VM& vm)
return NodeVMGlobalObject::createStructure(vm, jsNull());
}
NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox)
{
auto* targetContext = NodeVMGlobalObject::create(vm,
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure());
// Set sandbox as contextified object
targetContext->setContextifiedObject(sandbox);
// Store context in WeakMap for isContext checks
auto* zigGlobalObject = defaultGlobalObject(globalObject);
zigGlobalObject->vmModuleContextMap()->set(vm, sandbox, targetContext);
return targetContext;
}
JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
@@ -1194,31 +1235,17 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject,
return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg);
}
// If options is provided, validate name and origin properties
if (optionsArg.isObject()) {
JSObject* options = asObject(optionsArg);
NodeVMContextOptions contextOptions {};
// Check name property
if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) {
RETURN_IF_EXCEPTION(scope, {});
if (!nameValue.isUndefined() && !nameValue.isString()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue);
}
}
// Check origin property
if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) {
RETURN_IF_EXCEPTION(scope, {});
if (!originValue.isUndefined() && !originValue.isString()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue);
}
}
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, optionsArg, contextOptions, "codeGeneration")) {
return *encodedException;
}
JSObject* sandbox = asObject(contextArg);
auto* targetContext = NodeVMGlobalObject::create(vm,
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure());
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(),
contextOptions);
// Set sandbox as contextified object
targetContext->setContextifiedObject(sandbox);
@@ -1571,8 +1598,8 @@ static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const Arg
auto body = args.at(0).toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
program = tryMakeString("(function () {"_s, body, "})"_s);
*outOffset = "(function () {"_s.length();
program = tryMakeString("(function () {\n"_s, body, "\n})"_s);
*outOffset = "(function () {\n"_s.length();
if (UNLIKELY(!program)) {
throwOutOfMemoryError(globalObject, scope);
@@ -1596,8 +1623,8 @@ static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const Arg
auto body = args.at(parameterCount).toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
program = tryMakeString("(function ("_s, paramString.toString(), ") {"_s, body, "})"_s);
*outOffset = "(function ("_s.length() + paramString.length() + ") {"_s.length();
program = tryMakeString("(function ("_s, paramString.toString(), ") {\n"_s, body, "\n})"_s);
*outOffset = "(function ("_s.length() + paramString.length() + ") {\n"_s.length();
if (UNLIKELY(!program)) {
throwOutOfMemoryError(globalObject, scope);
@@ -1608,4 +1635,68 @@ static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const Arg
return program;
}
// Returns an encoded exception if the options are invalid.
// Otherwise, returns an empty optional.
static std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey)
{
outOptions = {};
// If options is provided, validate name and origin properties
if (!optionsArg.isObject()) {
return std::nullopt;
}
JSObject* options = asObject(optionsArg);
// Check name property
if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) {
RETURN_IF_EXCEPTION(scope, {});
if (!nameValue.isUndefined() && !nameValue.isString()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue);
}
}
// Check origin property
if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) {
RETURN_IF_EXCEPTION(scope, {});
if (!originValue.isUndefined() && !originValue.isString()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue);
}
}
if (JSValue codeGenerationValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, codeGenerationKey))) {
RETURN_IF_EXCEPTION(scope, {});
if (codeGenerationValue.isUndefined()) {
return std::nullopt;
}
if (!codeGenerationValue.isObject()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey), "object"_s, codeGenerationValue);
}
JSObject* codeGenerationObject = asObject(codeGenerationValue);
if (JSValue allowStringsValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "strings"_s))) {
RETURN_IF_EXCEPTION(scope, {});
if (!allowStringsValue.isBoolean()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey, ".strings"_s), "boolean"_s, allowStringsValue);
}
outOptions.allowStrings = allowStringsValue.toBoolean(globalObject);
}
if (JSValue allowWasmValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "wasm"_s))) {
RETURN_IF_EXCEPTION(scope, {});
if (!allowWasmValue.isBoolean()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey, ".wasm"_s), "boolean"_s, allowWasmValue);
}
outOptions.allowWasm = allowWasmValue.toBoolean(globalObject);
}
}
return std::nullopt;
}
} // namespace Bun

View File

@@ -13,6 +13,11 @@
#include <JavaScriptCore/Nodes.h>
namespace Bun {
class NodeVMContextOptions final {
public:
bool allowStrings = true;
bool allowWasm = true;
};
// This class represents a sandboxed global object for vm contexts
class NodeVMGlobalObject final : public Bun::GlobalScope {
@@ -23,16 +28,17 @@ 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);
void clearContextifiedObject();
void sigintReceived();
// Override property access to delegate to contextified object
static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&);

View File

@@ -8,7 +8,7 @@ const ObjectFreeze = Object.freeze;
const { createContext, isContext, Script, runInNewContext, runInThisContext, compileFunction } = vm;
function runInContext(code, context, options) {
return new Script(code, options).runInContext(context);
return new Script(code, options).runInContext(context, options);
}
function createScript(code, options) {

50
src/vm/Semaphore.cpp Normal file
View File

@@ -0,0 +1,50 @@
#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

32
src/vm/Semaphore.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#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

99
src/vm/SigintWatcher.cpp Normal file
View File

@@ -0,0 +1,99 @@
#include "NodeVM.h"
#include "SigintWatcher.h"
extern "C" void Bun__onPosixSignal(int signalNumber);
namespace Bun {
SigintWatcher SigintWatcher::s_instance;
SigintWatcher::SigintWatcher()
: m_semaphore(1)
{
m_globalObjects.reserve(16);
}
SigintWatcher::~SigintWatcher()
{
uninstall();
}
void SigintWatcher::install()
{
if (m_installed.exchange(true)) {
return;
}
m_thread = std::thread([this] {
while (m_installed.load()) {
bool success = m_semaphore.wait();
ASSERT(success);
if (m_waiting.test_and_set()) {
m_waiting.clear();
if (!signalAll()) {
Bun__onPosixSignal(SIGINT);
}
} else {
m_waiting.clear();
}
}
});
}
void SigintWatcher::uninstall()
{
if (m_installed.exchange(false)) {
m_thread.join();
}
}
void SigintWatcher::signalReceived()
{
if (!m_waiting.test_and_set()) {
bool success = m_semaphore.signal();
ASSERT(success);
}
}
void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject)
{
std::unique_lock lock(m_globalObjectsMutex);
m_globalObjects.push_back(globalObject);
}
void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject)
{
std::unique_lock 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.back());
m_globalObjects.pop_back();
}
SigintWatcher& SigintWatcher::get()
{
return s_instance;
}
bool SigintWatcher::signalAll()
{
std::unique_lock lock(m_globalObjectsMutex);
if (m_globalObjects.empty()) {
return false;
}
for (NodeVMGlobalObject* globalObject : m_globalObjects) {
globalObject->sigintReceived();
}
return true;
}
} // namespace Bun

41
src/vm/SigintWatcher.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "root.h"
#include "Semaphore.h"
#include <atomic>
#include <mutex>
#include <thread>
#include <vector>
namespace Bun {
class NodeVMGlobalObject;
class SigintWatcher {
public:
SigintWatcher();
~SigintWatcher();
void install();
void uninstall();
void signalReceived();
void registerGlobalObject(NodeVMGlobalObject* globalObject);
void unregisterGlobalObject(NodeVMGlobalObject* globalObject);
static SigintWatcher& get();
private:
std::thread m_thread;
std::atomic_bool m_installed = false;
std::atomic_flag m_waiting = false;
Semaphore m_semaphore;
std::mutex m_globalObjectsMutex;
std::vector<NodeVMGlobalObject*> m_globalObjects;
bool signalAll();
static SigintWatcher s_instance;
};
} // namespace Bun

View File

@@ -0,0 +1,101 @@
'use strict';
require('../common');
const assert = require('assert');
const { createContext, runInContext, runInNewContext } = require('vm');
const WASM_BYTES = Buffer.from(
[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
{
const ctx = createContext({ WASM_BYTES });
const test = 'eval(""); new WebAssembly.Module(WASM_BYTES);';
runInContext(test, ctx);
runInNewContext(test, { WASM_BYTES }, {
contextCodeGeneration: undefined,
});
}
{
const ctx = createContext({}, {
codeGeneration: {
strings: false,
},
});
const EvalError = runInContext('EvalError', ctx);
assert.throws(() => {
runInContext('eval("x")', ctx);
}, EvalError);
}
{
const ctx = createContext({ WASM_BYTES }, {
codeGeneration: {
wasm: false,
},
});
const CompileError = runInContext('WebAssembly.CompileError', ctx);
assert.rejects(() => {
return runInContext('try { WebAssembly.instantiate(new WebAssembly.Module(WASM_BYTES)); } catch (e) { Promise.reject(e); }', ctx);
}, CompileError);
}
assert.throws(() => {
runInNewContext('eval("x")', {}, {
contextCodeGeneration: {
strings: false,
},
});
}, {
name: 'EvalError'
});
assert.rejects(() => {
return runInNewContext('try { WebAssembly.instantiate(new WebAssembly.Module(WASM_BYTES)); } catch (e) { Promise.reject(e); }', { WASM_BYTES }, {
contextCodeGeneration: {
wasm: false,
},
});
}, {
name: 'CompileError'
});
assert.throws(() => {
createContext({}, {
codeGeneration: {
strings: 0,
},
});
}, {
code: 'ERR_INVALID_ARG_TYPE',
});
assert.throws(() => {
runInNewContext('eval("x")', {}, {
contextCodeGeneration: {
wasm: 1,
},
});
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
createContext({}, {
codeGeneration: 1,
});
}, {
code: 'ERR_INVALID_ARG_TYPE',
});
assert.throws(() => {
createContext({}, {
codeGeneration: null,
});
}, {
code: 'ERR_INVALID_ARG_TYPE',
});