google / google/closure-compiler
`typeof someFun()` erroneously collapses to `typeof globalVar` when inlined
- 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
Assessment
This issue has not been assessed yet.