Skip to content

Commit 779877f

Browse files
committed
fix: address PR review comments
- Copy fn pointer out of CUSTOM_MODULES lock before calling module declaration to prevent potential deadlock (spin::Mutex not re-entrant) - Move cargo:rerun-if-env-changed=HYPERLIGHT_JS_RUNTIME_PATH before the match so Cargo tracks the env var even when unset; use canonical path for rerun-if-changed - Lock down globalThis.console binding (non-writable/non-configurable) after Object.freeze to prevent handler code replacing it entirely - Fix test doc comment: built-in overrides work except for require Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent cd6829d commit 779877f

File tree

8 files changed

+108
-31
lines changed

8 files changed

+108
-31
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[workspace]
22
resolver = "2"
33
members = ["src/hyperlight-js", "src/js-host-api", "src/hyperlight-js-runtime"]
4-
exclude = ["src/hyperlight-js-runtime/tests/fixtures/extended_runtime"]
4+
exclude = ["src/hyperlight-js-runtime/tests/fixtures"]
55

66
[workspace.package]
77
version = "0.1.1"

src/hyperlight-js-runtime/src/globals/freeze.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use rquickjs::Ctx;
2020
/// Called AFTER custom_globals! so extender crates can modify/extend
2121
/// globals first (e.g. adding console.warn/error/info/debug).
2222
///
23-
/// Frozen: console (Object.freeze), print (non-writable).
23+
/// Frozen: console (Object.freeze + non-writable/non-configurable binding),
24+
/// print (non-writable/non-configurable binding).
2425
/// Already frozen: require (non-configurable from setup),
2526
/// String.bytesFrom (on frozen String constructor).
2627
pub fn setup(ctx: &Ctx<'_>) -> rquickjs::Result<()> {
@@ -29,6 +30,12 @@ pub fn setup(ctx: &Ctx<'_>) -> rquickjs::Result<()> {
2930
if (typeof globalThis.console === 'object') {
3031
Object.freeze(globalThis.console);
3132
}
33+
if ('console' in globalThis) {
34+
Object.defineProperty(globalThis, 'console', {
35+
writable: false,
36+
configurable: false
37+
});
38+
}
3239
if (typeof globalThis.print === 'function') {
3340
Object.defineProperty(globalThis, 'print', {
3441
writable: false,

src/hyperlight-js-runtime/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,17 @@ hyperlight_js_runtime::custom_globals! {}
3131
// etc.) is provided by the lib's `guest` module.
3232
// The binary only needs to provide the native CLI entry point.
3333

34+
// Force the guest module to be linked into the final hyperlight binary.
35+
// Without this, the linker may drop the guest module's object since nothing
36+
// in main.rs references the guest entrypoints directly.
37+
#[cfg(hyperlight)]
38+
unsafe extern "C" {
39+
fn hyperlight_main();
40+
}
41+
42+
#[cfg(hyperlight)]
43+
#[used]
44+
static _FORCE_GUEST_LINK: unsafe extern "C" fn() = hyperlight_main;
45+
3446
#[cfg(not(hyperlight))]
3547
include!("main/native.rs");

src/hyperlight-js-runtime/src/modules/mod.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ use rquickjs::module::ModuleDef;
2121
use rquickjs::{Ctx, Module, Result};
2222
use spin::{Lazy, Mutex};
2323

24-
pub(crate) mod console;
25-
pub(crate) mod crypto;
26-
pub(crate) mod io;
27-
pub(crate) mod require;
24+
#[doc(hidden)]
25+
pub mod console;
26+
#[doc(hidden)]
27+
pub mod crypto;
28+
#[doc(hidden)]
29+
pub mod io;
30+
#[doc(hidden)]
31+
pub mod require;
2832

2933
/// A function pointer type for declaring a native module.
3034
#[doc(hidden)]
@@ -115,7 +119,9 @@ pub struct NativeModuleLoader;
115119
impl Resolver for NativeModuleLoader {
116120
fn resolve(&mut self, _ctx: &Ctx<'_>, base: &str, name: &str) -> Result<String> {
117121
ensure_custom_modules_init();
118-
if CUSTOM_MODULES.lock().contains_key(name) || BUILTIN_MODULES.contains_key(name) {
122+
// Copy result out before dropping the lock to avoid holding it during resolution
123+
let found_custom = CUSTOM_MODULES.lock().contains_key(name);
124+
if found_custom || BUILTIN_MODULES.contains_key(name) {
119125
Ok(name.to_string())
120126
} else {
121127
Err(rquickjs::Error::new_resolving(base, name))
@@ -126,8 +132,12 @@ impl Resolver for NativeModuleLoader {
126132
impl Loader for NativeModuleLoader {
127133
fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result<Module<'js>> {
128134
ensure_custom_modules_init();
129-
// Check custom modules first
130-
if let Some(decl) = CUSTOM_MODULES.lock().get(name) {
135+
// Copy the fn pointer out while holding the lock, then drop the guard
136+
// before calling the declaration. This avoids deadlock if decl()
137+
// triggers a nested module load that tries to lock CUSTOM_MODULES
138+
// (spin::Mutex is not re-entrant).
139+
let custom_decl = CUSTOM_MODULES.lock().get(name).copied();
140+
if let Some(decl) = custom_decl {
131141
return decl(ctx.clone(), name);
132142
}
133143
// Fall back to built-in modules

src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight-js-runtime/tests/fixtures/extended_runtime/src/main.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ fn main() -> anyhow::Result<()> {
5050
use std::{env, fs};
5151

5252
let args: Vec<String> = env::args().collect();
53+
if args.len() < 3 {
54+
let program = args.first().map(|s| s.as_str()).unwrap_or("extended_runtime");
55+
eprintln!("Usage: {program} <handler_file> <event_json>");
56+
return Err(anyhow::anyhow!(
57+
"missing required arguments: <handler_file> and <event_json>"
58+
));
59+
}
5360
let file = std::path::PathBuf::from(&args[1]);
5461
let event = &args[2];
5562

@@ -77,3 +84,13 @@ fn main() -> anyhow::Result<()> {
7784

7885
// For hyperlight builds: the lib's `guest` module provides hyperlight_main,
7986
// guest_dispatch_function, and all plumbing. Nothing else needed here.
87+
88+
// Force the guest module to be linked for hyperlight builds.
89+
#[cfg(hyperlight)]
90+
unsafe extern "C" {
91+
fn hyperlight_main();
92+
}
93+
94+
#[cfg(hyperlight)]
95+
#[used]
96+
static _FORCE_GUEST_LINK: unsafe extern "C" fn() = hyperlight_main;

src/hyperlight-js-runtime/tests/native_modules.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ limitations under the License.
2020
//! - `register_native_module` adds custom modules to the loader
2121
//! - Built-in modules always work
2222
//! - Custom modules can be imported from JS handlers
23-
//! - Built-in module names cannot be overridden (panics)
23+
//! - Overriding most built-in module names works; overriding `require` panics
2424
//! - The `native_modules!` macro generates correct `init_native_modules`
2525
//! - Full pipeline tests with the extended_runtime fixture binary
2626
@@ -345,8 +345,30 @@ const EXTENDED_RUNTIME_MANIFEST: &str = concat!(
345345
);
346346

347347
static EXTENDED_RUNTIME_BINARY: LazyLock<PathBuf> = LazyLock::new(|| {
348+
let fixture_dir = PathBuf::from(EXTENDED_RUNTIME_MANIFEST)
349+
.parent()
350+
.unwrap()
351+
.to_path_buf();
352+
let target_dir = fixture_dir.join("target");
353+
354+
// Match the profile of the outer test build so the fixture binary
355+
// lands in the same subdirectory we look for it in.
356+
let profile = if cfg!(debug_assertions) {
357+
"dev"
358+
} else {
359+
"release"
360+
};
361+
348362
let output = Command::new("cargo")
349-
.args(["build", "--manifest-path", EXTENDED_RUNTIME_MANIFEST])
363+
.args([
364+
"build",
365+
"--locked",
366+
"--manifest-path",
367+
EXTENDED_RUNTIME_MANIFEST,
368+
"--target-dir",
369+
])
370+
.arg(&target_dir)
371+
.args(["--profile", profile])
350372
.output()
351373
.expect("Failed to run cargo build for extended-runtime fixture");
352374

@@ -356,16 +378,14 @@ static EXTENDED_RUNTIME_BINARY: LazyLock<PathBuf> = LazyLock::new(|| {
356378
String::from_utf8_lossy(&output.stderr)
357379
);
358380

359-
let fixture_dir = PathBuf::from(EXTENDED_RUNTIME_MANIFEST)
360-
.parent()
361-
.unwrap()
362-
.to_path_buf();
381+
// Cargo outputs "dev" profile binaries to "debug" directory
382+
let profile_dir = if profile == "dev" { "debug" } else { profile };
363383
let binary_name = if cfg!(windows) {
364384
"extended-runtime.exe"
365385
} else {
366386
"extended-runtime"
367387
};
368-
let binary = fixture_dir.join("target/debug").join(binary_name);
388+
let binary = target_dir.join(profile_dir).join(binary_name);
369389
assert!(binary.exists(), "Binary not found at {binary:?}");
370390
binary
371391
});

src/hyperlight-js/build.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,25 @@ fn build_js_runtime() -> PathBuf {
193193
}
194194

195195
fn bundle_runtime() {
196+
// Always rerun if the environment variable changes, even if it's currently unset.
197+
println!("cargo:rerun-if-env-changed=HYPERLIGHT_JS_RUNTIME_PATH");
198+
196199
let js_runtime_resource = match env::var("HYPERLIGHT_JS_RUNTIME_PATH") {
197200
Ok(path) => {
198-
println!("cargo:warning=Using custom JS runtime: {}", path);
199-
println!("cargo:rerun-if-env-changed=HYPERLIGHT_JS_RUNTIME_PATH");
200-
println!("cargo:rerun-if-changed={}", path);
201-
PathBuf::from(path)
201+
let canonical = PathBuf::from(&path)
202202
.canonicalize()
203-
.expect("HYPERLIGHT_JS_RUNTIME_PATH must point to a valid file")
203+
.expect("HYPERLIGHT_JS_RUNTIME_PATH must point to a valid file");
204+
assert!(
205+
canonical.is_file(),
206+
"HYPERLIGHT_JS_RUNTIME_PATH must point to a file, not a directory: {}",
207+
canonical.display()
208+
);
209+
println!(
210+
"cargo:warning=Using custom JS runtime: {}",
211+
canonical.display()
212+
);
213+
println!("cargo:rerun-if-changed={}", canonical.display());
214+
canonical
204215
}
205216
Err(_) => build_js_runtime(),
206217
};

0 commit comments

Comments
 (0)