Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: More accurate lifetime bounds on scoped methods to allow valid code to compile #919

Merged
merged 1 commit into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions crates/neon/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,10 @@ pub trait Context<'a>: ContextInternal<'a> {
/// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape.
///
/// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks.
fn execute_scoped<T, F>(&mut self, f: F) -> T
fn execute_scoped<'b, T, F>(&mut self, f: F) -> T
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't provide as much tangible value, but I thought it was good to keep them aligned.

where
F: for<'b> FnOnce(ExecuteContext<'b>) -> T,
'a: 'b,
F: FnOnce(ExecuteContext<'b>) -> T,
{
let env = self.env();
let scope = unsafe { HandleScope::new(env.to_raw()) };
Expand All @@ -263,17 +264,17 @@ pub trait Context<'a>: ContextInternal<'a> {
/// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape, with the exception of the result value, which is rooted in the outer context.
///
/// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks.
fn compute_scoped<V, F>(&mut self, f: F) -> JsResult<'a, V>
fn compute_scoped<'b, V, F>(&mut self, f: F) -> JsResult<'a, V>
where
'a: 'b,
V: Value,
F: for<'b, 'c> FnOnce(ComputeContext<'b, 'c>) -> JsResult<'b, V>,
F: FnOnce(ComputeContext<'b>) -> JsResult<'b, V>,
{
let env = self.env();
let scope = unsafe { EscapableHandleScope::new(env.to_raw()) };
let cx = ComputeContext {
env,
phantom_inner: PhantomData,
phantom_outer: PhantomData,
};

let escapee = unsafe { scope.escape(f(cx)?.to_raw()) };
Expand Down Expand Up @@ -585,19 +586,18 @@ impl<'a> ContextInternal<'a> for ExecuteContext<'a> {
impl<'a> Context<'a> for ExecuteContext<'a> {}

/// An execution context of a scope created by [`Context::compute_scoped()`](Context::compute_scoped).
pub struct ComputeContext<'a, 'outer> {
pub struct ComputeContext<'a> {
env: Env,
phantom_inner: PhantomData<&'a ()>,
phantom_outer: PhantomData<&'outer ()>,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was required before to ensure that ComputeContext couldn't accidentally outlive the outer context. However, compute_scoped now includes a 'a: 'b bound that provides the same guarantee.

}

impl<'a, 'b> ContextInternal<'a> for ComputeContext<'a, 'b> {
impl<'a> ContextInternal<'a> for ComputeContext<'a> {
fn env(&self) -> Env {
self.env
}
}

impl<'a, 'b> Context<'a> for ComputeContext<'a, 'b> {}
impl<'a> Context<'a> for ComputeContext<'a> {}

/// An execution context of a function call.
///
Expand Down
3 changes: 3 additions & 0 deletions test/napi/lib/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ describe("JsFunction", function () {
});

it("computes a value in a scoped computation", function () {
const o = {};

assert.equal(addon.compute_scoped(), 99);
assert.equal(addon.recompute_scoped(o), o);
});

it("catches an exception with cx.try_catch", function () {
Expand Down
8 changes: 8 additions & 0 deletions test/napi/src/js/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ pub fn compute_scoped(mut cx: FunctionContext) -> JsResult<JsNumber> {
Ok(i)
}

// Simple identity function to verify that a handle can be moved to `compute_scoped`
// closure and re-escaped.
pub fn recompute_scoped(mut cx: FunctionContext) -> JsResult<JsValue> {
let value = cx.argument::<JsValue>(0)?;

cx.compute_scoped(move |_| Ok(value))
}

pub fn throw_and_catch(mut cx: FunctionContext) -> JsResult<JsValue> {
let v = cx
.argument_opt(0)
Expand Down
1 change: 1 addition & 0 deletions test/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("check_string_and_number", check_string_and_number)?;
cx.export_function("execute_scoped", execute_scoped)?;
cx.export_function("compute_scoped", compute_scoped)?;
cx.export_function("recompute_scoped", recompute_scoped)?;

cx.export_function("return_js_array", return_js_array)?;
cx.export_function("return_js_array_with_number", return_js_array_with_number)?;
Expand Down