Skip to content

Commit d73a0ae

Browse files
committed
Remove old support for reading annotations from bytecode
1 parent d008a79 commit d73a0ae

6 files changed

Lines changed: 66 additions & 213 deletions

File tree

nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java

Lines changed: 50 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,7 @@
4444
import com.sun.tools.javac.code.Attribute;
4545
import com.sun.tools.javac.code.Flags;
4646
import com.sun.tools.javac.code.Symbol;
47-
import com.sun.tools.javac.code.TargetType;
4847
import com.sun.tools.javac.code.Type;
49-
import com.sun.tools.javac.code.TypeAnnotationPosition;
50-
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
5148
import com.sun.tools.javac.code.TypeTag;
5249
import com.sun.tools.javac.code.Types;
5350
import com.sun.tools.javac.tree.JCTree;
@@ -62,7 +59,6 @@
6259
import java.util.stream.Stream;
6360
import javax.lang.model.element.AnnotationMirror;
6461
import javax.lang.model.element.AnnotationValue;
65-
import javax.lang.model.element.ElementKind;
6662
import javax.lang.model.element.ExecutableElement;
6763
import javax.lang.model.type.TypeKind;
6864
import org.checkerframework.nullaway.javacutil.AnnotationUtils;
@@ -195,35 +191,6 @@ public static Stream<? extends AnnotationMirror> getAllAnnotations(Symbol symbol
195191
return Stream.concat(symbol.getAnnotationMirrors().stream(), typeUseAnnotations);
196192
}
197193

