Skip to content

dead_branch_remover turns indirect eval into direct eval #2465

@mischnic

Description

@mischnic

Describe the bug

With eval, it makes a difference whether the callee is the literal identifier eval or just some expressions that is === eval. This is currently incorrectly optimized

Input code

const X = {
    run() {
        console.log(eval("this") === this);
        console.log((0, eval)("this") === globalThis);
    },
};

X.run();

Prints true, true

Config

Details
use swc_common::{
    chain, comments::SingleThreadedComments, sync::Lrc, FileName, Globals, Mark, SourceMap,
};
use swc_ecma_ast::Module;
use swc_ecma_codegen::{text_writer::JsWriter, Config, Emitter};
use swc_ecma_parser::{lexer::Lexer, EsConfig, PResult, Parser, StringInput, Syntax};
use swc_ecma_transforms::{
    compat::reserved_words::reserved_words, fixer, helpers, hygiene,
    optimization::simplify::dead_branch_remover, resolver::resolver_with_mark,
};
use swc_ecma_visit::FoldWith;

fn main() {
    let cm = Lrc::<SourceMap>::default();
    let src = r#"
    const X = {
        run() {
            console.log(eval("this") === this);
            console.log((0, eval)("this") === globalThis);
        },
    };
    
    X.run();
"#;
    let (module, comments) = parse(src, "test.js", &cm).unwrap();

    swc_common::GLOBALS.set(&Globals::new(), || {
        helpers::HELPERS.set(&helpers::Helpers::default(), || {
            let global_mark = Mark::fresh(Mark::root());
            let module = module.fold_with(&mut resolver_with_mark(global_mark));

            let transform = &mut dead_branch_remover();
            let module = module.fold_with(transform);

            let module = module.fold_with(&mut chain!(
                reserved_words(),
                hygiene(),
                fixer(Some(&comments))
            ));

            let code = emit(&module, &comments, cm);
            println!("{}", code);
        });
    });
}

fn parse(
    code: &str,
    filename: &str,
    cm: &Lrc<SourceMap>,
) -> PResult<(Module, SingleThreadedComments)> {
    let source_file = cm.new_source_file(FileName::Real(filename.into()), code.into());
    let comments = SingleThreadedComments::default();

    let lexer = Lexer::new(
        Syntax::Es(EsConfig {
            jsx: true,
            ..Default::default()
        }),
        // Syntax::Typescript(TsConfig {
        //     tsx: true,
        //     ..Default::default()
        // }),
        Default::default(),
        StringInput::from(&*source_file),
        Some(&comments),
    );
    let mut parser = Parser::new_from(lexer);
    match parser.parse_module() {
        Err(err) => Err(err),
        Ok(module) => Ok((module, comments)),
    }
}

fn emit(module: &Module, comments: &SingleThreadedComments, cm: Lrc<SourceMap>) -> String {
    let mut buf = vec![];
    {
        let writer = Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None));
        let config = Config { minify: false };
        let mut emitter = Emitter {
            cfg: config,
            comments: Some(&comments),
            cm,
            wr: writer,
        };
        emitter.emit_module(&module).unwrap();
    }

    String::from_utf8(buf).unwrap()
}

Current output

const X = {
    run() {
        console.log(eval("this") === this);
        console.log(eval("this") === globalThis);
    },
};

X.run();

Prints true, false

Expected behavior
Output should be the same as the input

Version
98a18e3

Additional context
parcel-bundler/parcel#7116

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions