mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 09:20:56 -06:00
Merge main into fix/script-rethrown-error-place
This commit is contained in:
@@ -194,7 +194,6 @@ fn script_target_other_app_does_not_route() {
|
||||
("#target\nvc.log(1);", "#target needs a value"),
|
||||
("#targetengine \"session\"", "unsupported script directive: #targetengine"),
|
||||
("#engine \"main\"", "unsupported script directive: #engine"),
|
||||
("#strict on", "unsupported strict checking: #strict on"),
|
||||
("#strict maybe", "#strict takes on or off"),
|
||||
("#frobnicate 1", "unknown script directive: #frobnicate"),
|
||||
("#include \"x.jsxinc", "unterminated quote in #include"),
|
||||
@@ -223,12 +222,134 @@ fn script_name_comes_from_the_script_line() {
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
/// The script's `ro`, with a read-only `x`.
|
||||
const READ_ONLY: &str = "var ro = {};\nObject.defineProperty(ro, 'x', {value: 1});\n";
|
||||
|
||||
#[test]
|
||||
fn script_strict_on_is_refused() {
|
||||
fn strict_on_runs_and_read_only_write_throws() {
|
||||
let mut s = Session::new();
|
||||
let e = s.execute("script.run", &json!({"source": "#strict on\nvc.log(1);"})).unwrap_err().to_string();
|
||||
assert!(e.contains("unsupported strict checking: #strict on"), "{e}");
|
||||
assert_eq!(s.script_report().unwrap()["kind"], "syntax");
|
||||
for head in ["#strict on\n", "//@strict on\n", "#strict ON\n", "#strict off\n#strict on\n"] {
|
||||
let source = format!("{head}{READ_ONLY}vc.log(1);\n ro.x = 2;\nvc.log(2);");
|
||||
let e = s.execute("script.run", &json!({"source": source})).unwrap_err().to_string();
|
||||
assert!(e.contains("TypeError: cannot assign to property 'x': it is read-only"), "{head}: {e}");
|
||||
let report = s.script_report().unwrap();
|
||||
assert_eq!(report["kind"], "thrown", "{head}");
|
||||
assert_eq!(report["log"], json!([1]), "{head}: the script ran up to the write");
|
||||
let line = head.lines().count() as u64 + 4;
|
||||
assert_eq!((report["line"].as_u64(), report["column"].as_u64()), (Some(line), Some(3)), "{head}: {report}");
|
||||
}
|
||||
// The error can be caught, and every kind of assignment is checked.
|
||||
let source = format!(
|
||||
"#strict on\n{READ_ONLY}var out = [];\nfunction t(f) {{ try {{ f(); out.push('wrote'); }} catch (e) {{ out.push(e.name); }} }}\nt(function () {{ ro.x = 2; }});\nt(function () {{ ro['x'] += 1; }});\nt(function () {{ ro.x++; }});\nt(function () {{ undefined = 1; }});\nt(function () {{ [].concat.length = 0; }});\nvc.log(out);\nvc.log(ro.x);"
|
||||
);
|
||||
let r = s.execute("script.run", &json!({"source": source})).unwrap();
|
||||
assert_eq!(r["log"], json!([["TypeError", "TypeError", "TypeError", "TypeError", "TypeError"], 1]));
|
||||
// Without the line, or with `#strict off` last, the write is ignored as before.
|
||||
for head in ["", "#strict off\n", "#strict on\n#strict off\n"] {
|
||||
let r = s.execute("script.run", &json!({"source": format!("{head}{READ_ONLY}ro.x = 2;\nvc.log(ro.x);")})).unwrap();
|
||||
assert_eq!(r["log"], json!([1]), "{head}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_on_rejects_getter_only_and_nonextensible_additions() {
|
||||
let mut s = Session::new();
|
||||
let source = "#strict on\nvar out = [];\nfunction t(f) { try { f(); out.push('wrote'); } catch (e) { out.push(String(e)); } }\n\
|
||||
var g = {get v() { return 1; }}, set = {set v(x) { this.got = x; }};\n\
|
||||
t(function () { g.v = 2; });\n\
|
||||
t(function () { Object.preventExtensions({}).n = 1; });\n\
|
||||
t(function () { Object.seal({a: 1}).n = 1; });\n\
|
||||
t(function () { Object.freeze({a: 1}).a = 2; });\n\
|
||||
t(function () { vc.extra = 1; });\n\
|
||||
var open = {};\nt(function () { open.n = 1; });\nt(function () { set.v = 3; });\nt(function () { Object.seal({a: 1}).a = 2; });\n\
|
||||
vc.log(out);\nvc.log(open.n);\nvc.log(set.got);";
|
||||
let r = s.execute("script.run", &json!({"source": source})).unwrap();
|
||||
assert_eq!(
|
||||
r["log"],
|
||||
json!([
|
||||
[
|
||||
"TypeError: cannot assign to property 'v': it has only a getter",
|
||||
"TypeError: cannot assign to property 'n': the object is not extensible",
|
||||
"TypeError: cannot assign to property 'n': the object is not extensible",
|
||||
"TypeError: cannot assign to property 'a': it is read-only",
|
||||
"TypeError: cannot assign to property 'extra': the object is not extensible",
|
||||
"wrote",
|
||||
"wrote",
|
||||
"wrote"
|
||||
],
|
||||
1,
|
||||
3
|
||||
])
|
||||
);
|
||||
let r = s.execute("script.run", &json!({"source": source.replacen("#strict on", "#strict off", 1)})).unwrap();
|
||||
assert_eq!(r["log"][0], json!(["wrote", "wrote", "wrote", "wrote", "wrote", "wrote", "wrote", "wrote"]), "off, nothing is refused");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_on_does_not_enable_es5_early_errors() {
|
||||
let mut s = Session::new();
|
||||
let source = "#strict on\nwith ({w: 1}) { vc.log(w); }\nfunction f(a, a) { return a; }\nvc.log(f(1, 2));\nvc.log(010);\nundeclared = 4;\nvc.log(undeclared);\n\
|
||||
vc.log((function () { return typeof this; })());\nvc.log(delete Object.prototype);\nvar eval2 = 5;\nvc.log((function () { arguments = 6; return arguments; })());";
|
||||
let r = s.execute("script.run", &json!({"source": source})).unwrap();
|
||||
assert_eq!(r["log"], json!([1, 2, 8, 4, "object", false, 6]));
|
||||
// 'use strict' still makes the code strict mode code.
|
||||
let e = s.execute("script.run", &json!({"source": "#strict on\n'use strict';\nundeclared2 = 1;"})).unwrap_err().to_string();
|
||||
assert!(e.contains("ReferenceError"), "{e}");
|
||||
let e = s.execute("script.run", &json!({"source": "#strict on\n'use strict';\nwith ({}) {}"})).unwrap_err().to_string();
|
||||
assert_eq!(s.script_report().unwrap()["kind"], "syntax", "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_on_in_include_controls_expanded_run() {
|
||||
let base = folder("strict");
|
||||
write(&base.join("main/on.jsxinc"), "#strict on\nvar inc = 1;");
|
||||
write(&base.join("main/off.jsxinc"), "//@strict off\n");
|
||||
write(&base.join("main/writes.jsxinc"), &format!("{READ_ONLY}\n ro.x = 2;\n"));
|
||||
// An included file's line sets the checks for the script's own code too.
|
||||
let report = failed(&base, &format!("#include \"on.jsxinc\"\n{READ_ONLY}ro.x = 2;"));
|
||||
assert!(report["message"].as_str().unwrap().contains("it is read-only"), "{report}");
|
||||
assert_eq!(report["line"], 4, "{report}");
|
||||
assert!(report.get("file").is_none(), "{report}");
|
||||
// A write in an included file is reported at its own line.
|
||||
let report = failed(&base, "#strict on\n#include \"writes.jsxinc\"");
|
||||
assert!(report["message"].as_str().unwrap().contains("it is read-only"), "{report}");
|
||||
assert_eq!((report["line"].as_u64(), report["column"].as_u64()), (Some(4), Some(3)), "{report}");
|
||||
assert!(report["file"].as_str().unwrap().ends_with("writes.jsxinc"), "{report}");
|
||||
// The last line of the expanded run wins, wherever it is.
|
||||
let r = run(&base, &format!("#strict on\n#include \"off.jsxinc\"\n{READ_ONLY}ro.x = 2;\nvc.log(ro.x);")).unwrap();
|
||||
assert_eq!(r["log"], json!([1]));
|
||||
let report = failed(&base, &format!("#include \"off.jsxinc\"\n#strict on\n{READ_ONLY}ro.x = 2;"));
|
||||
assert!(report["message"].as_str().unwrap().contains("it is read-only"), "{report}");
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_mode_in_eval_file_is_scoped() {
|
||||
let base = folder("strict-eval");
|
||||
write(
|
||||
&base.join("main/on.jsx"),
|
||||
&format!(
|
||||
"#strict on\n{READ_ONLY}function w(o) {{ o.x = 2; }}\nvar inner;\ntry {{ ro.x = 2; inner = 'wrote'; }} catch (e) {{ inner = e.name; }}\n"
|
||||
),
|
||||
);
|
||||
write(&base.join("main/off.jsx"), &format!("#strict off\n{READ_ONLY}ro.x = 2;\n'off ran';"));
|
||||
write(&base.join("main/plain.jsx"), &format!("{READ_ONLY}ro.x = 2;\n'plain ran';"));
|
||||
// A file's own `#strict on` holds for its code, its functions included, and not the caller's.
|
||||
let r = run(
|
||||
&base,
|
||||
"$.evalFile('on.jsx');\nvar after;\ntry { ro.x = 3; after = 'ignored'; } catch (e) { after = e.name; }\nvar called;\ntry { w(ro); called = 'wrote'; } catch (e) { called = e.name; }\nvc.log([inner, after, called]);",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r["log"], json!([["TypeError", "ignored", "TypeError"]]));
|
||||
// A file without the line takes the caller's checks, and its own `#strict off` turns them off.
|
||||
let r = run(&base, "#strict on\nvc.log($.evalFile('off.jsx'));\nvar e1;\ntry { ro.x = 2; } catch (e) { e1 = e.name; }\nvc.log(e1);").unwrap();
|
||||
assert_eq!(r["log"], json!(["off ran", "TypeError"]), "the caller keeps its own checks");
|
||||
let report = failed(&base, "#strict on\n$.evalFile('plain.jsx');");
|
||||
assert!(report["message"].as_str().unwrap().contains("it is read-only"), "{report}");
|
||||
assert!(report["file"].as_str().unwrap().ends_with("plain.jsx"), "{report}");
|
||||
assert_eq!(report["line"], 3, "{report}");
|
||||
assert_eq!(run(&base, "vc.log($.evalFile('plain.jsx'));").unwrap()["log"], json!(["plain ran"]));
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user