198-
/**
199-
* Check if any direct annotation a symbol matches a given predicate. Includes code for backward
200-
* compatibility with older JDKs where javac did not place type-use annotations on Symbols from
201-
* bytecodes.
202-
*
203-
* @param symbol the symbol
204-
* @param predicate the predicate to match annotation names against
205-
* @return true if any annotation on the symbol matches the predicate, false otherwise
206-
*/
207-
public static boolean hasAnyAnnotationMatchingBackCompat(
208-
Symbol symbol, Predicate<String> predicate) {
209-
if (hasAnyAnnotationMatching(symbol, predicate)) {
210-
return true;
211-
}
212-
// to handle bytecodes, also check direct type-use annotations stored in attributes
213-
Symbol typeAnnotationOwner =
214-
symbol.getKind().equals(ElementKind.PARAMETER) ? symbol.owner : symbol;
215-
for (Attribute.TypeCompound typeCompound : typeAnnotationOwner.getRawTypeAttributes()) {
216-
if (!targetTypeMatches(symbol, typeCompound.position)
217-
|| !isDirectTypeUseAnnotation(typeCompound, symbol)) {
218-
continue;
219-
}
220-
if (predicate.test(typeCompound.getAnnotationType().toString())) {
221-
return true;
222-
}
223-
}
224-
return false;
225-
}
226-
227194
/**
228195
* Check if any direct annotation a symbol matches a given predicate.
229196
*
@@ -238,14 +205,24 @@ public static boolean hasAnyAnnotationMatching(Symbol symbol, Predicate<String>
238205
return true;
239206
}
240207
}
241-
// check for type use annotations. For MethodSymbols, look on the return type
242-
Type annotatedType =
243-
symbol instanceof Symbol.MethodSymbol methodSymbol
244-
? methodSymbol.getReturnType()
245-
: symbol.type;
246-
for (AnnotationMirror annotationMirror : annotatedType.getAnnotationMirrors()) {
247-
if (predicate.test(annotationMirror.getAnnotationType().toString())) {
248-
return true;
208+
// Check for type-use annotations. For methods, look on the return type and its enclosing types,
209+
// since NullAway treats an annotation before the outer class of a nested return type as
210+
// applying to the full return type.
211+
if (symbol instanceof Symbol.MethodSymbol methodSymbol) {
212+
for (Type currentType = methodSymbol.getReturnType();
213+
currentType != null && !currentType.hasTag(TypeTag.NONE);
214+
currentType = currentType.getEnclosingType()) {
215+
for (AnnotationMirror annotationMirror : currentType.getAnnotationMirrors()) {
216+
if (predicate.test(annotationMirror.getAnnotationType().toString())) {
217+
return true;
218+
}
219+
}
220+
}
221+
} else {
222+
for (AnnotationMirror annotationMirror : symbol.type.getAnnotationMirrors()) {
223+
if (predicate.test(annotationMirror.getAnnotationType().toString())) {
224+
return true;
225+
}
249226
}
250227
}
251228
return false;
@@ -354,18 +331,11 @@ public static boolean hasAnyAnnotationMatching(Symbol symbol, Predicate<String>
354331
public static Stream<? extends AnnotationMirror> getAllAnnotationsForParameter(
355332
Symbol.MethodSymbol symbol, int paramInd) {
356333
Symbol.VarSymbol varSymbol = symbol.getParameters().get(paramInd);
357-
// On modern javac versions, type-use annotations are attached directly to the parameter's
358-
// type. Keep reading raw attributes as well for backward compatibility with older javac
359-
// behavior.
360-
Stream<? extends AnnotationMirror> typeUseAnnotations =
334+
Type parameterType = symbol.type.getParameterTypes().get(paramInd);
335+
Stream<Attribute.TypeCompound> typeUseAnnotations =
361336
Stream.concat(
362-
varSymbol.type.getAnnotationMirrors().stream(),
363-
symbol.getRawTypeAttributes().stream()
364-
.filter(
365-
t ->
366-
t.position.type.equals(TargetType.METHOD_FORMAL_PARAMETER)
367-
&& t.position.parameter_index == paramInd
368-
&& NullabilityUtil.isDirectTypeUseAnnotation(t, symbol)))
337+
getTypeUseAnnotationsIncludingEnclosingTypes(varSymbol.type),
338+
getTypeUseAnnotationsIncludingEnclosingTypes(parameterType))
369339
.distinct();
370340
return Stream.concat(varSymbol.getAnnotationMirrors().stream(), typeUseAnnotations);
371341
}
@@ -375,137 +345,30 @@ public static Stream<? extends AnnotationMirror> getAllAnnotationsForParameter(
375345
* arguments, wildcards, etc.)
376346
*/
377347
public static Stream<Attribute.TypeCompound> getTypeUseAnnotations(Symbol symbol) {
378-
return getTypeUseAnnotations(symbol, /* onlyDirect= */ true);
379-
}
380-
381-
/**
382-
* Gets the type use annotations on a symbol
383-
*
384-
* @param symbol the symbol
385-
* @param onlyDirect if true, only return annotations that are directly on the type, not on
386-
* components of the type (type arguments, wildcards, array contents, etc.)
387-
* @return the type use annotations on the symbol
388-
*/
389-
private static Stream<Attribute.TypeCompound> getTypeUseAnnotations(
390-
Symbol symbol, boolean onlyDirect) {
391-
// Adapted from Error Prone's MoreAnnotations class:
392-
// https://github.com/google/error-prone/blob/5f71110374e63f3c35b661f538295fa15b5c1db2/check_api/src/main/java/com/google/errorprone/util/MoreAnnotations.java#L84-L91
393-
Symbol typeAnnotationOwner =
394-
symbol.getKind().equals(ElementKind.PARAMETER) ? symbol.owner : symbol;
395-
Stream<Attribute.TypeCompound> rawTypeAttributes =
396-
typeAnnotationOwner.getRawTypeAttributes().stream();
348+
Type annotatedType =
349+
symbol instanceof Symbol.MethodSymbol methodSymbol
350+
? methodSymbol.getReturnType()
351+
: symbol.type;
397352
if (symbol instanceof Symbol.MethodSymbol) {
398-
// for methods, we want annotations on the return type
399-
return rawTypeAttributes.filter(
400-
(t) ->
401-
t.position.type.equals(TargetType.METHOD_RETURN)
402-
&& (!onlyDirect || isDirectTypeUseAnnotation(t, symbol)));
403-
} else {
404-
// filter for annotations directly on the type
405-
return rawTypeAttributes.filter(
406-
t ->
407-
targetTypeMatches(symbol, t.position)
408-
&& (!onlyDirect || isDirectTypeUseAnnotation(t, symbol)));
409-
}
410-
}
411-
412-
// Adapted from Error Prone MoreAnnotations:
413-
// https://github.com/google/error-prone/blob/5f71110374e63f3c35b661f538295fa15b5c1db2/check_api/src/main/java/com/google/errorprone/util/MoreAnnotations.java#L128
414-
private static boolean targetTypeMatches(Symbol sym, TypeAnnotationPosition position) {
415-
switch (sym.getKind()) {
416-
case LOCAL_VARIABLE -> {
417-
return position.type == TargetType.LOCAL_VARIABLE;
418-
}
419-
case FIELD, ENUM_CONSTANT -> {
420-
// treated like a field
421-
return position.type == TargetType.FIELD;
422-
}
423-
case CONSTRUCTOR, METHOD -> {
424-
return position.type == TargetType.METHOD_RETURN;
425-
}
426-
case PARAMETER -> {
427-
if (position.type.equals(TargetType.METHOD_FORMAL_PARAMETER)) {
428-
int parameterIndex = position.parameter_index;
429-
if (position.onLambda != null) {
430-
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> lambdaParams =
431-
position.onLambda.params;
432-
return parameterIndex >= 0
433-
&& parameterIndex < lambdaParams.size()
434-
&& lambdaParams.get(parameterIndex).sym.equals(sym);
435-
} else {
436-
return ((Symbol.MethodSymbol) sym.owner).getParameters().indexOf(sym) == parameterIndex;
437-
}
438-
} else {
439-
return false;
440-
}
441-
}
442-
case CLASS, ENUM, RECORD -> {
443-
// treated like a class
444-
// There are no type annotations on the top-level type of the class/enum/record being
445-
// declared,
446-
// only on other types in the signature (e.g. `class Foo extends Bar<@A Baz> {}`).
447-
return false;
448-
}
449-
default -> {
450-
throw new AssertionError("unsupported element kind " + sym.getKind() + " symbol " + sym);
451-
}
353+
return getTypeUseAnnotationsIncludingEnclosingTypes(annotatedType);
452354
}
355+
return annotatedType.getAnnotationMirrors().stream();
453356
}
454357

