Skip to content

Commit a74e5d6

Browse files
committed
Remove old support for reading annotations from bytecode
1 parent 741fbad commit a74e5d6

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 org.checkerframework.nullaway.javacutil.AnnotationUtils;
6864
import org.jspecify.annotations.Nullable;
@@ -194,35 +190,6 @@ public static Stream<? extends AnnotationMirror> getAllAnnotations(Symbol symbol
194190
return Stream.concat(symbol.getAnnotationMirrors().stream(), typeUseAnnotations);
195191
}
196192

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

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

510373
/**
@@ -644,19 +507,17 @@ private static boolean checkArrayElementAnnotations(
644507
Config config,
645508
BiPredicate<String, Config> typeUseCheck,
646509
BiPredicate<Symbol, Config> declarationCheck) {
647-
if (getTypeUseAnnotations(arraySymbol, /* onlyDirect= */ false)
648-
.anyMatch(
649-
t -> {
650-
// the location list should be of length 1 and the entry tag should be ARRAY
651-
com.sun.tools.javac.util.List<TypePathEntry> location = t.position.location;
652-
TypePathEntry head = location.head;
653-
boolean singleElementList = head != null && location.tail.isEmpty();
654-
if (singleElementList && head.tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
655-
return typeUseCheck.test(t.type.toString(), config);
656-
}
657-
return false;
658-
})) {
659-
return true;
510+
Type annotatedType =
511+
arraySymbol instanceof Symbol.MethodSymbol methodSymbol
512+
? methodSymbol.getReturnType()
513+
: arraySymbol.type;
514+
if (annotatedType instanceof Type.ArrayType arrayType) {
515+
for (AnnotationMirror annotationMirror :
516+
arrayType.getComponentType().getAnnotationMirrors()) {
517+
if (typeUseCheck.test(annotationMirror.getAnnotationType().toString(), config)) {
518+
return true;
519+
}
520+
}
660521
}
661522
// For varargs symbols we also check for declaration annotations on the parameter
662523
// 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
@@ -35,7 +35,6 @@
3535
import com.sun.tools.javac.code.BoundKind;
3636
import com.sun.tools.javac.code.Symbol;
3737
import com.sun.tools.javac.code.Symtab;
38-
import com.sun.tools.javac.code.TargetType;
3938
import com.sun.tools.javac.code.Type;
4039
import com.sun.tools.javac.code.Types;
4140
import com.sun.tools.javac.tree.JCTree;
@@ -295,22 +294,6 @@ private boolean[] getTypeParamsWithNullableUpperBound(Type type) {
295294
result[i] = true;
296295
}
297296
}
298-
// For handling types declared in bytecode rather than source code.
299-
// Due to a bug in javac versions before JDK 22 (https://bugs.openjdk.org/browse/JDK-8225377),
300-
// the above code does not work for types declared in bytecode. We need to read the raw type
301-
// attributes instead.
302-
com.sun.tools.javac.util.List<Attribute.TypeCompound> rawTypeAttributes =
303-
tsym.getRawTypeAttributes();
304-
if (rawTypeAttributes != null) {
305-
for (Attribute.TypeCompound typeCompound : rawTypeAttributes) {
306-
if (typeCompound.position.type.equals(TargetType.CLASS_TYPE_PARAMETER_BOUND)
307-
&& Nullness.isNullableAnnotation(
308-
typeCompound.type.tsym.getQualifiedName().toString(), config)) {
309-
int index = typeCompound.position.parameter_index;
310-
result[index] = true;
311-
}
312-
}
313-
}
314297
return result;
315298
}
316299

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)