diff options
-rw-r--r-- | src/tools/skylark/java/com/google/devtools/skylark/skylint/ControlFlowChecker.java | 36 | ||||
-rw-r--r-- | src/tools/skylark/javatests/com/google/devtools/skylark/skylint/ControlFlowCheckerTest.java | 83 |
2 files changed, 103 insertions, 16 deletions
diff --git a/src/tools/skylark/java/com/google/devtools/skylark/skylint/ControlFlowChecker.java b/src/tools/skylark/java/com/google/devtools/skylark/skylint/ControlFlowChecker.java index 821749ca3d..a75a4905f8 100644 --- a/src/tools/skylark/java/com/google/devtools/skylark/skylint/ControlFlowChecker.java +++ b/src/tools/skylark/java/com/google/devtools/skylark/skylint/ControlFlowChecker.java @@ -18,6 +18,7 @@ import com.google.common.base.Preconditions; import com.google.devtools.build.lib.syntax.BuildFileAST; import com.google.devtools.build.lib.syntax.Expression; import com.google.devtools.build.lib.syntax.ExpressionStatement; +import com.google.devtools.build.lib.syntax.FlowStatement; import com.google.devtools.build.lib.syntax.ForStatement; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.FunctionDefStatement; @@ -64,6 +65,22 @@ public class ControlFlowChecker extends SyntaxTreeVisitor { } @Override + public void visitBlock(List<Statement> statements) { + if (cfi == null) { + super.visitBlock(statements); + return; + } + boolean alreadyReported = false; + for (Statement stmt : statements) { + if (!cfi.reachable && !alreadyReported) { + issues.add(new Issue("unreachable statement", stmt.getLocation())); + alreadyReported = true; + } + visit(stmt); + } + } + + @Override public void visit(IfStatement node) { if (cfi == null) { return; @@ -92,9 +109,16 @@ public class ControlFlowChecker extends SyntaxTreeVisitor { } @Override + public void visit(FlowStatement node) { + Preconditions.checkNotNull(cfi); + cfi.reachable = false; + } + + @Override public void visit(ReturnStatement node) { // Should be rejected by parser, but we may have been fed a bad AST. Preconditions.checkState(cfi != null, "AST has illegal top-level return statement"); + cfi.reachable = false; cfi.returnsAlwaysExplicitly = true; if (node.getReturnExpression() != null) { cfi.hasReturnWithValue = true; @@ -110,6 +134,7 @@ public class ControlFlowChecker extends SyntaxTreeVisitor { return; } if (isFail(node.getExpression())) { + cfi.reachable = false; cfi.returnsAlwaysExplicitly = true; } } @@ -169,16 +194,19 @@ public class ControlFlowChecker extends SyntaxTreeVisitor { } private static class ControlFlowInfo { + private boolean reachable; private boolean hasReturnWithValue; private boolean hasReturnWithoutValue; private boolean returnsAlwaysExplicitly; private final LinkedHashSet<Return> returnStatementsWithoutValue; private ControlFlowInfo( + boolean reachable, boolean hasReturnWithValue, boolean hasReturnWithoutValue, boolean returnsAlwaysExplicitly, LinkedHashSet<Return> returnStatementsWithoutValue) { + this.reachable = reachable; this.hasReturnWithValue = hasReturnWithValue; this.hasReturnWithoutValue = hasReturnWithoutValue; this.returnsAlwaysExplicitly = returnsAlwaysExplicitly; @@ -187,12 +215,13 @@ public class ControlFlowChecker extends SyntaxTreeVisitor { /** Create a CFI corresponding to an entry point in the control-flow graph. */ static ControlFlowInfo entry() { - return new ControlFlowInfo(false, false, false, new LinkedHashSet<>()); + return new ControlFlowInfo(true, false, false, false, new LinkedHashSet<>()); } /** Creates a copy of a CFI, including the {@code returnStatementsWithoutValue} collection. */ static ControlFlowInfo copy(ControlFlowInfo existing) { return new ControlFlowInfo( + existing.reachable, existing.hasReturnWithValue, existing.hasReturnWithoutValue, existing.returnsAlwaysExplicitly, @@ -201,9 +230,10 @@ public class ControlFlowChecker extends SyntaxTreeVisitor { /** Joins the CFIs for several alternative paths together. */ static ControlFlowInfo join(List<ControlFlowInfo> infos) { - ControlFlowInfo result = new ControlFlowInfo( - false, false, true, new LinkedHashSet<>()); + ControlFlowInfo result = + new ControlFlowInfo(false, false, false, true, new LinkedHashSet<>()); for (ControlFlowInfo info : infos) { + result.reachable |= info.reachable; result.hasReturnWithValue |= info.hasReturnWithValue; result.hasReturnWithoutValue |= info.hasReturnWithoutValue; result.returnsAlwaysExplicitly &= info.returnsAlwaysExplicitly; diff --git a/src/tools/skylark/javatests/com/google/devtools/skylark/skylint/ControlFlowCheckerTest.java b/src/tools/skylark/javatests/com/google/devtools/skylark/skylint/ControlFlowCheckerTest.java index 12d1125366..b6297a423c 100644 --- a/src/tools/skylark/javatests/com/google/devtools/skylark/skylint/ControlFlowCheckerTest.java +++ b/src/tools/skylark/javatests/com/google/devtools/skylark/skylint/ControlFlowCheckerTest.java @@ -154,6 +154,62 @@ public class ControlFlowCheckerTest { } @Test + public void testUnreachableAfterIf() throws Exception { + String messages = + findIssues( + "def some_function(x):", + " if x:", + " return", + " else:", + " fail('fail')", + " print('This line is unreachable')") + .toString(); + Truth.assertThat(messages).contains(":6:3: unreachable statement"); + } + + @Test + public void testNoUnreachableDuplicates() throws Exception { + List<Issue> messages = + findIssues( + "def some_function():", + " return", + " print('unreachable1')", + " print('unreachable2')"); + Truth.assertThat(messages).hasSize(1); + } + + @Test + public void testUnreachableAfterBreakContinue() throws Exception { + String messages = + findIssues( + "def some_function(x):", + " for y in x:", + " if y:", + " break", + " else:", + " continue", + " print('unreachable')") + .toString(); + Truth.assertThat(messages).contains(":7:5: unreachable statement"); + } + + @Test + public void testReachableStatements() throws Exception { + Truth.assertThat( + findIssues( + "def some_function(x):", + " if x:", + " return", + " for y in []:", + " if y:", + " continue", + " else:", + " fail('fail')", + " return")) + .isEmpty(); + } + + @Test public void testIfBeforeReturn() throws Exception { Truth.assertThat( findIssues( @@ -211,19 +267,20 @@ public class ControlFlowCheckerTest { " else:", " return y")) .isEmpty(); - Truth.assertThat( - findIssues( - "def f(x,y):", - " if x:", - " return x", - " else:", - " return x", - " # from now on everything's unreachable", - " print('bar')", - " if y:", - " return x", - " # no else branch but doesn't matter since it's unreachable")) - .isEmpty(); + List<Issue> issues = + findIssues( + "def f(x,y):", + " if x:", + " return x", + " else:", + " return x", + " # from now on everything's unreachable", + " print('bar')", + " if y:", + " return x", + " # no else branch but doesn't matter since it's unreachable"); + Truth.assertThat(issues).hasSize(1); + Truth.assertThat(issues.toString()).contains(":7:3: unreachable statement"); } @Test |