-
Notifications
You must be signed in to change notification settings - Fork 318
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a new `UnsafeEval` binding APi that is intended only for use with workerd and only when the `--experimental` flag is specified. See the included test for an example on how to use it.
- Loading branch information
Showing
10 changed files
with
175 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { | ||
strictEqual, | ||
ok, | ||
throws | ||
} from 'node:assert'; | ||
|
||
export const basics = { | ||
test(ctx, env) { | ||
strictEqual(env.unsafe.eval('1'), 1); | ||
|
||
// eval does not capture outer scope. | ||
let m = 1; | ||
throws(() => env.unsafe.eval('m')); | ||
|
||
throws(() => env.unsafe.eval(' throw new Error("boom"); ', 'foo'), { | ||
message: 'boom', | ||
stack: /at foo/ | ||
}); | ||
|
||
// Regular dynamic eval is still not allowed | ||
throws(() => eval('')); | ||
} | ||
}; | ||
|
||
export const newFunction = { | ||
test(ctx, env) { | ||
const fn = env.unsafe.newFunction('return m', 'bar', 'm'); | ||
strictEqual(fn.length, 1); | ||
strictEqual(fn.name, 'bar'); | ||
strictEqual(fn(), undefined); | ||
strictEqual(fn(1), 1); | ||
strictEqual(fn(fn), fn); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Workerd = import "/workerd/workerd.capnp"; | ||
|
||
const unitTests :Workerd.Config = ( | ||
services = [ | ||
( name = "unsafe-test", | ||
worker = ( | ||
modules = [ | ||
(name = "worker", esModule = embed "unsafe-test.js") | ||
], | ||
compatibilityDate = "2023-01-15", | ||
compatibilityFlags = ["nodejs_compat", "experimental"], | ||
bindings = [ | ||
(name = "unsafe", unsafeEval = void ) | ||
] | ||
) | ||
), | ||
], | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
#include "unsafe.h" | ||
|
||
namespace workerd::api { | ||
|
||
namespace { | ||
static constexpr auto EVAL_STR = "eval"_kjc; | ||
inline kj::StringPtr getName(jsg::Optional<kj::String>& name, kj::StringPtr def) { | ||
return name.map([](kj::String& str) { | ||
return str.asPtr(); | ||
}).orDefault(def); | ||
} | ||
} // namespace | ||
|
||
jsg::JsValue UnsafeEval::eval(jsg::Lock& js, kj::String script, | ||
jsg::Optional<kj::String> name) { | ||
js.setAllowEval(true); | ||
KJ_DEFER(js.setAllowEval(false)); | ||
auto compiled = jsg::NonModuleScript::compile(script, js, getName(name, EVAL_STR)); | ||
return jsg::JsValue(compiled.runAndReturn(js.v8Context())); | ||
} | ||
|
||
UnsafeEval::UnsafeEvalFunction UnsafeEval::newFunction( | ||
jsg::Lock& js, | ||
jsg::JsString script, | ||
jsg::Optional<kj::String> name, | ||
jsg::Arguments<jsg::JsRef<jsg::JsString>> args, | ||
const jsg::TypeHandler<UnsafeEvalFunction>& handler) { | ||
js.setAllowEval(true); | ||
KJ_DEFER(js.setAllowEval(false)); | ||
|
||
auto nameStr = js.str(getName(name, EVAL_STR)); | ||
v8::ScriptOrigin origin(js.v8Isolate, nameStr); | ||
v8::ScriptCompiler::Source source(script, origin); | ||
|
||
auto argNames = KJ_MAP(arg, args) { | ||
return v8::Local<v8::String>(arg.getHandle(js)); | ||
}; | ||
|
||
auto fn = jsg::check(v8::ScriptCompiler::CompileFunction( | ||
js.v8Context(), &source, argNames.size(), argNames.begin(), 0, nullptr)); | ||
fn->SetName(nameStr); | ||
|
||
return KJ_ASSERT_NONNULL(handler.tryUnwrap(js, fn)); | ||
} | ||
|
||
} // namespace workerd::api |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#pragma once | ||
|
||
#include <workerd/jsg/jsg.h> | ||
|
||
namespace workerd::api { | ||
|
||
// A special binding object that allows for dynamic evaluation. | ||
class UnsafeEval: public jsg::Object { | ||
public: | ||
UnsafeEval() = default; | ||
|
||
// A non-capturing eval. Compile and evaluates the given script, returning whatever | ||
// value is returned by the script. This version of eval intentionally does not | ||
// capture any part of the outer scope other than globalThis and globally scoped | ||
// variables. The optional `name` will appear in stack traces for any errors thrown. | ||
// | ||
// console.log(env.unsafe.eval('1 + 1')); // prints 2 | ||
// | ||
jsg::JsValue eval(jsg::Lock& js, kj::String script, jsg::Optional<kj::String> name); | ||
|
||
using UnsafeEvalFunction = jsg::Function<jsg::Value(jsg::Arguments<jsg::Value>)>; | ||
|
||
// Compiles and returns a new Function using the given script. The function does not | ||
// capture any part of the outer scope other than globalThis and globally scoped | ||
// variables. The optional `name` will be set as the name of the function and will | ||
// appear in stack traces for any errors thrown. An optional list of argument names | ||
// can be passed in. | ||
// | ||
// const fn = env.unsafe.newFunction('return m', 'foo', 'm'); | ||
// console.log(fn(1)); // prints 1 | ||
// | ||
UnsafeEvalFunction newFunction( | ||
jsg::Lock& js, | ||
jsg::JsString script, | ||
jsg::Optional<kj::String> name, | ||
jsg::Arguments<jsg::JsRef<jsg::JsString>> args, | ||
const jsg::TypeHandler<UnsafeEvalFunction>& handler); | ||
|
||
JSG_RESOURCE_TYPE(UnsafeEval) { | ||
JSG_METHOD(eval); | ||
JSG_METHOD(newFunction); | ||
} | ||
}; | ||
|
||
#define EW_UNSAFE_ISOLATE_TYPES \ | ||
api::UnsafeEval | ||
|
||
} // namespace workerd::api |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters