quickjs-oxide 0.4.1

A safe Rust JavaScript engine targeting semantic parity with QuickJS
Documentation
use super::*;
use crate::engine::test_support::test_context;

#[test]
fn debug_metadata_tracks_operator_tail_call_and_root_call_sites() {
    let source = "(function outer(){ return (function inner(){ return 1n + 1; })(); })()";
    let (runtime, context) = test_context();
    let root = context.compile_with_filename(source, "<cmdline>").unwrap();
    let outer = runtime.test_child_function_bytecode(&root, 0).unwrap();
    let inner = runtime.test_child_function_bytecode(&outer, 0).unwrap();

    let root_code = runtime.test_function_code(&root).unwrap();
    let outer_code = runtime.test_function_code(&outer).unwrap();
    let inner_code = runtime.test_function_code(&inner).unwrap();
    let root_call = root_code
        .iter()
        .rposition(|instruction| matches!(instruction, Instruction::Call(0)))
        .unwrap();
    let outer_call = outer_code
        .iter()
        .rposition(|instruction| matches!(instruction, Instruction::Call(0)))
        .unwrap();
    let inner_add = inner_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::Add))
        .unwrap();

    assert_eq!(
        runtime
            .test_function_debug_location(&inner, Some(inner_add))
            .unwrap(),
        Some((
            JsString::from_static("<cmdline>"),
            crate::source::LineColumn::new(0, 55)
        ))
    );
    assert_eq!(
        runtime
            .test_function_debug_location(&outer, Some(outer_call))
            .unwrap(),
        Some((
            JsString::from_static("<cmdline>"),
            crate::source::LineColumn::new(0, 19)
        ))
    );
    assert_eq!(
        runtime
            .test_function_debug_location(&root, Some(root_call))
            .unwrap(),
        Some((
            JsString::from_static("<cmdline>"),
            crate::source::LineColumn::new(0, 68)
        ))
    );
    assert_eq!(runtime.test_function_debug_source(&root).unwrap(), None);
    assert_eq!(
        runtime.test_function_debug_source(&outer).unwrap(),
        Some(b"function outer(){ return (function inner(){ return 1n + 1; })(); }".to_vec())
    );
    assert_eq!(
        runtime.test_function_debug_source(&inner).unwrap(),
        Some(b"function inner(){ return 1n + 1; }".to_vec())
    );
}

#[test]
fn ordinary_assignment_inherits_last_rhs_marker_and_var_initializer_marks_equal() {
    let (runtime, context) = test_context();
    let assignment_source = "\"use strict\"; missing = 1";
    let root = context
        .compile_with_filename(assignment_source, "globals.js")
        .unwrap();
    let code = runtime.test_function_code(&root).unwrap();
    let dup = code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::Dup))
        .unwrap();
    assert!(matches!(code.get(dup + 1), Some(Instruction::PutVar(_))));
    let lhs = u32::try_from(assignment_source.find("missing").unwrap()).unwrap();
    let expected = Some((
        JsString::from_static("globals.js"),
        crate::source::LineColumn::new(0, lhs),
    ));
    assert_eq!(
        runtime
            .test_function_debug_location(&root, Some(dup))
            .unwrap(),
        expected
    );
    assert_eq!(
        runtime
            .test_function_debug_location(&root, Some(dup + 1))
            .unwrap(),
        expected
    );

    let identifier_rhs_source = "(function(){ \"use strict\"; var y=1; missing = y; })";
    let identifier_rhs_root = context
        .compile_with_filename(identifier_rhs_source, "globals.js")
        .unwrap();
    let identifier_rhs = runtime
        .test_child_function_bytecode(&identifier_rhs_root, 0)
        .unwrap();
    let identifier_rhs_code = runtime.test_function_code(&identifier_rhs).unwrap();
    let identifier_put = identifier_rhs_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::PutVar(_)))
        .unwrap();
    let rhs_identifier = u32::try_from(identifier_rhs_source.rfind("y;").unwrap()).unwrap();
    assert_eq!(
        runtime
            .test_function_debug_location(&identifier_rhs, Some(identifier_put))
            .unwrap(),
        Some((
            JsString::from_static("globals.js"),
            crate::source::LineColumn::new(0, rhs_identifier)
        ))
    );

    let operator_rhs_source = "\"use strict\"; missing = 1 + 2";
    let operator_rhs = context
        .compile_with_filename(operator_rhs_source, "globals.js")
        .unwrap();
    let operator_rhs_code = runtime.test_function_code(&operator_rhs).unwrap();
    let operator_put = operator_rhs_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::PutVar(_)))
        .unwrap();
    let plus = u32::try_from(operator_rhs_source.find('+').unwrap()).unwrap();
    assert_eq!(
        runtime
            .test_function_debug_location(&operator_rhs, Some(operator_put))
            .unwrap(),
        Some((
            JsString::from_static("globals.js"),
            crate::source::LineColumn::new(0, plus)
        ))
    );

    let declaration_source = "(function(){ var x = 1; return x; })";
    let declaration_root = context
        .compile_with_filename(declaration_source, "globals.js")
        .unwrap();
    let declaration = runtime
        .test_child_function_bytecode(&declaration_root, 0)
        .unwrap();
    let declaration_code = runtime.test_function_code(&declaration).unwrap();
    let put_local = declaration_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::PutLocal(_)))
        .unwrap();
    let equal = u32::try_from(declaration_source.find("= 1").unwrap()).unwrap();
    assert_eq!(
        runtime
            .test_function_debug_location(&declaration, Some(put_local))
            .unwrap(),
        Some((
            JsString::from_static("globals.js"),
            crate::source::LineColumn::new(0, equal)
        ))
    );
}

#[test]
fn call_and_construct_debug_sites_follow_quickjs_tokens() {
    let (runtime, context) = test_context();

    let call_source = "Error()";
    let call_root = context
        .compile_with_filename(call_source, "calls.js")
        .unwrap();
    let call_code = runtime.test_function_code(&call_root).unwrap();
    let call_pc = call_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::Call(0)))
        .unwrap();
    assert_eq!(
        runtime
            .test_function_debug_location(&call_root, Some(call_pc))
            .unwrap(),
        Some((
            JsString::from_static("calls.js"),
            crate::source::LineColumn::new(0, 5)
        ))
    );

    let construct_source = "(function f(){ return new Error('x'); })";
    let construct_root = context
        .compile_with_filename(construct_source, "construct.js")
        .unwrap();
    let constructor = runtime
        .test_child_function_bytecode(&construct_root, 0)
        .unwrap();
    let constructor_code = runtime.test_function_code(&constructor).unwrap();
    let construct_pc = constructor_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::Construct(1)))
        .unwrap();
    let left_paren = construct_source.find("Error(").unwrap() + "Error".len();
    assert_eq!(
        runtime
            .test_function_debug_location(&constructor, Some(construct_pc))
            .unwrap(),
        Some((
            JsString::from_static("construct.js"),
            crate::source::LineColumn::new(0, u32::try_from(left_paren).unwrap())
        ))
    );

    let no_parens_source = "(function f(){ return new Error; })";
    let no_parens_root = context
        .compile_with_filename(no_parens_source, "construct.js")
        .unwrap();
    let no_parens_constructor = runtime
        .test_child_function_bytecode(&no_parens_root, 0)
        .unwrap();
    let no_parens_code = runtime.test_function_code(&no_parens_constructor).unwrap();
    let no_parens_pc = no_parens_code
        .iter()
        .position(|instruction| matches!(instruction, Instruction::Construct(0)))
        .unwrap();
    let semicolon = no_parens_source.find("Error;").unwrap() + "Error".len();
    assert_eq!(
        runtime
            .test_function_debug_location(&no_parens_constructor, Some(no_parens_pc))
            .unwrap(),
        Some((
            JsString::from_static("construct.js"),
            crate::source::LineColumn::new(0, u32::try_from(semicolon).unwrap())
        ))
    );
}

#[test]
fn primitive_and_function_primaries_do_not_emit_source_markers() {
    let (runtime, context) = test_context();
    for source in [
        "(1)",
        "('x')",
        "(null)",
        "(true)",
        "(this)",
        "(function(){})",
        "(!1)",
        "(void 1)",
        "(typeof 1)",
        "(true && false)",
        "(1 ? 2 : 3)",
        "(1, 2)",
    ] {
        let root = context.compile_with_filename(source, "primary.js").unwrap();
        for pc in 0..runtime.test_function_code(&root).unwrap().len() {
            assert_eq!(
                runtime
                    .test_function_debug_location(&root, Some(pc))
                    .unwrap(),
                Some((
                    JsString::from_static("primary.js"),
                    crate::source::LineColumn::new(0, 0)
                )),
                "source: {source}, pc: {pc}"
            );
        }
    }
}

#[test]
fn root_and_ordinary_function_names_stay_distinct() {
    let (runtime, context) = test_context();
    let root = context.compile("(function(){ return 1; })").unwrap();
    let child = runtime.test_child_function_bytecode(&root, 0).unwrap();

    assert_eq!(
        runtime.test_function_name(&root).unwrap(),
        Some(JsString::from_static("<eval>"))
    );
    assert_eq!(runtime.test_function_name(&child).unwrap(), None);
    assert_eq!(
        runtime.test_function_debug_location(&root, None).unwrap(),
        Some((
            JsString::from_static(super::DEFAULT_EVAL_FILENAME),
            crate::source::LineColumn::new(0, 0)
        ))
    );
}

#[test]
fn runtime_strip_mode_controls_debug_payload() {
    let (runtime, context) = test_context();

    runtime
        .set_debug_info_mode(DebugInfoMode::StripSource)
        .expect("set runtime configuration");
    let root = context
        .compile_with_filename("(function(){})", "strip-source-unique.js")
        .unwrap();
    let child = runtime.test_child_function_bytecode(&root, 0).unwrap();
    assert_eq!(
        runtime.test_function_debug_location(&child, None).unwrap(),
        Some((
            JsString::from_static("strip-source-unique.js"),
            crate::source::LineColumn::new(0, 1),
        ))
    );
    assert_eq!(runtime.test_function_debug_source(&child).unwrap(), None);

    runtime
        .set_debug_info_mode(DebugInfoMode::StripDebug)
        .expect("set runtime configuration");
    let root = context
        .compile_with_filename("(function(){})", "strip-debug-unique.js")
        .unwrap();
    let child = runtime.test_child_function_bytecode(&root, 0).unwrap();
    assert_eq!(
        runtime.test_function_debug_location(&child, None).unwrap(),
        None
    );
    assert_eq!(runtime.test_function_debug_source(&child).unwrap(), None);
}