/*
* Copyright 2016 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 com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.Category;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.method.MethodMatchers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link FindIdentifiers}. */
@RunWith(JUnit4.class)
public class FindIdentifiersTest {
/** A {@link BugChecker} that prints all identifers in scope at a call to String.format(). */
@BugPattern(
name = "PrintIdents",
category = Category.ONE_OFF,
severity = SeverityLevel.ERROR,
summary = "Prints all identifers in scope at a call to String.format()"
)
public static class PrintIdents extends BugChecker implements MethodInvocationTreeMatcher {
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (MethodMatchers.staticMethod()
.onClass("java.lang.String")
.named("format")
.matches(tree, state)) {
return buildDescription(tree)
.setMessage(FindIdentifiers.findAllIdents(state).toString())
.build();
}
return Description.NO_MATCH;
}
}
@Test
public void findAllIdentsLocals() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" String s1 = \"\";",
" String s2 = \"\";",
" // BUG: Diagnostic contains: [s1, s2]",
" String.format(s1 + s2);",
" String s3 = \"\";",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsLocalsEnclosedTreeNode() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" String s1 = \"\";",
" String s2 = \"\";",
" if (true)",
" // BUG: Diagnostic contains: [s1, s2]",
" String.format(s1 + s2);",
" String s3 = \"\";",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsLocalsOuterScope() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" String s1 = \"\";",
" while (true) {",
" // BUG: Diagnostic contains: [s1]",
" String.format(s1);",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsParams() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt(String s1, String s2) {",
" // BUG: Diagnostic contains: [s1, s2]",
" String.format(s1 + s2);",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsMixedLocalsAndParams() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt(String s1) {",
" String s2 = s1;",
" while (true) {",
" String s3 = s1;",
" // BUG: Diagnostic contains: [s3, s2, s1]",
" String.format(s3 + s2 + s1);",
" String s4 = s1;",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsFields() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private static String s1;",
" private String s2;",
" private void doIt() {",
" // BUG: Diagnostic contains: [s1, s2]",
" String.format(s1 + s2);",
" }",
" private static void staticDoIt() {",
" // BUG: Diagnostic contains: [s1]",
" String.format(s1);",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInheritedFieldsSamePackage() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"pkg/Super.java",
"package pkg;",
"public class Super {",
" public String s1;",
" protected String s2;",
" String s3;",
" private String s4;",
" public static String s5;",
" protected static String s6;",
" static String s7;",
" private static String s8;",
"}")
.addSourceLines(
"pkg/Sub.java",
"package pkg;",
"public class Sub extends Super {",
" private void doIt() {",
" // BUG: Diagnostic contains: [s1, s2, s3, s5, s6, s7]",
" String.format(s1 + s2 + s3 + s5 + s6 + s7);",
" }",
" private static void doItStatically() {",
" // BUG: Diagnostic contains: [s5, s6, s7]",
" String.format(s5 + s6 + s7);",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInheritedFieldsDifferentPackage() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"pkg1/Super.java",
"package pkg1;",
"public class Super {",
" public String s1;",
" protected String s2;",
" String s3;",
" private String s4;",
" public static String s5;",
" protected static String s6;",
" static String s7;",
" private static String s8;",
"}")
.addSourceLines(
"pkg2/Sub.java",
"package pkg2;",
"import pkg1.Super;",
"public class Sub extends Super {",
" private void doIt() {",
" // BUG: Diagnostic contains: [s1, s2, s5, s6]",
" String.format(s1 + s2 + s5 + s6);",
" }",
" private static void doItStatically() {",
" // BUG: Diagnostic contains: [s5, s6]",
" String.format(s5 + s6);",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInheritedFieldsSameTopLevel() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Holder.java",
"public class Holder {",
" class Super {",
" private String s1;",
" }",
" class Sub extends Super {",
" private String s2;",
" private void doIt() {",
" // BUG: Diagnostic contains: [s2]",
" String.format(s2);",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsStaticNestedClass() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Outer.java",
"class Outer {",
" private String s1;",
" private static String s2;",
" static class Inner {",
" private String s3;",
" private static final String s4 = \"s4\";",
" private void doIt() {",
" // BUG: Diagnostic contains: [s3, s4, s2]",
" String.format(s3 + s4 + s2);",
" }",
" private static void doItStatically() {",
" // BUG: Diagnostic contains: [s4, s2]",
" String.format(s4 + s2);",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInnerClass() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Outer.java",
"class Outer {",
" private String s1;",
" private static String s2;",
" class Inner {",
" private String s3;",
" private static final String s4 = \"s4\";",
" class EvenMoreInner {",
" private String s5;",
" private static final String s6 = \"s6\";",
" private void doIt() {",
" // BUG: Diagnostic contains: [s5, s6, s3, s4, s1, s2]",
" String.format(s5 + s6 + s3 + s4 + s1 + s2);",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInnerClassWhereTopLevelExtends() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines("ToExtend.java", "class ToExtend {", " String s1;", "}")
.addSourceLines(
"Outer.java",
"class Outer extends ToExtend {",
" private String s2;",
" class Inner {",
" void doIt() {",
" // BUG: Diagnostic contains: [s2, s1]",
" String.format(s2 + s1);",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsMixedStaticAndNonStaticNestedClasses() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Outer.java",
"class Outer {",
" String s1;",
" static class Inner {",
" String s2;",
" class EvenMoreInner {",
" String s3;",
" void doIt() {",
" // BUG: Diagnostic contains: [s3, s2]",
" String.format(s3 + s2);",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsAnonymousClass() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" public String s1;",
" private void doIt(final String s2) {",
" String s3 = \"\";",
" String s4 = \"\";",
" new Thread(",
" new Runnable() {",
" @Override public void run() {",
" String s5 = \"\";",
" s5 = \"foo\";",
" // BUG: Diagnostic contains: [s5, s3, s2, s1]",
" String.format(s5 + s3 + s2 + s1);",
" }",
" }",
" ).start();",
" s4 = \"foo\";",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsLambda() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" public String s1;",
" private void doIt(final String s2) {",
" String s3 = \"\";",
" String s4 = \"\";",
" // BUG: Diagnostic contains: [s3, s2, s1]",
" new Thread(() -> String.format(s3 + s2 + s1)).start();",
" s4 = \"foo\";",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsExceptionParameter() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" public String s1;",
" private void doIt(final String s2) {",
" String s3 = \"\";",
" try {",
" s3 = s1;",
" } catch (Exception e) {",
" // BUG: Diagnostic contains: [e, s3, s2, s1]",
" String.format(e + s3 + s2 + s1);",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsLocalClass() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" String s1 = \"\";",
" final String s2 = \"\";",
" String s3 = \"\";",
" class Local {",
" public void doIt() {",
" // BUG: Diagnostic contains: [s1, s2]",
" String.format(s2 + s1);",
" }",
" }",
" s3 = \"foo\";",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsStaticInitializer() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" public static String s1;",
" public String s2;",
" static {",
" // BUG: Diagnostic contains: [s1]",
" String.format(s1);",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsStaticVariableInitializer() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" public static String s1;",
" public String s2;",
" // BUG: Diagnostic contains: [s1]",
" public static String s3 = String.format(s1);",
"}")
.doTest();
}
@Test
public void findAllIdentsExplicitConstructorInvocation() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private String s1;",
" private static String s2;",
" public Test(String s1) {",
" this.s1 = s1;",
" }",
" public Test() {",
" // BUG: Diagnostic contains: [s2]",
" this(String.format(s2));",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsExplicitConstructorInvocation2() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Super.java",
"class Super {",
" protected String s1;",
" protected static String s2;",
" public Super(String s1) {",
" this.s1 = s1;",
" }",
"}")
.addSourceLines(
"Sub.java",
"class Sub extends Super {",
" public Sub(String s3) {",
" // BUG: Diagnostic contains: [s3, s2]",
" super(String.format(s3 + s2));",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInterface() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"MyInterface.java", //
"interface MyInterface {",
" String EMPTY_STRING = \"\";",
"}")
.addSourceLines(
"MyImpl.java",
"class MyImpl implements MyInterface {",
" void doIt() {",
" // BUG: Diagnostic contains: [EMPTY_STRING]",
" String.format(EMPTY_STRING);",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsEnumConstant() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"MyEnum.java",
"enum MyEnum {",
" FOO,",
" BAR,",
" BAZ;",
" static class Nested {",
" void doIt() {",
" // BUG: Diagnostic contains: [FOO, BAR, BAZ]",
" String.format(FOO.toString() + BAR.toString() + BAZ.toString());",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsForLoop() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" void doIt() {",
" for (int i = 0; i < 10; i++) {",
" // BUG: Diagnostic contains: [i]",
" String.format(Integer.toString(i));",
" }",
" for (int j : new int[]{0, 1, 2}) {",
" // BUG: Diagnostic contains: [j]",
" String.format(Integer.toString(j));",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsTryWithResources() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"import java.io.*;",
"import java.nio.charset.StandardCharsets;",
"import java.nio.file.*;",
"class Test {",
" void doIt() {",
" try (",
" BufferedReader reader = Files.newBufferedReader(Paths.get(\"foo\"));",
" // BUG: Diagnostic contains: [reader]",
" InputStream is = new ByteArrayInputStream(String.format(reader.readLine()).getBytes(StandardCharsets.UTF_8))",
" ) {",
" // BUG: Diagnostic contains: [reader, is]",
" String.format(reader.readLine() + is.toString());",
" } catch (IOException e) {",
" // BUG: Diagnostic contains: [e]",
" String.format(e.toString());",
" }",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsStaticImport() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"Test.java",
"import static java.nio.charset.StandardCharsets.UTF_8;",
"class Test {",
" void doIt() {",
" // BUG: Diagnostic contains: [UTF_8]",
" String.format(UTF_8.toString());",
" }",
"}")
.doTest();
}
@Test
public void findAllIdentsInheritedStaticImport() {
CompilationTestHelper.newInstance(PrintIdents.class, getClass())
.addSourceLines(
"pkg/MyInterface.java",
"package pkg;",
"public interface MyInterface {",
" String EMPTY = \"\";",
"}")
.addSourceLines(
"pkg/Super.java",
"package pkg;",
"public class Super {",
" public static final String FOO = \"foo\";",
"}")
.addSourceLines(
"pkg/Impl.java",
"package pkg;",
"public class Impl extends Super implements MyInterface {}")
.addSourceLines(
"Test.java",
"import static pkg.Impl.EMPTY;",
"import static pkg.Impl.FOO;",
"public class Test {",
" void doIt() {",
" // BUG: Diagnostic contains: [EMPTY, FOO]",
" String.format(EMPTY + FOO);",
" }",
"}")
.doTest();
}
/**
* A {@link BugChecker} that prints all unused variables in scope at a call to String.format().
*/
@BugPattern(
name = "PrintUnusedVariables",
category = Category.ONE_OFF,
severity = SeverityLevel.ERROR,
summary = "Prints all unused variabled in scope at a call to String.format()"
)
public static class PrintUnusedVariables extends BugChecker
implements MethodInvocationTreeMatcher {
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (MethodMatchers.staticMethod()
.onClass("java.lang.String")
.named("format")
.matches(tree, state)) {
return buildDescription(tree)
.setMessage(FindIdentifiers.findUnusedIdentifiers(state).toString())
.build();
}
return Description.NO_MATCH;
}
}
@Test
public void returnsVariable_findUnusedVariables_whenVariableUnusedInBlock() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" String a = null;",
" // BUG: Diagnostic contains: [a]",
" String.format(a);",
" }",
"}")
.doTest();
}
@Test
public void ignoresVariable_findUnusedVariables_whenVariableUsedAsArgumentInBlock() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" void test(String arg) {}",
" private void doIt() {",
" String a = null;",
" test(a);",
" // BUG: Diagnostic contains: []",
" String.format(null);",
" }",
"}")
.doTest();
}
@Test
public void ignoresVariable_findUnusedVariables_whenVariableUsedInExpressionInBlock() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" void test(String arg) {}",
" private void doIt() {",
" String a = null;",
" if (a == null) {}",
" // BUG: Diagnostic contains: []",
" String.format(null);",
" }",
"}")
.doTest();
}
@Test
public void returnsVariable_findUnusedVariables_whenVariableUsedLaterInBlock() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" void test(String arg) {}",
" private void doIt() {",
" String a = null;",
" // BUG: Diagnostic contains: [a]",
" String.format(null);",
" test(a);",
" }",
"}")
.doTest();
}
@Test
public void returnsVariable_findUnusedVariables_whenVariableUnusedButSameNameAsField() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" String a = null;",
" void test(String arg) {}",
" private void doIt() {",
" test(a);",
" String a = null;",
" // BUG: Diagnostic contains: [a]",
" String.format(null);",
" }",
"}")
.doTest();
}
@Test
public void returnsVariable_findUnusedVariables_whenVariableDefinedInEnhancedFor() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" for(String a : new String[] {}) {",
" // BUG: Diagnostic contains: [a]",
" String.format(a);",
" }",
" }",
"}")
.doTest();
}
@Test
public void returnsVariable_findUnusedVariables_whenLaterUseInAssignment() {
CompilationTestHelper.newInstance(PrintUnusedVariables.class, getClass())
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" String a = null;",
" // BUG: Diagnostic contains: [a]",
" String b = String.format(a);",
" }",
"}")
.doTest();
}
/** A {@link BugChecker} that prints all fields in receiver class on method invocations. */
@BugPattern(
name = "PrintFields",
category = Category.ONE_OFF,
severity = SeverityLevel.ERROR,
summary = "Prints all fields in receivers of method invocations"
)
public static class PrintFields extends BugChecker implements MethodInvocationTreeMatcher {
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
MethodSymbol symbol = ASTHelpers.getSymbol(tree);
Type receiverType = symbol.owner.asType();
List<VarSymbol> fields = FindIdentifiers.findAllFields(receiverType, state);
return buildDescription(tree).setMessage(fields.toString()).build();
}
}
@Test
public void findsStaticAndInstanceFields_findAllFields() {
CompilationTestHelper.newInstance(PrintFields.class, getClass())
.addSourceLines(
"Reference.java",
"class Reference {",
" static String staticField;",
" String instanceField;",
" static void test() {}",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" // BUG: Diagnostic contains: [staticField, instanceField]",
" Reference.test();",
" }",
"}")
.doTest();
}
@Test
public void findsInheritedStaticAndInstanceFields_findAllFields() {
CompilationTestHelper.newInstance(PrintFields.class, getClass())
.addSourceLines(
"Super.java",
"class Super {",
" static String staticField;",
" String instanceField;",
"}")
.addSourceLines(
"Reference.java", "class Reference extends Super {", " static void test() {}", "}")
.addSourceLines(
"Test.java",
"class Test {",
" private void doIt() {",
" // BUG: Diagnostic contains: [staticField, instanceField]",
" Reference.test();",
" }",
"}")
.doTest();
}
}