boa
boa copied to clipboard
Inconsistent function vs object assignment in indirect eval
Description
When executing an indirect eval call ((1,eval)) that contains both a function declaration and a subsequent variable assignment with the same name, Boa exhibits inconsistent behavior that may not conform to ECMAScript standards.
Reproduction Code
t = []
var s1 = `
function core() { t.push(1) }
core.prototype.a = function () { t.push(2) }
core.prototype.b = function () { t.push(3) }
`;
var s2 = `
function core() { t.push(1) }
core.prototype.a = function () { t.push(2) }
core.prototype.b = function () { t.push(3) }
var core = new core();
`;
var s3 = `
function core() { t.push(1) }
var core = new core();
`;
function run_ctx(s) {
(1,eval)(s);
}
function test() {
run_ctx(s1);
var core1 = new core()
run_ctx(s2);
var core2 = core
run_ctx(s3);
var core3 = core
return [core1,core2,core3]
}
test()
Expected Behavior
According to ECMAScript standards, when a function declaration is followed by a variable assignment that overwrites it with an instance of itself (e.g., var core = new core()), the final global binding should be the object instance, not the function.
All three elements in the returned array should be object instances.
And in most of my javascript environments, it is like:
"[object Object],[object Object],[object Object]"
Actual Behavior
v0.20.0
"[object Object],function () { [native code] },[object Object]"
git master
"[object Object],function () { t.push(3) },[object Object]"
The second element remains a function instead of becoming an object instance as expected.
Questions
- Which behavior is conformant with the ECMAScript standard?
- Is this a bug or a specification edge case?
Environment
- Boa version: 0.20.0 and git master
- Platform: rust 1.88
Rust source code
use boa_engine::{Context, Source, JsValue, JsError, js_string};
use std::{env, fs, path::Path, error::Error};
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().skip(1).collect();
if args.is_empty() {
eprintln!("Usage: {} <file1.js> [file2.js ...]", env::args().next().unwrap());
return Ok(());
}
let mut context = Context::default();
let mut last_result = None;
for file in args {
let js_code = fs::read_to_string(&file)?;
match context.eval(Source::from_bytes(&js_code).with_path(Path::new(&file))) {
Ok(result) => {
last_result = Some(result);
},
Err(err) => {
return Ok(()); // Exit on first error
}
}
}
if let Some(result) = last_result {
let result_str = result.to_string(&mut context)?;
println!("{:?}", result_str);
}
Ok(())
}