/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.errorprone.util;
import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.SwitchTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.SwitchTree;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/** {@link Reachability}Test. */
@RunWith(Parameterized.class)
public class ReachabilityTest {
/** Reports an error if the first case in a switch falls through to the second. */
@BugPattern(name = "FirstCaseFallsThrough", category = JDK, summary = "", severity = ERROR)
public static class FirstCaseFallsThrough extends BugChecker implements SwitchTreeMatcher {
@Override
public Description matchSwitch(SwitchTree tree, VisitorState state) {
if (tree.getCases().size() != 2 || tree.getCases().get(0).getStatements().isEmpty()) {
return NO_MATCH;
}
return Reachability.canCompleteNormally(
Iterables.getLast(tree.getCases().get(0).getStatements()))
? describeMatch(tree.getCases().get(1))
: NO_MATCH;
}
}
private final String[] lines;
public ReachabilityTest(String[] lines) {
this.lines = lines;
}
@Test
public void test() {
CompilationTestHelper.newInstance(FirstCaseFallsThrough.class, getClass())
.addSourceLines(
"in/Test.java",
"import java.io.*;",
"import java.nio.file.*;",
"class Test {",
" void f(int x) {",
" switch (x) {",
" case 1:",
Joiner.on('\n').join(lines),
" default:",
" break;",
" }",
" }",
"}")
.doTest();
}
@Parameters
public static List<Object[]> parameters() {
String[][] parameters = {
{
"int a = 1;", //
"int b = 2;",
"break;",
},
{
"System.err.println();", //
"// BUG: Diagnostic contains:",
},
{
"int a = 1;",
"int b = 2;",
"class L {}",
";;",
"assert false;",
"label: System.err.println();",
"// BUG: Diagnostic contains:",
},
{
"if (true) {", //
"} else {",
" break;",
"}",
"// BUG: Diagnostic contains:",
},
{
"if (true) {", //
" break;",
"}",
"// BUG: Diagnostic contains:",
},
{
"if (true) {", //
" break;",
"} else {",
"}",
"// BUG: Diagnostic contains:",
},
{
"if (true) {", //
" break;",
"} else {",
" break;",
"}",
},
{
"switch (42) {", //
" case 0:",
" case 1:",
" case 2:",
"}",
"// BUG: Diagnostic contains:",
},
{
"switch (42) {", //
"}",
"// BUG: Diagnostic contains:",
},
{
"switch (42) {",
" case 0:",
" break;",
" case 1:",
"}",
"// BUG: Diagnostic contains:",
},
{
"switch (42) {", //
" default:",
" break;",
"}",
"// BUG: Diagnostic contains:",
},
{
"switch (42) {", //
" default:",
" return;",
"}",
},
{
"while (true) {", //
"}",
},
{
"while (true) {", //
" break;",
"}",
"// BUG: Diagnostic contains:",
},
{
"while (true) {", //
" return;",
"}",
},
{
"while (x == 0) {}", //
"// BUG: Diagnostic contains:",
},
{
"loop: do {", //
" continue loop;",
"} while (x == 0);",
"// BUG: Diagnostic contains:",
},
{
"loop: do {", //
" continue loop;",
"} while (true);",
},
{
"int i = 1;",
"loop: do {",
" i++;",
" continue loop;",
"} while (i == 0);",
"// BUG: Diagnostic contains:",
},
{
"try {",
" Files.readAllBytes(Paths.get(\"file\"));",
" return;",
"} catch (IOException e) {",
" return;",
"}",
},
{
"try {",
" try {",
" Files.readAllBytes(Paths.get(\"file\"));",
" return;",
" } catch (NullPointerException e) {",
" return;",
" }",
"} catch (IOException e) {",
" return;",
"}",
},
{
"try {",
" Files.readAllBytes(Paths.get(\"file\"));",
" return;",
"} catch (IOException e) {",
" return;",
"} finally {",
"}",
},
{
"try {", //
" //",
"} catch (Throwable t) {",
" return;",
"} finally {",
" return;",
"}",
},
{
"try {", //
" return;",
"} catch (Throwable t) {",
" //",
"} finally {",
" return;",
"}",
},
{
"int y = 0;",
"while (true) {",
" if (y++ > 10) {",
" return;",
" }",
" if (y-- < 10) {",
" return;",
" }",
"}",
},
{
"int y = 0;",
"while (true) {",
" do {",
" switch (y) {",
" case 0:",
" continue;", // continue target is do/while, not switch or outer while
" }",
" } while (y > 0);",
" break;",
"}",
"// BUG: Diagnostic contains:",
},
{
"try {",
" if (Files.readAllBytes(Paths.get(\"file\")) != null) {}",
" return;",
"} catch (IOException e) {",
"}",
"// BUG: Diagnostic contains:",
},
{
"try {",
" Files.readAllBytes(Paths.get(\"file\"));",
"} catch (IOException e) {",
" throw new IOError(e);",
"}",
"// BUG: Diagnostic contains:",
},
{
"try {",
" Files.readAllBytes(Paths.get(\"file\"));",
" return;",
"} catch (IOException e) {",
" throw new IOError(e);",
"}",
},
{
"throw new AssertionError();",
},
{
"for (;;) {}",
},
{
"System.exit(1);", //
},
{
"{", //
" System.exit(1);",
" throw new AssertionError();",
"}",
},
{
"l:", //
"do {",
" break l;",
"} while (true);",
"System.err.println();",
"// BUG: Diagnostic contains:",
},
};
return Arrays.stream(parameters).map(x -> new Object[] {x}).collect(toList());
}
}