Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize InjectScopeOrQualifierAnnotationRetention to MissingRuntimeRetention #4439

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,85 +14,117 @@

package com.google.errorprone.bugpatterns.inject;

import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.bugpatterns.inject.ElementPredicates.doesNotHaveRuntimeRetention;
import static com.google.errorprone.bugpatterns.inject.ElementPredicates.hasSourceRetention;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.InjectMatchers.DAGGER_MAP_KEY_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.GUICE_BINDING_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.GUICE_MAP_KEY_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.GUICE_SCOPE_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.JAVAX_QUALIFIER_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.JAVAX_SCOPE_ANNOTATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.kindIs;
import static com.google.errorprone.util.ASTHelpers.annotationsAmong;
import static com.google.errorprone.util.ASTHelpers.getDeclaredSymbol;
import static com.sun.source.tree.Tree.Kind.ANNOTATION_TYPE;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.InjectMatchers;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.util.Name;
import java.lang.annotation.Retention;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;

// TODO(b/180081278): Rename this check to MissingRuntimeRetention
/**
* @author sgoldfeder@google.com (Steven Goldfeder)
*/
@BugPattern(
name = "InjectScopeOrQualifierAnnotationRetention",
name = "MissingRuntimeRetention",
altNames = "InjectScopeOrQualifierAnnotationRetention",
summary = "Scoping and qualifier annotations must have runtime retention.",
severity = ERROR)
public class ScopeOrQualifierAnnotationRetention extends BugChecker implements ClassTreeMatcher {
public class MissingRuntimeRetention extends BugChecker implements ClassTreeMatcher {

private static final String RETENTION_ANNOTATION = "java.lang.annotation.Retention";

/** Matches classes that are qualifier, scope annotation or map binding keys. */
private static final Matcher<ClassTree> SCOPE_OR_QUALIFIER_ANNOTATION_MATCHER =
allOf(
kindIs(ANNOTATION_TYPE),
anyOf(
hasAnnotation(GUICE_SCOPE_ANNOTATION),
hasAnnotation(JAVAX_SCOPE_ANNOTATION),
hasAnnotation(GUICE_BINDING_ANNOTATION),
hasAnnotation(JAVAX_QUALIFIER_ANNOTATION),
hasAnnotation(GUICE_MAP_KEY_ANNOTATION),
hasAnnotation(DAGGER_MAP_KEY_ANNOTATION)));
private static final Supplier<ImmutableSet<Name>> INJECT_ANNOTATIONS =
VisitorState.memoize(
state ->
Stream.of(
GUICE_SCOPE_ANNOTATION,
JAVAX_SCOPE_ANNOTATION,
GUICE_BINDING_ANNOTATION,
JAVAX_QUALIFIER_ANNOTATION,
GUICE_MAP_KEY_ANNOTATION,
DAGGER_MAP_KEY_ANNOTATION)
.map(state::binaryNameFromClassname)
.collect(toImmutableSet()));

private static final Supplier<ImmutableSet<Name>> ANNOTATIONS =
VisitorState.memoize(
state ->
Streams.concat(
Stream.of("com.google.apps.framework.annotations.ProcessorAnnotation")
.map(state::binaryNameFromClassname),
INJECT_ANNOTATIONS.get(state).stream())
.collect(toImmutableSet()));

@Override
public final Description matchClass(ClassTree classTree, VisitorState state) {
if (SCOPE_OR_QUALIFIER_ANNOTATION_MATCHER.matches(classTree, state)) {
ClassSymbol classSymbol = ASTHelpers.getSymbol(classTree);
if (hasSourceRetention(classSymbol)) {
return describe(classTree, state, ASTHelpers.getAnnotation(classSymbol, Retention.class));
}
if (!classTree.getKind().equals(ANNOTATION_TYPE)) {
return NO_MATCH;
}
Set<Name> annotations =
annotationsAmong(getDeclaredSymbol(classTree), ANNOTATIONS.get(state), state);
if (annotations.isEmpty()) {
return NO_MATCH;
}
ClassSymbol classSymbol = ASTHelpers.getSymbol(classTree);
if (!doesNotHaveRuntimeRetention(classSymbol)) {
return NO_MATCH;
}
if (!Collections.disjoint(annotations, INJECT_ANNOTATIONS.get(state))
&& exemptInjectAnnotation(state, classSymbol)) {
return NO_MATCH;
}
return describe(classTree, state, ASTHelpers.getAnnotation(classSymbol, Retention.class));
}

// TODO(glorioso): This is a poor hack to exclude android apps that are more likely to not
// have reflective DI than normal java. JSR spec still says the annotations should be
// runtime-retained, but this reduces the instances that are flagged.
if (!state.isAndroidCompatible() && doesNotHaveRuntimeRetention(classSymbol)) {
// Is this in a dagger component?
ClassTree outer = ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class);
if (outer != null
&& allOf(InjectMatchers.IS_DAGGER_COMPONENT_OR_MODULE).matches(outer, state)) {
return Description.NO_MATCH;
}
return describe(classTree, state, ASTHelpers.getAnnotation(classSymbol, Retention.class));
}
private static boolean exemptInjectAnnotation(VisitorState state, ClassSymbol classSymbol) {
// TODO(glorioso): This is a poor hack to exclude android apps that are more likely to not
// have reflective DI than normal java. JSR spec still says the annotations should be
// runtime-retained, but this reduces the instances that are flagged.
if (hasSourceRetention(classSymbol)) {
return false;
}
if (state.isAndroidCompatible()) {
return true;
}
// Is this in a dagger component?
ClassTree outer = ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class);
if (outer != null
&& allOf(InjectMatchers.IS_DAGGER_COMPONENT_OR_MODULE).matches(outer, state)) {
return true;
}
return Description.NO_MATCH;
return false;
}

private Description describe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,14 +499,14 @@
import com.google.errorprone.bugpatterns.inject.JavaxInjectOnAbstractMethod;
import com.google.errorprone.bugpatterns.inject.JavaxInjectOnFinalField;
import com.google.errorprone.bugpatterns.inject.MisplacedScopeAnnotations;
import com.google.errorprone.bugpatterns.inject.MissingRuntimeRetention;
import com.google.errorprone.bugpatterns.inject.MoreThanOneInjectableConstructor;
import com.google.errorprone.bugpatterns.inject.MoreThanOneQualifier;
import com.google.errorprone.bugpatterns.inject.MoreThanOneScopeAnnotationOnClass;
import com.google.errorprone.bugpatterns.inject.OverlappingQualifierAndScopeAnnotation;
import com.google.errorprone.bugpatterns.inject.QualifierOrScopeOnInjectMethod;
import com.google.errorprone.bugpatterns.inject.QualifierWithTypeUse;
import com.google.errorprone.bugpatterns.inject.ScopeAnnotationOnInterfaceOrAbstractClass;
import com.google.errorprone.bugpatterns.inject.ScopeOrQualifierAnnotationRetention;
import com.google.errorprone.bugpatterns.inject.dagger.AndroidInjectionBeforeSuper;
import com.google.errorprone.bugpatterns.inject.dagger.EmptySetMultibindingContributions;
import com.google.errorprone.bugpatterns.inject.dagger.PrivateConstructorForNoninstantiableModule;
Expand Down Expand Up @@ -1179,6 +1179,7 @@ public static ScannerSupplier warningChecks() {
MethodCanBeStatic.class,
MissingBraces.class,
MissingDefault.class,
MissingRuntimeRetention.class,
MixedArrayDimensions.class,
MockitoDoSetup.class,
MoreThanOneQualifier.class,
Expand All @@ -1205,7 +1206,6 @@ public static ScannerSupplier warningChecks() {
ReturnMissingNullable.class,
ReturnsNullCollection.class,
ScopeOnModule.class,
ScopeOrQualifierAnnotationRetention.class,
StaticOrDefaultInterfaceMethod.class,
StaticQualifiedUsingExpression.class,
StringFormatWithLiteral.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,22 @@
* @author sgoldfeder@google.com (Steven Goldfeder)
*/
@RunWith(JUnit4.class)
public class ScopeOrQualifierAnnotationRetentionTest {
public class MissingRuntimeRetentionTest {

private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
ScopeOrQualifierAnnotationRetention.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(MissingRuntimeRetention.class, getClass());

private final CompilationTestHelper compilationHelper =
CompilationTestHelper.newInstance(ScopeOrQualifierAnnotationRetention.class, getClass());
CompilationTestHelper.newInstance(MissingRuntimeRetention.class, getClass());

@Test
public void positiveCase() {
compilationHelper
.addSourceFile("ScopeOrQualifierAnnotationRetentionPositiveCases.java")
.doTest();
compilationHelper.addSourceFile("MissingRuntimeRetentionPositiveCases.java").doTest();
}

@Test
public void negativeCase() {
compilationHelper
.addSourceFile("ScopeOrQualifierAnnotationRetentionNegativeCases.java")
.doTest();
compilationHelper.addSourceFile("MissingRuntimeRetentionNegativeCases.java").doTest();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
/**
* @author sgoldfeder@google.com (Steven Goldfeder)
*/
public class ScopeOrQualifierAnnotationRetentionNegativeCases {
public class MissingRuntimeRetentionNegativeCases {
/** A scoping (@Scope) annotation with runtime retention */
@Scope
@Target({TYPE, METHOD})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
/**
* @author sgoldfeder@google.com (Steven Goldfeder)
*/
public class ScopeOrQualifierAnnotationRetentionPositiveCases {
public class MissingRuntimeRetentionPositiveCases {
/** A scoping (@Scope) annotation with SOURCE retention */
@Scope
@Target({TYPE, METHOD})
Expand Down
Loading