455358
/**
456-
* Check whether a type-use annotation should be treated as applying directly to the top-level
457-
* type
359+
* Gets the type-use annotations directly on {@code type} or any of its enclosing types.
458360
*
459-
* <p>For example {@code @Nullable List<T> lst} is a direct type use annotation of {@code lst},
460-
* but {@code List<@Nullable T> lst} is not.
461-
*
462-
* @param t the annotation and its position in the type
463-
* @param symbol the symbol for the annotated element
464-
* @return {@code true} if the annotation should be treated as applying directly to the top-level
465-
* type, false otherwise
361+
* <p>javac models an annotation written before the outer class in a nested type on that enclosing
362+
* type. For method return and parameter nullability, NullAway treats such an annotation as
363+
* applying to the full nested type.
466364
*/
467-
private static boolean isDirectTypeUseAnnotation(Attribute.TypeCompound t, Symbol symbol) {
468-
// location is a list of TypePathEntry objects, indicating whether the annotation is
469-
// on an array, inner type, wildcard, or type argument. If it's empty, then the
470-
// annotation is directly on the type.
471-
// Annotations on array dimensions, wildcards, or type arguments do not apply to the top-level
472-
// type. For nested classes, an annotation applies directly only when placed on the innermost
473-
// type.
474-
int innerTypeCount = 0;
475-
for (TypePathEntry entry : t.position.location) {
476-
switch (entry.tag) {
477-
case INNER_TYPE -> {
478-
innerTypeCount++;
479-
}
480-
case ARRAY -> {
481-
return false;
482-
}
483-
default -> {
484-
// Wildcard or type argument!
485-
return false;
486-
}
487-
}
488-
}
489-
// For non-nested classes annotations apply to the innermost type.
490-
if (!isTypeOfNestedClass(symbol.type)) {
491-
return true;
492-
}
493-
// For nested classes the annotation is only valid if it is on the innermost type.
494-
return innerTypeCount == getNestingDepth(symbol.type) - 1;
495-
}
496-
497-
private static int getNestingDepth(Type type) {
498-
int depth = 0;
499-
for (Type curr = type;
500-
curr != null && !curr.hasTag(TypeTag.NONE);
501-
curr = curr.getEnclosingType()) {
502-
depth++;
503-
}
504-
return depth;
505-
}
506-
507-
private static boolean isTypeOfNestedClass(Type type) {
508-
return type.tsym != null && type.tsym.owner instanceof Symbol.ClassSymbol;
365+
private static Stream<Attribute.TypeCompound> getTypeUseAnnotationsIncludingEnclosingTypes(
366+
Type type) {
367+
Stream<Attribute.TypeCompound> annotations = type.getAnnotationMirrors().stream();
368+
Type enclosingType = type.getEnclosingType();
369+
return enclosingType == null || enclosingType.hasTag(TypeTag.NONE)
370+
? annotations
371+
: Stream.concat(annotations, getTypeUseAnnotationsIncludingEnclosingTypes(enclosingType));
509372
}
510373

511374
/**
@@ -661,19 +524,17 @@ private static boolean checkArrayElementAnnotations(
661524
Config config,
662525
BiPredicate<String, Config> typeUseCheck,
663526
BiPredicate<Symbol, Config> declarationCheck) {
664-
if (getTypeUseAnnotations(arraySymbol, /* onlyDirect= */ false)
665-
.anyMatch(
666-
t -> {
667-
// the location list should be of length 1 and the entry tag should be ARRAY
668-
com.sun.tools.javac.util.List<TypePathEntry> location = t.position.location;
669-
TypePathEntry head = location.head;
670-
boolean singleElementList = head != null && location.tail.isEmpty();
671-
if (singleElementList && head.tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
672-
return typeUseCheck.test(t.type.toString(), config);
673-
}
674-
return false;
675-
})) {
676-
return true;
527+
Type annotatedType =
528+
arraySymbol instanceof Symbol.MethodSymbol methodSymbol
529+
? methodSymbol.getReturnType()
530+
: arraySymbol.type;
531+
if (annotatedType instanceof Type.ArrayType arrayType) {
532+
for (AnnotationMirror annotationMirror :
533+
arrayType.getComponentType().getAnnotationMirrors()) {
534+
if (typeUseCheck.test(annotationMirror.getAnnotationType().toString(), config)) {
535+
return true;
536+
}
537+
}
677538
}
678539
// For varargs symbols we also check for declaration annotations on the parameter
679540
// NOTE this flag check does not work for the varargs parameter of a method defined in bytecodes

nullaway/src/main/java/com/uber/nullaway/Nullness.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static boolean isMonotonicNonNullAnnotation(String annotName) {
7070
* {@code symbol}. Used to reason whether a field may be null.
7171
*/
7272
public static boolean hasNullableOrMonotonicNonNullAnnotation(Symbol symbol, Config config) {
73-
return NullabilityUtil.hasAnyAnnotationMatchingBackCompat(
73+
return NullabilityUtil.hasAnyAnnotationMatching(
7474
symbol,
7575
annot -> isNullableAnnotation(annot, config) || isMonotonicNonNullAnnotation(annot));
7676
}
@@ -208,7 +208,7 @@ public static boolean isNonNullAnnotation(String annotName, Config config) {
208208
* Config)}
209209
*/
210210
public static boolean hasNonNullAnnotation(Symbol symbol, Config config) {
211-
return NullabilityUtil.hasAnyAnnotationMatchingBackCompat(
211+
return NullabilityUtil.hasAnyAnnotationMatching(
212212
symbol, annot -> isNonNullAnnotation(annot, config));
213213
}
214214

@@ -220,7 +220,7 @@ public static boolean hasNonNullAnnotation(Symbol symbol, Config config) {
220220
* Config)}
221221
*/
222222
public static boolean hasNullableAnnotation(Symbol symbol, Config config) {
223-
return NullabilityUtil.hasAnyAnnotationMatchingBackCompat(
223+
return NullabilityUtil.hasAnyAnnotationMatching(
224224
symbol, annot -> isNullableAnnotation(annot, config));
225225
}
226226

@@ -319,7 +319,7 @@ public static boolean varargsArrayIsNonNull(Symbol paramSymbol, Config config) {
319319

320320
/** Checks if the symbol has a {@code @Nullable} declaration annotation */
321321
public static boolean hasNullableDeclarationAnnotation(Symbol symbol, Config config) {
322-
for (AnnotationMirror annotationMirror : symbol.getRawAttributes()) {
322+
for (AnnotationMirror annotationMirror : symbol.getAnnotationMirrors()) {
323323
if (isNullableAnnotation(annotationMirror.getAnnotationType().toString(), config)) {
324324
return true;
325325
}
@@ -329,7 +329,7 @@ public static boolean hasNullableDeclarationAnnotation(Symbol symbol, Config con
329329

330330
/** Checks if the symbol has a {@code @NonNull} declaration annotation */
331331
public static boolean hasNonNullDeclarationAnnotation(Symbol symbol, Config config) {
332-
for (AnnotationMirror annotationMirror : symbol.getRawAttributes()) {
332+
for (AnnotationMirror annotationMirror : symbol.getAnnotationMirrors()) {
333333
if (isNonNullAnnotation(annotationMirror.getAnnotationType().toString(), config)) {
334334
return true;
335335
}

nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import com.sun.tools.javac.code.BoundKind;
3838
import com.sun.tools.javac.code.Symbol;
3939
import com.sun.tools.javac.code.Symtab;
40-
import com.sun.tools.javac.code.TargetType;
4140
import com.sun.tools.javac.code.Type;
4241
import com.sun.tools.javac.code.Types;
4342
import com.sun.tools.javac.tree.JCTree;
@@ -305,22 +304,6 @@ private boolean[] getTypeParamsWithNullableUpperBound(Type type) {
305304
result[i] = true;
306305
}
307306
}
308-
// For handling types declared in bytecode rather than source code.
309-
// Due to a bug in javac versions before JDK 22 (https://bugs.openjdk.org/browse/JDK-8225377),
310-
// the above code does not work for types declared in bytecode. We need to read the raw type
311-
// attributes instead.
312-
com.sun.tools.javac.util.List<Attribute.TypeCompound> rawTypeAttributes =
313-
tsym.getRawTypeAttributes();
314-
if (rawTypeAttributes != null) {
315-
for (Attribute.TypeCompound typeCompound : rawTypeAttributes) {
316-
if (typeCompound.position.type.equals(TargetType.CLASS_TYPE_PARAMETER_BOUND)
317-
&& Nullness.isNullableAnnotation(
318-
typeCompound.type.tsym.getQualifiedName().toString(), config)) {
319-
int index = typeCompound.position.parameter_index;
320-
result[index] = true;
321-
}
322-
}
323-
}
324307
return result;
325308
}
326309

nullaway/src/test/java/com/uber/nullaway/ErrorProneCLIFlagsConfigTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ public void bothAnnotatedPackagesAndOnlyNullMarkedFails() {
6767
public void missingTypeAnnotationSymbolFlagForJSpecifyModeOnOlderJDK() {
6868
Assume.assumeTrue(Runtime.version().feature() < 22);
6969
CompilationTestHelper compilationTestHelper =
70-
makeTestHelperWithArgs(
70+
CompilationTestHelper.newInstance(NullAway.class, getClass())
71+
.setArgs(
7172
List.of("-XepOpt:NullAway:OnlyNullMarked", "-XepOpt:NullAway:JSpecifyMode=true"))
7273
.addSourceLines("Stub.java", "package com.uber; class Stub {}");
7374
AssertionError e = assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());

0 commit comments

Comments
 (0)