google / google/closure-compiler

`typeof someFun()` erroneously collapses to `typeof globalVar` when inlined

Open
#3,498 6 comments 0 reactions 0 assignees View on GitHub
internal-issue-created triage-done
Dominant language
JavaScript
Stars
7.7k
Forks
1.2k
Avg merge
2d 12h
Merged PRs (30d)
6

Description

This is quite the corner case. It produces code that behaves differently depending on whether a function call is inlined or not.

Reproducer:
```js
// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name default.js
// @formatting pretty_print
// ==/ClosureCompiler==

function testInlined() {
return thisVarDoesNotExist;
}

/**
* @noinline
*/
function testNotInlined() {
return thisVarDoesNotExist;
}

function test(f) {
try {
console.log(f());
} catch (e) {
console.log(e);
}
}

test(() => typeof thisVarDoesNotExist);
test(() => typeof testInlined());
test(() => typeof testNotInlined());
```
[link to the online compiler](https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%2540formatting%2520pretty_print%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250Afunction%2520testInlined()%2520%257B%250A%2520%2520return%2520thisVarDoesNotExist%253B%250A%257D%250A%250A%252F**%2520%250A%2520*%2520%2540noinline%250A%2520*%252F%250Afunction%2520testNotInlined()%2520%257B%250A%2520%2520return%2520thisVarDoesNotExist%253B%250A%257D%250A%250Afunction%2520test(f)%2520%257B%250A%2520%2520try%2520%257B%250A%2520%2520%2520%2520console.log(f())%253B%250A%2520%2520%257D%2520catch%2520(e)%2520%257B%250A%2520%2520%2520%2520console.log(e)%253B%250A%2520%2520%257D%250A%257D%250A%250Atest(()%2520%253D%253E%2520typeof%2520thisVarDoesNotExist)%253B%250Atest(()%2520%253D%253E%2520typeof%2520testInlined())%253B%250Atest(()%2520%253D%253E%2520typeof%2520testNotInlined())%253B%250A)

The (pretty-printed) compiled code is:
```js
function a() {
return thisVarDoesNotExist;
}
function b(c) {
try {
console.log(c());
} catch (d) {
console.log(d);
}
}
b(function() {
return typeof thisVarDoesNotExist;
});
b(function() {
return typeof thisVarDoesNotExist;
});
b(function() {
return typeof a();
});
```

The original code prints:
```
undefined
ReferenceError: thisVarDoesNotExist is not defined
ReferenceError: thisVarDoesNotExist is not defined
```
as it should, but the compiled code prints:
```
undefined
undefined
ReferenceError: thisVarDoesNotExist is not defined
```
The problem is that, when inlining `testInlined()` in the expression `typeof testInlined()`, the compiler forgot to protect the reference to the non-existent global reference to `thisVarDoesNotExist`, resulting in
```js
typeof thisVarDoesNotExist
```
This changes the semantics of the evaluation, because a `typeof` whose direct argument is a reference to a non-existent global variable returns `"undefined"` instead of throwing a `ReferenceError`.

A correct translation would be to protect the inlined argument to `typeof` with:
```js
typeof (0, thisVarDoesNotExist)
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.