/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
/**
* Tests for {@link CheckUnreachableCode}.
*
*/
public final class CheckUnreachableCodeTest extends CompilerTestCase {
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new CombinedCompilerPass(compiler,
new CheckUnreachableCode(compiler));
}
public void testCorrectSimple() {
testSame("var x");
testSame("var x = 1");
testSame("var x = 1; x = 2;");
testSame("if (x) { var x = 1 }");
testSame("if (x) { var x = 1 } else { var y = 2 }");
testSame("while(x) {}");
}
public void testIncorrectSimple() {
assertUnreachable("function f() { return; x=1; }");
assertUnreachable("function f() { return; x=1; x=1; }");
assertUnreachable("function f() { return; var x = 1; }");
}
public void testCorrectIfReturns() {
testSame("function f() { if (x) { return } }");
testSame("function f() { if (x) { return } return }");
testSame("function f() { if (x) { if (y) { return } } else { return }}");
testSame("function f()" +
"{ if (x) { if (y) { return } return } else { return }}");
}
public void testInCorrectIfReturns() {
assertUnreachable(
"function f() { if (x) { return } else { return } return }");
}
public void testCorrectSwitchReturn() {
testSame("function f() { switch(x) { default: return; case 1: x++; }}");
testSame("function f() {" +
"switch(x) { default: return; case 1: x++; } return }");
testSame("function f() {" +
"switch(x) { default: return; case 1: return; }}");
testSame("function f() {" +
"switch(x) { case 1: return; } return }");
testSame("function f() {" +
"switch(x) { case 1: case 2: return; } return }");
testSame("function f() {" +
"switch(x) { case 1: return; case 2: return; } return }");
testSame("function f() {" +
"switch(x) { case 1 : return; case 2: return; } return }");
}
public void testInCorrectSwitchReturn() {
assertUnreachable("function f() {" +
"switch(x) { default: return; case 1: return; } return }");
assertUnreachable("function f() {" +
"switch(x) { default: return; return; case 1: return; } }");
}
public void testCorrectLoopBreaksAndContinues() {
testSame("while(1) { foo(); break }");
testSame("while(1) { foo(); continue }");
testSame("for(;;) { foo(); break }");
testSame("for(;;) { foo(); continue }");
testSame("for(;;) { if (x) { break } }");
testSame("for(;;) { if (x) { continue } }");
testSame("do { foo(); continue} while(1)");
}
public void testInCorrectLoopBreaksAndContinues() {
assertUnreachable("while(1) { foo(); break; bar()}");
assertUnreachable("while(1) { foo(); continue; bar() }");
assertUnreachable("for(;;) { foo(); break; bar() }");
assertUnreachable("for(;;) { foo(); continue; bar() }");
assertUnreachable("for(;;) { if (x) { break; bar() } }");
assertUnreachable("for(;;) { if (x) { continue; bar() } }");
assertUnreachable("do { foo(); continue; bar()} while(1)");
}
public void testUncheckedWhileInDo() {
assertUnreachable("do { foo(); break} while(1)");
}
public void testUncheckedConditionInFor() {
assertUnreachable("for(var x = 0; x < 100; x++) { break };");
}
public void testFunctionDeclaration() {
// functions are not in our CFG.
testSame("function f() { return; function ff() { }}");
}
public void testVarDeclaration() {
assertUnreachable("function f() { return; var x = 1 }");
// I think the user should fix this as well.
assertUnreachable("function f() { return; var x }");
}
public void testReachableTryCatchFinally() {
testSame("try { } finally { }");
testSame("try { foo(); } finally { bar() } ");
testSame("try { foo() } finally { bar() }");
testSame("try { foo(); } catch (e) {e()} finally { bar() }");
testSame("try { foo() } catch (e) {e()} finally { bar() }");
testSame("try { foo() } catch (e) { throw e; } finally { bar() }");
}
public void testUnreachableCatch() {
assertUnreachable("try { var x = 0 } catch (e) { }");
assertUnreachable("try { } catch (e) { throw e; }");
}
public void testSpuriousBreak() {
testSame("switch (x) { default: throw x; break; }");
}
public void testInstanceOfThrowsException() {
testSame("function f() {try { if (value instanceof type) return true; } " +
"catch (e) { }}");
}
public void testFalseCondition() {
assertUnreachable("if(false) { }");
assertUnreachable("if(0) { }");
}
public void testUnreachableLoop() {
assertUnreachable("while(false) {}");
}
public void testInfiniteLoop() {
testSame("while (true) { foo(); break; }");
// TODO(user): Have a infinite loop warning instead.
assertUnreachable("while(true) {} foo()");
}
public void testSuppression() {
assertUnreachable("if(false) { }");
testSame(
"/** @fileoverview\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
"if(false) { }");
testSame(
"/** @fileoverview\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
"function f() { if(false) { } }");
testSame(
"/**\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
"function f() { if(false) { } }");
assertUnreachable(
"/**\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
"function f() { if(false) { } }\n" +
"function g() { if(false) { } }\n");
testSame(
"/**\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
"function f() {\n" +
" function g() { if(false) { } }\n" +
" if(false) { } }\n");
assertUnreachable(
"function f() {\n" +
" /**\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
" function g() { if(false) { } }\n" +
" if(false) { } }\n");
testSame(
"function f() {\n" +
" /**\n" +
" * @suppress {uselessCode}\n" +
" */\n" +
" function g() { if(false) { } }\n" +
"}\n");
}
public void testES6FeaturesInIfExpression() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
// class X{} always eval to true by toBoolean();
assertUnreachable("if(!class {}) x = 1;");
assertUnreachable("if(!class A{}) x = 1;");
// Template string with substitution and tagged template will be evaluated
// to UNKNOWN. Template string with definite length (without substitution)
// will be evaled like a normal string.
assertUnreachable("if(!`tempLit`) {x = 1;}");
assertUnreachable("if(``) {x = 1;}");
testSame("if(`temp${sub}Lit`) {x = 1;} else {x = 2;}");
testSame("if(`${sub}`) {x = 1;} else {x = 2;}");
testSame("if(tagged`tempLit`) {x = 1;} else {x = 2;}");
// Functions are truthy.
assertUnreachable("if(()=>true) {x = 1;} else {x = 2;}");
}
public void testES6FeaturesInTryCatch() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
assertUnreachable("try { let x = 1; } catch(e) {}");
assertUnreachable("try { const x = 1; } catch(e) {}");
assertUnreachable("try {()=>42;} catch(e) {}");
assertUnreachable("try {function *gen(){};} catch(e) {}");
// Assumed tagged template may throw exception.
testSame("try {tagged`temp`;} catch(e) {}");
assertUnreachable("try { var obj = {a(){}};} catch(e) {}");
testSame("try { var obj = {a(){}}; obj.a();} catch(e) {}");
}
public void testCorrectForOfBreakAndContinues() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
testSame("for(x of [1, 2, 3]) {foo();}");
testSame("for(x of [1, 2, 3]) {foo(); break;}");
testSame("for(x of [1, 2, 3]) {foo(); continue;}");
}
public void testInCorrectForOfBreakAndContinues() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
assertUnreachable("for(x of [1, 2, 3]) {foo(); break; bar();}");
assertUnreachable("for(x of [1, 2, 3]) {foo(); continue; bar();}");
assertUnreachable("for(x of [1, 2, 3]) {if(x) {break; bar();}}");
assertUnreachable("for(x of [1, 2, 3]) {if(x) {continue; bar();}}");
}
public void testForLoopsEs6() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
assertUnreachable("for(;;) {if(x) {continue; bar();}}");
assertUnreachable("for(x in y) {if(x) {continue; bar();}}");
}
public void testReturnsInShorthandFunctionOfObjLit() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
testSame(LINE_JOINER.join(
"var obj = {",
" f() { ",
" switch(x) { ",
" default: return; ",
" case 1: x++; ",
" }",
" }",
"}"));
assertUnreachable(LINE_JOINER.join(
"var obj = {f() {",
" switch(x) { ",
" default: return; ",
" case 1: return; ",
" }",
" return; ",
"}}"));
testSame("var obj = {f() { if(x) {return;} else {return; }}}");
assertUnreachable(LINE_JOINER.join(
"var obj = {f() { ",
" if(x) {",
" return;",
" } else {",
" return;",
" }",
" return; ",
"}}"));
}
public void testObjLit() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
assertUnreachable("var a = {c(){if(true){return;}x = 1;}};");
}
public void testClass() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
testSame("class C{func(){}}");
assertUnreachable("class C{func(){if (true){return;} else {return;}}}");
assertUnreachable("class C{func(){if (true){return;} x = 1;}}");
testSame("var C = class{func(){}}");
testSame("let C = class{func(){}}");
testSame("var C; C = class{func(){}}");
testSame("let C; C = class{func(){}}");
assertUnreachable("var C = class{func(){if (true){return;} x = 1;}}");
}
private void assertUnreachable(String js) {
test(js, js, null, CheckUnreachableCode.UNREACHABLE_CODE);
}
}