Skip to content
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

assignment in switch statement corrupts program #445

Closed
aeschli opened this issue Sep 3, 2019 · 6 comments
Closed

assignment in switch statement corrupts program #445

aeschli opened this issue Sep 3, 2019 · 6 comments
Labels

Comments

@aeschli
Copy link

aeschli commented Sep 3, 2019

Terser 4.2.1

The test shows that the outputs are not the same.

The issue is in
https://github.com/aeschli/sample-terser-bug/blob/662548260936142a48e9c0d2ad65e69001bb296d/json.pack.js#L75

The switch statement also does an assignment to n.

  • This corrupts the program as n is already used for the length of the input string of the scanner.
  • The assignment to n is unnecessary

The corresponding line is
https://github.com/aeschli/sample-terser-bug/blob/662548260936142a48e9c0d2ad65e69001bb296d/json.js#L117

A workaround is to set hoist_funs to true

@aeschli
Copy link
Author

aeschli commented Sep 3, 2019

What also fixes the issue is to avoid the re-declaration of ch at https://github.com/aeschli/sample-terser-bug/blob/662548260936142a48e9c0d2ad65e69001bb296d/json.js#L143
renaming ch to ch2 .

@fabiosantoscode
Copy link
Collaborator

Hey there!

Thank you for your report. Could you try to come up with a minimal reproduction of the bug? A whole repository is a bit big to find bugs inside, and it's probably possible to reproduce this in a minimal way.

@aeschli
Copy link
Author

aeschli commented Sep 4, 2019

I tried, but it's very difficult as any change (e.g. simplification) resulted in a different result on the minifier.

I suspect it has to do with the variable shadowing. const ch is is defined in an outer and an inner scope. #335 is a similar issue.

@fabiosantoscode
Copy link
Collaborator

This is probably unrelated to that issue since you can only reproduce that with a catch block specifically, which has a tricky implementation due to historical reasons (old Uglify code is var-centric and the catch block binding is the only block scoped variable in oldjs, and to make that trickier there's a nasty IE8 bug which changes scoping rules for that binding)

If simple shadowed variables were the issue it would've been fixed years ago.

@fabiosantoscode
Copy link
Collaborator

I've managed to remove most of the input code and create a single JS file which is executable and outputs either PASS or FAIL, which is what we use in most of our tests:

const leak = () => null                                    
                                                           
function createScanner(text) {                             
    let pos = 0;                                           
    let len = text.length;                                 
    function scanString() {                                
        while (true) {                                     
            if (pos >= len) {                              
                leak(text)                                 
                break;                                     
            }                                              
            let ch = text.charCodeAt(pos++);               
            if (ch === 92 /* backslash */) {               
                ch = text.charCodeAt(pos++);               
                switch (ch) {                              
                    case 34 /* doubleQuote */:             
                        break;                             
                    case "never-reached":                  
                        const ch = Math.random();          
                        leak(ch)                           
                        break;                             
                    default:                               
                        throw 'err'                        
                }                                          
                continue;                                  
            }                                              
            pos++;                                         
        }                                                  
    }                                                      
    function scanNext() {                                  
        if (pos >= len) return 17                          
        let code = text.charCodeAt(pos);                   
        pos++                                              
        scanString()                                       
        return 10                                          
    }                                                      
    return [() => pos, scanNext]                           
}                                                          
                                                           
const [scan, getPos] = createScanner('"\\"')               
scan()                                                     
scan()                                                     
                                                           
console.log(getPos() === 3 ? 'PASS' : 'FAIL')              

Eventually the purpose of creating the perfectly minimal reproduction of an error is to use that directly in the compress tests. However this is still a tad big, I'm thinking I can make this smaller still by ditching some of the state in it.

@fabiosantoscode
Copy link
Collaborator

I got this further down. I think I'm ready to devise a fix.

const leak = () => null                           
                                                  
function scan() {                                 
    let len = leak('something');                  
    let ch = 0;                                   
    ch = 123;                                     
    switch (ch) {                                 
        case "never-reached":                     
            const ch = leak();                    
            leak(ch);                             
    }                                             
    return len === 123 ? 'FAIL' : 'PASS'          
}                                                 
                                                  
console.log(scan())                               

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants