-
-
Notifications
You must be signed in to change notification settings - Fork 158
fix(hir): reject legacy numerics in strict eval #8698
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1730,6 +1730,16 @@ fn try_const_fold_eval( | |
| // plain assignment. (test262 language/eval-code/direct/strictness-override) | ||
| let eval_strict = ctx.current_strict || crate::lower_decl::body_has_use_strict(&body_stmts); | ||
|
|
||
| // SWC initially lexes the standalone eval body as a sloppy Script. That is | ||
| // necessary for legal sloppy-only syntax, but it means the lexer defers the | ||
| // strict-mode errors for legacy numeric literals (`01`, `08`, ...). Re-lex | ||
| // strict eval source with an explicit directive and surface those deferred | ||
| // diagnostics at the eval call. Modern `0o` literals remain valid, and the | ||
| // parser keeps comment/string contents out of the diagnostic stream. | ||
| if eval_strict && strict_eval_has_legacy_numeric_literal(&body_src) { | ||
| return synth_function_syntax_error(ctx, EvalSurface::Eval, span).map(Some); | ||
| } | ||
|
|
||
| // Annex B.3.3.3: a *sloppy global* direct eval routes the `var`/`function` | ||
| // declarations of its body into the global variable environment, so they | ||
| // survive after the eval returns. Rewrite them to global assignments before | ||
|
|
@@ -1767,6 +1777,32 @@ fn try_const_fold_eval( | |
| build_eval_completion_iife(ctx, body_stmts, eval_strict, span) | ||
| } | ||
|
|
||
| fn strict_eval_has_legacy_numeric_literal(source: &str) -> bool { | ||
| const LEGACY_NUMERIC_DIAGNOSTICS: [&str; 2] = [ | ||
| "Legacy decimal escape is not permitted in strict mode", | ||
| "Legacy octal escape is not permitted in strict mode", | ||
| ]; | ||
|
|
||
| let strict_source = format!("\"use strict\";\n{source}"); | ||
| let mut cache = perry_diagnostics::SourceCache::new(); | ||
| match perry_parser::parse_typescript_with_cache( | ||
| &strict_source, | ||
| "<strict eval numeric probe>.cjs", | ||
| &mut cache, | ||
| ) { | ||
| Ok(parsed) => parsed | ||
| .diagnostics | ||
| .iter() | ||
| .any(|diagnostic| LEGACY_NUMERIC_DIAGNOSTICS.contains(&diagnostic.message.as_str())), | ||
| Err(error) => { | ||
| let message = format!("{error:#}"); | ||
| LEGACY_NUMERIC_DIAGNOSTICS | ||
| .iter() | ||
| .any(|diagnostic| message.contains(diagnostic)) | ||
| } | ||
|
Comment on lines
+1786
to
+1802
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
file="crates/perry-hir/src/lower/const_fold_fn.rs"
printf '%s\n' '--- target file outline ---'
ast-grep outline "$file" --lang rust 2>/dev/null | sed -n '1,220p'
printf '%s\n' '--- target sections ---'
sed -n '1680,1835p' "$file"
sed -n '1860,1950p' "$file"
printf '%s\n' '--- relevant symbols and call sites ---'
rg -n -C 4 'try_const_fold_eval|strict_eval_has_legacy_numeric_literal|reparse_eval_body_with_super|SyntaxError|LEGACY_NUMERIC_DIAGNOSTICS' crates/perry-hir crates/perry-parser crates/perry-* 2>/dev/null | sed -n '1,280p'Repository: PerryTS/perry Length of output: 39707 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- syntax-error synthesis and eval helpers ---'
sed -n '1,155p' crates/perry-hir/src/lower/const_fold_fn.rs
sed -n '620,710p' crates/perry-hir/src/lower/const_fold_fn.rs
sed -n '1655,1810p' crates/perry-hir/src/lower/const_fold_fn.rs
printf '%s\n' '--- builtin identifier handling ---'
sed -n '55,125p' crates/perry-hir/src/lower/lower_expr/helpers.rs
rg -n -C 5 'SyntaxErrorNew|synth_function_syntax_error|EvalSurface::Eval|js_throw_eval_syntax_error' crates/perry-hir/src crates/perry-codegen-js/src crates/perry-codegen-wasm/src | sed -n '1,260p'
printf '%s\n' '--- parser entry points and diagnostic collection ---'
rg -n -C 5 'parse_typescript_with_cache|struct SourceCache|diagnostics' crates/perry-parser crates/perry-diagnostics crates | head -n 320Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- parser implementation ---'
sed -n '1,145p' crates/perry-parser/src/lib.rs
rg -n -C 8 'parse_source_file_with_typescript_fallback|fn parse_source_file|super' crates/perry-parser/src/lib.rs crates/perry-hir/src/lower/eval_super_scan.rs crates/perry-hir/src/lower | sed -n '1,260p'
printf '%s\n' '--- lowering of synthesized constructor names ---'
rg -n -C 8 'fn lower_expr|Expr::Ident|lookup_local|is_known_global_identifier_name|SyntaxError' crates/perry-hir/src/lower/lower_expr crates/perry-hir/src/lower | sed -n '1,340p'
printf '%s\n' '--- repository status and available runtimes ---'
git status --short
command -v node || true
node --version 2>/dev/null || trueRepository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- parser fallback and filename mode ---'
sed -n '139,225p' crates/perry-parser/src/lib.rs
rg -n -A 45 -B 8 'fn syntax_for_filename' crates/perry-parser/src/lib.rs
printf '%s\n' '--- identifier lowering body ---'
sed -n '125,285p' crates/perry-hir/src/lower/lower_expr/arm_ident.rs
printf '%s\n' '--- direct-eval parity probes ---'
if command -v node >/dev/null 2>&1; then
node <<'JS'
class Base {}
class Derived extends Base {
method() {
try {
eval("super.x; 01;");
return false;
} catch (error) {
console.log("legacy:", error.name, error instanceof SyntaxError);
return error instanceof SyntaxError;
}
}
modern() {
try {
eval("super.x; 1;");
return true;
} catch (error) {
console.log("modern:", error.name, error.message);
return false;
}
}
}
const d = new Derived();
console.log("result:", d.method());
console.log("modern-result:", d.modern());
JS
else
echo "node unavailable"
fiRepository: PerryTS/perry Length of output: 13646 Preserve direct-eval
🤖 Prompt for AI AgentsSource: MCP tools |
||
| } | ||
| } | ||
|
|
||
| /// Is the current eval call site at module top level in global-script mode, | ||
| /// where the enclosing variable environment *is* the global object — the only | ||
| /// place the Annex B.3.3.3 global var-scoped hoisting ([`apply_global_eval_hoist`]) | ||
|
|
@@ -1853,7 +1889,7 @@ fn build_eval_completion_iife( | |
|
|
||
| #[cfg(test)] | ||
| mod foldable_tests { | ||
| use super::eval_body_iife_foldable; | ||
| use super::{eval_body_iife_foldable, strict_eval_has_legacy_numeric_literal}; | ||
| use swc_ecma_ast as ast; | ||
|
|
||
| fn parse(src: &str) -> Vec<ast::Stmt> { | ||
|
|
@@ -1868,6 +1904,28 @@ mod foldable_tests { | |
| .collect() | ||
| } | ||
|
|
||
| #[test] | ||
| fn strict_eval_rejects_legacy_numeric_literals_only_in_code() { | ||
| for source in ["value = 01;", "value = 08;", "value = 000;"] { | ||
| assert!( | ||
| strict_eval_has_legacy_numeric_literal(source), | ||
| "expected strict early error for {source:?}" | ||
| ); | ||
| } | ||
|
|
||
| for source in [ | ||
| "value = 0o1; value = 0x1; value = 1;", | ||
| "value = '01';", | ||
| "// 01\nvalue = 1;", | ||
| "/* 08 */ value = 1;", | ||
| ] { | ||
| assert!( | ||
| !strict_eval_has_legacy_numeric_literal(source), | ||
| "unexpected strict early error for {source:?}" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn declaration_bearing_non_class_bodies_are_foldable() { | ||
| // The bodies that regressed to `undefined` because they declare a | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 38272
🏁 Script executed:
# Awaiting the repository inspection results.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50369
🏁 Script executed:
# Awaiting the targeted lowering and call-graph results.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50369
🏁 Script executed:
# Awaiting the focused constructor-lowering and regression-test results.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50369
🏁 Script executed:
# Awaiting the exact fallback, parser-diagnostic, and super-scanner results.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 32066
🏁 Script executed:
# Awaiting the parser and super-context evidence.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 38023
🏁 Script executed:
# Awaiting the parser error-order and super-scan details.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 240
🏁 Script executed:
# Awaiting the focused behavior verifier output.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 240
🏁 Script executed:
# Awaiting the corrected static behavior verifier output.Repository: PerryTS/perry
Length of output: 151
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 414
🏁 Script executed:
# Awaiting the corrected verifier output.Repository: PerryTS/perry
Length of output: 151
Force the synthesized error to use the intrinsic
SyntaxError.At line 1739,
synth_function_syntax_errorlowersnew SyntaxError(...)in the caller’s scope. WhenSyntaxErroris locally bound, lowering skips the intrinsic Error path and uses the local constructor. This can throw a user value orTypeErrorinstead of the intrinsicSyntaxError.Use a dedicated intrinsic error expression or force only this synthetic constructor through the global-intrinsic path. Add a regression test for strict
eval("01")with a localSyntaxErrorbinding.🤖 Prompt for AI Agents
Source: MCP tools