diff --git a/include/config.h b/include/config.h index 3d910b4..04373cd 100644 --- a/include/config.h +++ b/include/config.h @@ -61,6 +61,8 @@ typedef enum { SLOT_OBSERVABLE_SUBSCRIBER, SLOT_SUBSCRIPTION_OBSERVER, SLOT_SUBSCRIPTION_CLEANUP, + SLOT_HOISTED_VARS, + SLOT_HOISTED_VARS_LEN, SLOT_MAX = 255 } internal_slot_t; diff --git a/include/config.h.in b/include/config.h.in index d489011..be2489a 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -50,6 +50,8 @@ typedef enum { SLOT_OBSERVABLE_SUBSCRIBER, SLOT_SUBSCRIPTION_OBSERVER, SLOT_SUBSCRIPTION_CLEANUP, + SLOT_HOISTED_VARS, + SLOT_HOISTED_VARS_LEN, SLOT_MAX = 255 } internal_slot_t; diff --git a/src/ant.c b/src/ant.c index 5b13d6c..106cd41 100644 --- a/src/ant.c +++ b/src/ant.c @@ -454,6 +454,15 @@ static const uint8_t body_end_tok[TOK_MAX] = { [TOK_SEMICOLON] = 1, [TOK_COMMA] = 1, [TOK_EOF] = 1, }; +static const uint8_t expr_context_tok[TOK_MAX] = { + [TOK_ASSIGN] = 1, [TOK_LPAREN] = 1, [TOK_COLON] = 1, [TOK_LBRACKET] = 1, + [TOK_COMMA] = 1, [TOK_NOT] = 1, [TOK_Q] = 1, [TOK_OR] = 1, [TOK_AND] = 1, + [TOK_RETURN] = 1, [TOK_ARROW] = 1, [TOK_LAND] = 1, [TOK_LOR] = 1, + [TOK_PLUS_ASSIGN] = 1, [TOK_MINUS_ASSIGN] = 1, [TOK_MUL_ASSIGN] = 1, + [TOK_DIV_ASSIGN] = 1, [TOK_REM_ASSIGN] = 1, [TOK_AND_ASSIGN] = 1, + [TOK_OR_ASSIGN] = 1, [TOK_XOR_ASSIGN] = 1, [TOK_NULLISH] = 1, +}; + static const char *typestr_raw(uint8_t t) { const char *names[] = { "object", "prop", "string", "undefined", "null", "number", @@ -3393,7 +3402,17 @@ static void set_func_code_ptr(struct js *js, jsval_t func_obj, const char *code, static void set_func_code(struct js *js, jsval_t func_obj, const char *code, size_t len) { const char *arena_code = code_arena_alloc(code, len); if (!arena_code) return; + set_func_code_ptr(js, func_obj, arena_code, len); + if (!memmem(code, len, "var", 3)) return; + + size_t vars_buf_len; + char *vars = OXC_get_func_hoisted_vars(code, len, &vars_buf_len); + + if (vars) { + set_slot(js, func_obj, SLOT_HOISTED_VARS, mkval(T_CFUNC, (size_t)vars)); + set_slot(js, func_obj, SLOT_HOISTED_VARS_LEN, tov((double)vars_buf_len)); + } } static const char *get_func_code(struct js *js, jsval_t func_obj, jsoff_t *len) { @@ -4943,12 +4962,12 @@ static void hoist_function_declarations(struct js *js) { jsval_t saved_scope = js->scope; int depth = 0; - uint8_t tok; + uint8_t tok, prev_tok = TOK_EOF; while ((tok = next(js)) != TOK_EOF && !(tok == TOK_RBRACE && depth == 0)) { - if (tok == TOK_LBRACE) { depth++; js->consumed = 1; continue; } - if (tok == TOK_RBRACE) { depth--; js->consumed = 1; continue; } - if (depth > 0) { js->consumed = 1; continue; } + if (tok == TOK_LBRACE) { depth++; prev_tok = tok; js->consumed = 1; continue; } + if (tok == TOK_RBRACE) { depth--; prev_tok = tok; js->consumed = 1; continue; } + if (depth > 0) { prev_tok = tok; js->consumed = 1; continue; } if (tok == TOK_EXPORT) { js->consumed = 1; @@ -4963,23 +4982,40 @@ static void hoist_function_declarations(struct js *js) { js->consumed = 1; } -skip_export: - continue; + skip_export: { + prev_tok = tok; + continue; + } } if (depth == 0 && tok == TOK_FUNC) { + if (expr_context_tok[prev_tok]) { + prev_tok = tok; + js->consumed = 1; + continue; + } + jsoff_t after_func = js->pos; js->consumed = 1; + if (next(js) == TOK_IDENTIFIER) { js->pos = after_func; js->tok = TOK_FUNC; js->consumed = 1; js_func_decl(js); } + + prev_tok = tok; continue; } if (depth == 0 && tok == TOK_ASYNC) { + if (expr_context_tok[prev_tok]) { + prev_tok = tok; + js->consumed = 1; + continue; + } + js->consumed = 1; if (next(js) != TOK_FUNC) goto skip_async; jsoff_t func_pos = js->pos; @@ -4989,10 +5025,14 @@ skip_export: js->tok = TOK_FUNC; js->consumed = 0; js_func_decl_async(js); -skip_async: - continue; + + skip_async: { + prev_tok = tok; + continue; + } } + prev_tok = tok; js->consumed = 1; } @@ -5001,6 +5041,37 @@ skip_async: js->scope = saved_scope; } +static void declare_hoisted_vars(struct js *js, jsval_t var_scope, const char *var_names) { + const char *ptr = var_names; + while (*ptr) { + size_t len = strlen(ptr); + jsoff_t existing = lkp(js, var_scope, ptr, len); + if (existing == 0) mkprop(js, var_scope, js_mkstr(js, ptr, len), js_mkundef(), 0); + ptr += len + 1; + } +} + +static void hoist_var_declarations_from_slot(struct js *js, jsval_t var_scope, jsval_t func_obj) { + jsval_t vars_val = get_slot(js, func_obj, SLOT_HOISTED_VARS); + if (vtype(vars_val) != T_CFUNC) return; + const char *var_names = (const char *)vdata(vars_val); + if (!var_names) return; + declare_hoisted_vars(js, var_scope, var_names); +} + +static void hoist_var_declarations(struct js *js, jsval_t var_scope) { + if (js->flags & F_NOEXEC) return; + if (js->clen == 0) return; + if (!memmem(js->code, js->clen, "var", 3)) return; + + size_t buf_len; + char *var_names = OXC_get_hoisted_vars(js->code, (size_t)js->clen, &buf_len); + if (!var_names) return; + + declare_hoisted_vars(js, var_scope, var_names); + OXC_free_hoisted_vars(var_names, buf_len); +} + static jsval_t js_block(struct js *js, bool create_scope) { jsval_t res = js_mkundef(); bool scope_created = false; @@ -6633,13 +6704,12 @@ static jsval_t call_js_code_with_args(struct js *js, const char *fn, jsoff_t fnl jsval_t slot_name = get_slot(js, func_val, SLOT_NAME); if (vtype(slot_name) == T_STR && vtype(func_val) == T_FUNC) { - jsoff_t len; - (void)vstr(js, slot_name, &len); - if (len > 0) { - jsval_t prop = mkprop(js, function_scope, slot_name, func_val, CONSTMASK); - (void)prop; - } + jsoff_t len; vstr(js, slot_name, &len); + if (len > 0) mkprop(js, function_scope, slot_name, func_val, CONSTMASK); } + + jsval_t func_obj = mkval(T_OBJ, vdata(func_val)); + hoist_var_declarations_from_slot(js, function_scope, func_obj); jsoff_t fnpos = 1; int arg_idx = 0; @@ -22714,7 +22784,8 @@ static jsval_t js_eval_inherit_strict(struct js *js, const char *buf, size_t len } hoist_function_declarations(js); - + if (!(js->flags & F_CALL)) hoist_var_declarations(js, js->scope); + while (next(js) != TOK_EOF && !is_err(res)) { res = js_stmt(js); if (js->needs_gc && js->eval_depth == 1 && !js->gc_suppress) { diff --git a/src/strip/Cargo.lock b/src/strip/Cargo.lock index 103dc2c..6578cbf 100644 --- a/src/strip/Cargo.lock +++ b/src/strip/Cargo.lock @@ -51,15 +51,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -dependencies = [ - "allocator-api2", -] - [[package]] name = "castaway" version = "0.2.4" @@ -153,9 +144,9 @@ dependencies = [ [[package]] name = "dragonbox_ecma" -version = "0.0.5" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d742b56656e8b14d63e7ea9806597b1849ae25412584c8adf78c0f67bd985e66" +checksum = "6a5577f010d4e1bb3f3c4d6081e05718eb6992cf20119cab4d3abadff198b5ae" [[package]] name = "either" @@ -335,6 +326,8 @@ name = "oxc" version = "0.0.0" dependencies = [ "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", "oxc_codegen", "oxc_parser", "oxc_semantic", @@ -386,12 +379,11 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377063b29ba9762002f07bd4bc5cdd60373a4e3b094e15ea201eec07556d65ff" +checksum = "2174c7c8f77137b1bd1c653d7a5a531ae41f3b8fec1dd0251c801689784e7a2e" dependencies = [ "allocator-api2", - "bumpalo", "hashbrown", "oxc_data_structures", "rustc-hash", @@ -399,9 +391,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4d2aed570578cfe5249d1e50736971b85c4eedc39f9777f735914e10d42dd7" +checksum = "62f1902f97a5cac8767b76a1d8a1b3124e9db80c176ebbc98f75143dcc124a15" dependencies = [ "bitflags", "oxc_allocator", @@ -416,9 +408,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d5f0089ba8705ef065f8acb371c3ab44e3a7fb90372cccac6788b677e763f1" +checksum = "c5a31bd55516a98a35b2d99fa5813a3d3a5b798bad3262c819dfe7344bc6f390" dependencies = [ "phf", "proc-macro2", @@ -428,9 +420,9 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45305537949b2471ce9afe251a2051af6ed2188492cd079b1d83b96caa6714b5" +checksum = "e2c520a488c04ba5267223edd0bb245fb7f10e2358e8955802a5d962bb95b50a" dependencies = [ "oxc_allocator", "oxc_ast", @@ -440,9 +432,9 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf3d7a97d49d6557d315f7b806a246a6627aec4942b9bd50e670686be6d3b00" +checksum = "abfd3d146e6e0d340c183aa0e98f29ab1bba876c282350e5e06ab9d6f536eacd" dependencies = [ "bitflags", "cow-utils", @@ -461,9 +453,9 @@ dependencies = [ [[package]] name = "oxc_compat" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d772c9461d67a0bd1b8177674f73d7d666f8120bab90bfc37383eace93bcd82" +checksum = "7319f12eb8d4a05737a7f71642d7a97aee210488dc4041a7a452352a31ac0fe6" dependencies = [ "cow-utils", "oxc-browserslist", @@ -474,18 +466,18 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0264dbee1c186f744a433955a900a34f7a8bf6e4381d00100528d2b23043b21" +checksum = "a42840ce8d83a08a92823dda6189e4d97359feca24a4fa732f3256c4614bb5a4" dependencies = [ "ropey", ] [[package]] name = "oxc_diagnostics" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5d75130504c4b6792eba9a687349ec92cc7e220ddfc1cba35ef540c194b6ed" +checksum = "b4f7b09c1563a67ede53af131f717b31ba89a992959ebad188b5158c21d4dc0a" dependencies = [ "cow-utils", "oxc-miette", @@ -494,24 +486,25 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12400e655a18fe9ba1359324d7a634964f62d9a151008a48671e46abd9b728e1" +checksum = "4813b352bd5b0b05badf0c9e6c5ec7ea58a6a7ab49bec8d18ead262624c6ef8d" dependencies = [ "cow-utils", "num-bigint", "num-traits", "oxc_allocator", "oxc_ast", + "oxc_regular_expression", "oxc_span", "oxc_syntax", ] [[package]] name = "oxc_estree" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2059abb4c194b588d128c1933b6c20fde1b443b053e51da818caed17c272f65" +checksum = "e54fb3effe995e6538d68070bf0a450b5ffd11dd41b62f11a4d01efa1f40e278" [[package]] name = "oxc_index" @@ -525,9 +518,9 @@ dependencies = [ [[package]] name = "oxc_parser" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090741583620244210f9988d13e7ddec78d8c22b13f885eb8d31c6566528c943" +checksum = "5592bf8b64743944eb46528f9eabdde2b2435c8293cd502f5c183f9dff644e16" dependencies = [ "bitflags", "cow-utils", @@ -548,9 +541,9 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47fe9458baea58cfe0d514b32a3258839f9cad1af738a92c1c55ccea97c5ceb7" +checksum = "09de7f7e0fb82f54750e3a95346a828fd354b9aeac00f131719008733e66a18d" dependencies = [ "bitflags", "oxc_allocator", @@ -564,9 +557,9 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e425a107259ff3417682df38a9964491013de0080aecbaab7e43eb1f452b6de1" +checksum = "8c2269186b4f1510a76daf02914cb70e82a78549de451b8276bba0a419c62ac3" dependencies = [ "itertools", "memchr", @@ -581,6 +574,7 @@ dependencies = [ "oxc_syntax", "rustc-hash", "self_cell", + "smallvec", ] [[package]] @@ -598,9 +592,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb492189296521cf459f4caaee8d7525c70341daf10083c53c962c13e4748ee" +checksum = "2a42c0759b745eca0fe776890af46ce12e79e61796995e51a8eb9dcdf5516ab0" dependencies = [ "compact_str", "oxc-miette", @@ -611,9 +605,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09691f16fb7ed90acb442e88faca68751f2222dbed892122a9b50fc7d161c8d" +checksum = "b63eac2e04a75a10c5714aeb753cdfa06b1abc66bbaa748b7994700f52c9b184" dependencies = [ "bitflags", "cow-utils", @@ -631,9 +625,9 @@ dependencies = [ [[package]] name = "oxc_transformer" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b2b72d1e073c39b4993d7dfd71daeb7ea6fb445f5c8b445be6998022ee4f76" +checksum = "0e394bc5221c9e228fc06f54b7f7a3e2d63ed135a50b8678e8485b5b49222bb5" dependencies = [ "base64", "compact_str", @@ -660,9 +654,9 @@ dependencies = [ [[package]] name = "oxc_traverse" -version = "0.107.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcaa9168b7ae1f7df2461e33210605bbe60dd2b638af08a7093310c232f1bda" +checksum = "4473bf963b351d5b744b75aee9ff6aa41d62f8ca662012b03dc315cac9f1f2e5" dependencies = [ "itoa", "oxc_allocator", @@ -872,6 +866,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smawk" diff --git a/src/strip/Cargo.toml b/src/strip/Cargo.toml index 51fc76c..f73dfe1 100644 --- a/src/strip/Cargo.toml +++ b/src/strip/Cargo.toml @@ -7,12 +7,14 @@ path = "oxc.rs" crate-type = ["staticlib", "cdylib"] [dependencies] -oxc_allocator = "0.107.0" -oxc_parser = "0.107.0" -oxc_span = "0.107.0" -oxc_codegen = "0.107.0" -oxc_transformer = "0.107.0" -oxc_semantic = "0.107.0" +oxc_allocator = "0.110.0" +oxc_parser = "0.110.0" +oxc_span = "0.110.0" +oxc_codegen = "0.110.0" +oxc_transformer = "0.110.0" +oxc_semantic = "0.110.0" +oxc_ast = "0.110.0" +oxc_ast_visit = "0.110.0" [profile.release] lto = true diff --git a/src/strip/oxc.h b/src/strip/oxc.h index d0eb268..73307bb 100644 --- a/src/strip/oxc.h +++ b/src/strip/oxc.h @@ -16,4 +16,18 @@ int OXC_strip_types( size_t output_len ); +char *OXC_get_hoisted_vars( + const char *input, + size_t input_len, + size_t *out_len +); + +char *OXC_get_func_hoisted_vars( + const char *input, + size_t input_len, + size_t *out_len +); + +void OXC_free_hoisted_vars(char *ptr, size_t len); + #endif diff --git a/src/strip/oxc.rs b/src/strip/oxc.rs index 6ebb6bf..0fc30eb 100644 --- a/src/strip/oxc.rs +++ b/src/strip/oxc.rs @@ -1,11 +1,13 @@ -use std::ffi::{c_char, c_int, CStr}; +use std::ffi::{CStr, c_char, c_int}; use std::path::Path; use std::ptr; use oxc_allocator::Allocator; +use oxc_ast::ast::{BindingPattern, VariableDeclarationKind}; +use oxc_ast_visit::{Visit, walk}; use oxc_codegen::Codegen; use oxc_parser::Parser; -use oxc_semantic::SemanticBuilder; +use oxc_semantic::{ScopeFlags, SemanticBuilder}; use oxc_span::SourceType; use oxc_transformer::{TransformOptions, Transformer, TypeScriptOptions}; @@ -15,16 +17,11 @@ pub const OXC_ERR_TRANSFORM_FAILED: c_int = -4; pub const OXC_ERR_OUTPUT_TOO_LARGE: c_int = -5; #[unsafe(no_mangle)] -pub unsafe extern "C" fn OXC_strip_types( - input: *const c_char, - filename: *const c_char, - output: *mut c_char, - output_len: usize, -) -> c_int { +pub unsafe extern "C" fn OXC_strip_types(input: *const c_char, filename: *const c_char, output: *mut c_char, output_len: usize) -> c_int { if input.is_null() || output.is_null() { return OXC_ERR_NULL_INPUT; } - + let filename_str = match unsafe { CStr::from_ptr(filename).to_str() } { Ok(s) => s, Err(_) => return OXC_ERR_INVALID_UTF8, @@ -38,7 +35,9 @@ pub unsafe extern "C" fn OXC_strip_types( match strip_types_internal(input_str, filename_str) { Ok(result) => { let bytes = result.as_bytes(); - if bytes.len() + 1 > output_len { return OXC_ERR_OUTPUT_TOO_LARGE; } + if bytes.len() + 1 > output_len { + return OXC_ERR_OUTPUT_TOO_LARGE; + } unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), output as *mut u8, bytes.len()); *output.add(bytes.len()) = 0; @@ -88,3 +87,213 @@ fn strip_types_internal(source: &str, filename: &str) -> Result let output = Codegen::new().build(&program).code; Ok(output) } + +struct VarCollector<'a> { + vars: Vec<&'a str>, + func_depth: usize, + target_depth: usize, +} + +impl<'a> VarCollector<'a> { + fn new() -> Self { + Self { + vars: Vec::new(), + func_depth: 0, + target_depth: 0, + } + } + fn for_func_body() -> Self { + Self { + vars: Vec::new(), + func_depth: 0, + target_depth: 1, + } + } +} + +impl<'a> Visit<'a> for VarCollector<'a> { + fn visit_function(&mut self, func: &oxc_ast::ast::Function<'a>, flags: ScopeFlags) { + self.func_depth += 1; + walk::walk_function(self, func, flags); + self.func_depth -= 1; + } + + fn visit_arrow_function_expression(&mut self, expr: &oxc_ast::ast::ArrowFunctionExpression<'a>) { + self.func_depth += 1; + walk::walk_arrow_function_expression(self, expr); + self.func_depth -= 1; + } + + fn visit_variable_declaration(&mut self, decl: &oxc_ast::ast::VariableDeclaration<'a>) { + if decl.kind == VariableDeclarationKind::Var && self.func_depth == self.target_depth { + for declarator in &decl.declarations { + self.extract_binding_names(&declarator.id); + } + } + walk::walk_variable_declaration(self, decl); + } +} + +impl<'a> VarCollector<'a> { + fn extract_binding_names(&mut self, pattern: &BindingPattern<'a>) { + use BindingPattern::*; + match pattern { + BindingIdentifier(id) => self.vars.push(id.name.as_str()), + ObjectPattern(obj) => { + for prop in &obj.properties { + self.extract_binding_names(&prop.value); + } + if let Some(rest) = &obj.rest { + self.extract_binding_names(&rest.argument); + } + } + ArrayPattern(arr) => { + for elem in arr.elements.iter().flatten() { + self.extract_binding_names(elem); + } + if let Some(rest) = &arr.rest { + self.extract_binding_names(&rest.argument); + } + } + AssignmentPattern(assign) => { + self.extract_binding_names(&assign.left); + } + } + } +} + +fn collect_var_names(source: &str) -> Result, String> { + let allocator = Allocator::default(); + let source_type = SourceType::mjs(); + let parser_ret = Parser::new(&allocator, source, source_type).parse(); + + if !parser_ret.errors.is_empty() { + let errors: Vec = parser_ret.errors.iter().map(|e| e.to_string()).collect(); + return Err(format!("Parse errors: {}", errors.join("; "))); + } + + let mut collector = VarCollector::new(); + collector.visit_program(&parser_ret.program); + + Ok(collector.vars.into_iter().map(String::from).collect()) +} + +fn collect_var_names_from_func(source: &str) -> Result, String> { + let allocator = Allocator::default(); + let source_type = SourceType::mjs(); + + let wrapped = format!("(function{})", source); + let parser_ret = Parser::new(&allocator, &wrapped, source_type).parse(); + + if !parser_ret.errors.is_empty() { + let errors: Vec = parser_ret.errors.iter().map(|e| e.to_string()).collect(); + return Err(format!("Parse errors: {}", errors.join("; "))); + } + + let mut collector = VarCollector::for_func_body(); + collector.visit_program(&parser_ret.program); + + Ok(collector.vars.into_iter().map(String::from).collect()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn OXC_get_hoisted_vars(input: *const c_char, input_len: usize, out_len: *mut usize) -> *mut c_char { + if input.is_null() || out_len.is_null() { + return ptr::null_mut(); + } + + let input_slice = unsafe { std::slice::from_raw_parts(input as *const u8, input_len) }; + let input_str = match std::str::from_utf8(input_slice) { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + }; + + match collect_var_names(input_str) { + Ok(vars) => { + if vars.is_empty() { + return ptr::null_mut(); + } + let total_len: usize = vars.iter().map(|s| s.len() + 1).sum::() + 1; + + let layout = std::alloc::Layout::from_size_align(total_len, 1).unwrap(); + let ptr = unsafe { std::alloc::alloc(layout) as *mut c_char }; + if ptr.is_null() { + return ptr::null_mut(); + } + + let mut offset = 0; + for v in &vars { + unsafe { + ptr::copy_nonoverlapping(v.as_ptr(), ptr.add(offset) as *mut u8, v.len()); + *ptr.add(offset + v.len()) = 0; + } + offset += v.len() + 1; + } + unsafe { + *ptr.add(offset) = 0; + } + + unsafe { + *out_len = total_len; + } + ptr + } + Err(_) => ptr::null_mut(), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn OXC_get_func_hoisted_vars(input: *const c_char, input_len: usize, out_len: *mut usize) -> *mut c_char { + if input.is_null() || out_len.is_null() { + return ptr::null_mut(); + } + + let input_slice = unsafe { std::slice::from_raw_parts(input as *const u8, input_len) }; + let input_str = match std::str::from_utf8(input_slice) { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + }; + + match collect_var_names_from_func(input_str) { + Ok(vars) => { + if vars.is_empty() { + return ptr::null_mut(); + } + let total_len: usize = vars.iter().map(|s| s.len() + 1).sum::() + 1; + + let layout = std::alloc::Layout::from_size_align(total_len, 1).unwrap(); + let ptr = unsafe { std::alloc::alloc(layout) as *mut c_char }; + if ptr.is_null() { + return ptr::null_mut(); + } + + let mut offset = 0; + for v in &vars { + unsafe { + ptr::copy_nonoverlapping(v.as_ptr(), ptr.add(offset) as *mut u8, v.len()); + *ptr.add(offset + v.len()) = 0; + } + offset += v.len() + 1; + } + unsafe { + *ptr.add(offset) = 0; + } + unsafe { + *out_len = total_len; + } + ptr + } + Err(_) => ptr::null_mut(), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn OXC_free_hoisted_vars(ptr: *mut c_char, len: usize) { + if ptr.is_null() || len == 0 { + return; + } + let layout = std::alloc::Layout::from_size_align(len, 1).unwrap(); + unsafe { + std::alloc::dealloc(ptr as *mut u8, layout); + } +} diff --git a/tests/hoist.js b/tests/hoist.js new file mode 100644 index 0000000..d3ba453 --- /dev/null +++ b/tests/hoist.js @@ -0,0 +1,38 @@ +function hoist1() { + b(); // b is hoisted from below + function b() { + console.log('b'); + } +} + +function hoist2() { + b(); // b exists but is undefined + var b = function b() { + console.log('b'); + }; +} + +function hoist3() { + b(); // b does not exist + let b = function b() { + console.log('b'); + }; +} + +try { + hoist1(); +} catch (err) { + console.log(err); +} + +try { + hoist2(); +} catch (err) { + console.log(err); +} + +try { + hoist3(); +} catch (err) { + console.log(err); +}