diff --git a/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/AnnotatorScanner.java b/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/AnnotatorScanner.java index 233e8835e..927950db7 100644 --- a/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/AnnotatorScanner.java +++ b/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/AnnotatorScanner.java @@ -45,6 +45,8 @@ import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Context; +import edu.ucr.cs.riple.scanner.generatedcode.CodeAnnotationInfo; import edu.ucr.cs.riple.scanner.out.ClassRecord; import edu.ucr.cs.riple.scanner.out.ImpactedRegion; import edu.ucr.cs.riple.scanner.out.MethodRecord; @@ -90,12 +92,15 @@ public Description matchClass(ClassTree classTree, VisitorState visitorState) { if (!context.getConfig().isActive()) { return Description.NO_MATCH; } + Symbol.ClassSymbol classSymbol = ASTHelpers.getSymbol(classTree); + if (classSymbol == null || isInGeneratedClass(classSymbol, visitorState.context)) { + return Description.NO_MATCH; + } context .getConfig() .getSerializer() .serializeClassRecord( - new ClassRecord( - ASTHelpers.getSymbol(classTree), visitorState.getPath().getCompilationUnit())); + new ClassRecord(classSymbol, visitorState.getPath().getCompilationUnit())); return Description.NO_MATCH; } @@ -105,10 +110,14 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState if (!config.isActive()) { return Description.NO_MATCH; } + Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); + if (methodSymbol == null || isInGeneratedClass(methodSymbol, state.context)) { + return Description.NO_MATCH; + } config .getSerializer() .serializeImpactedRegionForMethod( - new ImpactedRegion(config, ASTHelpers.getSymbol(tree), state.getPath())); + new ImpactedRegion(config, methodSymbol, state.getPath())); return Description.NO_MATCH; } @@ -122,14 +131,15 @@ public Description matchNewClass(NewClassTree tree, VisitorState state) { if (methodSymbol == null) { throw new RuntimeException("not expecting unresolved method here"); } - if (methodSymbol.owner.enclClass().getSimpleName().isEmpty()) { + if (methodSymbol.owner.enclClass().getSimpleName().isEmpty() + || isInGeneratedClass(methodSymbol, state.context)) { // An anonymous class cannot declare its own constructors, so we do not need to serialize it. return Description.NO_MATCH; } config .getSerializer() .serializeImpactedRegionForMethod( - new ImpactedRegion(config, ASTHelpers.getSymbol(tree), state.getPath())); + new ImpactedRegion(config, methodSymbol, state.getPath())); return Description.NO_MATCH; } @@ -140,6 +150,9 @@ public Description matchMethod(MethodTree tree, VisitorState state) { return Description.NO_MATCH; } Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); + if (methodSymbol == null || isInGeneratedClass(methodSymbol, state.context)) { + return Description.NO_MATCH; + } serializeSymIfNonnull(methodSymbol); MethodRecord methodRecord = MethodRecord.findOrCreate(methodSymbol, context); methodRecord.findParent(state, context); @@ -159,8 +172,12 @@ public Description matchVariable(VariableTree tree, VisitorState state) { if (!context.getConfig().isActive()) { return Description.NO_MATCH; } + Symbol.VarSymbol varSymbol = ASTHelpers.getSymbol(tree); + if (varSymbol == null || isInGeneratedClass(varSymbol, state.context)) { + return Description.NO_MATCH; + } serializeSymIfField(ASTHelpers.getSymbol(tree.getInitializer()), state); - serializeSymIfNonnull(ASTHelpers.getSymbol(tree)); + serializeSymIfNonnull(varSymbol); return Description.NO_MATCH; } @@ -169,7 +186,11 @@ public Description matchIdentifier(IdentifierTree tree, VisitorState state) { if (!context.getConfig().isActive()) { return Description.NO_MATCH; } - serializeSymIfField(ASTHelpers.getSymbol(tree), state); + Symbol symbol = ASTHelpers.getSymbol(tree); + if (symbol == null || isInGeneratedClass(symbol, state.context)) { + return Description.NO_MATCH; + } + serializeSymIfField(symbol, state); return Description.NO_MATCH; } @@ -178,7 +199,11 @@ public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) if (!context.getConfig().isActive()) { return Description.NO_MATCH; } - serializeSymIfField(ASTHelpers.getSymbol(tree), state); + Symbol symbol = ASTHelpers.getSymbol(tree); + if (symbol == null || isInGeneratedClass(symbol, state.context)) { + return Description.NO_MATCH; + } + serializeSymIfField(symbol, state); return Description.NO_MATCH; } @@ -210,6 +235,9 @@ public Description matchMemberReference( serializeImpactedRegionForFunctionalInterface(config, memberReferenceTree, visitorState); if (memberReferenceTree instanceof JCTree.JCMemberReference) { Symbol calledMethod = ((JCTree.JCMemberReference) memberReferenceTree).sym; + if (calledMethod == null || isInGeneratedClass(calledMethod, visitorState.context)) { + return Description.NO_MATCH; + } if (calledMethod instanceof Symbol.MethodSymbol) { // serialize the called method: "bar()" context @@ -285,4 +313,16 @@ private static void serializeImpactedRegionForFunctionalInterface( .getSerializer() .serializeImpactedRegionForMethod(new ImpactedRegion(config, methodSym, state.getPath())); } + + /** + * Checks if the given symbol is in a generated class. + * + * @param symbol Given symbol. + * @param context Error prone context. + * @return True if the symbol is in a generated class, false otherwise. + */ + private boolean isInGeneratedClass(Symbol symbol, Context context) { + CodeAnnotationInfo codeAnnotationInfo = CodeAnnotationInfo.instance(context); + return codeAnnotationInfo.isInGeneratedClass(symbol); + } } diff --git a/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/SymbolUtil.java b/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/SymbolUtil.java index 6fde6fff6..d88e79865 100644 --- a/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/SymbolUtil.java +++ b/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/SymbolUtil.java @@ -256,4 +256,22 @@ public static Symbol.MethodSymbol getFunctionalInterfaceMethod(ExpressionTree tr Type funcInterfaceType = ((JCTree.JCFunctionalExpression) tree).type; return (Symbol.MethodSymbol) types.findDescriptorSymbol(funcInterfaceType.tsym); } + + /** + * NOTE: THIS SOURCE FILE IS COPIED AND MODIFIED FROM UBER NULLAWAY SOURCE CODE + * + *

A wrapper for {@link ASTHelpers#hasDirectAnnotationWithSimpleName(Symbol, String)} to avoid + * binary compatibility issues with new overloads in recent Error Prone versions. NullAway code + * should only use this method and not call the corresponding ASTHelpers methods directly. + * + *

TODO: delete this method and switch to ASTHelpers once we can require Error Prone 2.24.0 + * + * @param sym the symbol + * @param simpleName the simple name + * @return {@code true} iff the symbol has a direct annotation with the given simple name + */ + public static boolean hasDirectAnnotationWithSimpleName(Symbol sym, String simpleName) { + return ASTHelpers.hasDirectAnnotationWithSimpleName(sym, simpleName); + } } diff --git a/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/generatedcode/CodeAnnotationInfo.java b/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/generatedcode/CodeAnnotationInfo.java new file mode 100644 index 000000000..00f956738 --- /dev/null +++ b/annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/generatedcode/CodeAnnotationInfo.java @@ -0,0 +1,119 @@ +/* + * MIT License + * + * Copyright (c) 2024 Nima Karimipour + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package edu.ucr.cs.riple.scanner.generatedcode; + +import static edu.ucr.cs.riple.scanner.SymbolUtil.hasDirectAnnotationWithSimpleName; + +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.errorprone.util.ASTHelpers; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.util.Context; + +/** + * Provides APIs for querying whether code is annotated for nullness checking, and for related + * queries on what annotations are present on a class/method and/or on relevant enclosing scopes + * (i.e. enclosing classes or methods). Makes use of caching internally for performance. + * + *

NOTE: THIS SOURCE FILE IS COPIED AND MODIFIED FROM UBER NULLAWAY SOURCE CODE + */ +public final class CodeAnnotationInfo { + + private static final Context.Key ANNOTATION_INFO_KEY = new Context.Key<>(); + + private static final int MAX_CLASS_CACHE_SIZE = 200; + + private final Cache classCache = + CacheBuilder.newBuilder().maximumSize(MAX_CLASS_CACHE_SIZE).build(); + + private CodeAnnotationInfo() {} + + /** + * Get the CodeAnnotationInfo for the given javac context. We ensure there is one instance per + * context (as opposed to using static fields) to avoid memory leaks. + */ + public static CodeAnnotationInfo instance(Context context) { + CodeAnnotationInfo annotationInfo = context.get(ANNOTATION_INFO_KEY); + if (annotationInfo == null) { + annotationInfo = new CodeAnnotationInfo(); + context.put(ANNOTATION_INFO_KEY, annotationInfo); + } + return annotationInfo; + } + + /** + * Check if a symbol comes from generated code. + * + * @param symbol symbol for entity + * @return true if symbol represents an entity contained in a class annotated with + * {@code @Generated}; false otherwise + */ + public boolean isInGeneratedClass(Symbol symbol) { + Symbol.ClassSymbol classSymbol = + symbol instanceof Symbol.ClassSymbol + ? (Symbol.ClassSymbol) symbol + : ASTHelpers.enclosingClass(symbol); + if (classSymbol == null) { + return false; + } + Symbol.ClassSymbol outermostClassSymbol = get(classSymbol); + return hasDirectAnnotationWithSimpleName(outermostClassSymbol, "Generated"); + } + + /** + * Retrieve the (outermostClass, isNullMarked) record for a given class symbol. + * + *

This method is recursive, using the cache on the way up and populating it on the way down. + * + * @param classSymbol The class to query, possibly an inner class + * @return A record including the outermost class in which the given class is nested, as well as + * boolean flag noting whether it should be treated as nullness-annotated, taking into account + * annotations on enclosing classes, the containing package, and other NullAway configuration + * like annotated packages + */ + private Symbol.ClassSymbol get(Symbol.ClassSymbol classSymbol) { + Symbol.ClassSymbol record = classCache.getIfPresent(classSymbol); + if (record != null) { + return record; + } + if (classSymbol.getNestingKind().isNested()) { + Symbol owner = classSymbol.owner; + Preconditions.checkNotNull(owner, "Symbol.owner should only be null for modules!"); + Symbol.ClassSymbol enclosingClass = ASTHelpers.enclosingClass(classSymbol); + // enclosingClass can be null in weird cases like for array methods + if (enclosingClass != null) { + record = get(enclosingClass); + } + } + if (record == null) { + // We are already at the outermost class (we can find), so let's create a record for it + record = classSymbol; + } + classCache.put(classSymbol, record); + return record; + } +}