8381965: NullPointerException in TypeAnnotationPositions

Reviewed-by: mcimadamore
This commit is contained in:
Jan Lahoda 2026-06-01 11:17:01 +00:00
parent 3f2002d488
commit 9acd9f8790
2 changed files with 202 additions and 1 deletions

View File

@ -25,6 +25,7 @@
package com.sun.tools.javac.comp;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.LambdaExpressionTree.BodyKind;
import com.sun.source.tree.NewClassTree;
import com.sun.tools.javac.code.*;
@ -62,6 +63,7 @@ import java.util.function.Predicate;
import java.util.function.Supplier;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree.JCMemberReference.OverloadKind;
@ -191,6 +193,19 @@ public class DeferredAttr extends JCTree.Visitor {
result.pos = t.pos;
return result;
}
@Override
public JCTree visitAnnotatedType(AnnotatedTypeTree node, Void p) {
return copy(((JCAnnotatedType) node).underlyingType, p);
}
@Override
public JCTree visitModifiers(ModifiersTree node, Void p) {
JCModifiers mods = (JCModifiers) super.visitModifiers(node, p);
mods.annotations = List.nil();
return mods;
}
};
deferredCopier = new TypeMapping<Void> () {
@Override

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8371155 8379550 8384843
* @bug 8371155 8379550 8381965 8384843
* @summary Verify type annotations on local-like variables are propagated to
* their types at an appropriate time.
* @library /tools/lib
@ -559,6 +559,192 @@ public class TypeAnnotationsOnVariables {
" Test$TypeAnno");
}
@Test
void explicitLambdaHeader3() throws Exception {
Path src = base.resolve("src");
Path classes = base.resolve("classes");
tb.writeJavaFiles(src,
"""
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.function.Consumer;
import java.util.List;
class Test {
@Target(ElementType.TYPE_USE)
@interface TypeAnno { }
static final Consumer<List<@TypeAnno String>> TEST =
id((List<@TypeAnno String> arg1) -> {});
private static <T> Consumer<T> id(Consumer<T> t) { return t;}
private void test() {
Object test =
id((List<@TypeAnno String> arg2) -> {});
}
}
""");
Files.createDirectories(classes);
List<String> actual = new ArrayList<>();
new JavacTask(tb)
.options("-d", classes.toString())
.files(tb.findJavaFiles(src))
.callback(task -> {
task.addTaskListener(new TaskListener() {
@Override
public void finished(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) {
return ;
}
Trees trees = Trees.instance(task);
new TreePathScanner<Void, Void>() {
@Override
public Void visitVariable(VariableTree node, Void p) {
actual.add(node.getName() + ": " + typeToString(trees.getTypeMirror(getCurrentPath())));
return super.visitVariable(node, p);
}
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
actual.add(treeToString(node)+ ": " + typeToString(trees.getTypeMirror(getCurrentPath())));
return super.visitLambdaExpression(node, p);
}
}.scan(e.getCompilationUnit(), null);
}
});
})
.run()
.writeAll();
List<String> expected = List.of(
"TEST: java.util.function.Consumer<java.util.List<java.lang.@Test.TypeAnno String>>",
"(List<@TypeAnno String> arg1)->{ }: java.util.function.Consumer<java.util.List<java.lang.String>>",
"arg1: java.util.List<java.lang.@Test.TypeAnno String>",
"t: java.util.function.Consumer<T>",
"test: java.lang.Object",
"(List<@TypeAnno String> arg2)->{ }: java.util.function.Consumer<java.util.List<java.lang.String>>",
"arg2: java.util.List<java.lang.@Test.TypeAnno String>"
);
actual.forEach(System.out::println);
if (!expected.equals(actual)) {
throw new AssertionError("Expected: " + expected + ", but got: " + actual);
}
Path testClass = classes.resolve("Test.class");
TestClassDesc testClassDesc = TestClassDesc.create(testClass);
MethodModel clInit = singletonValue(testClassDesc.name2Method().get("<clinit>"));
assertEmpty(getAnnotationsFromHeader(clInit));
assertEmpty(getAnnotationsFromCode(clInit));
MethodModel test = singletonValue(testClassDesc.name2Method().get("test"));
assertEmpty(getAnnotationsFromHeader(test));
assertEmpty(getAnnotationsFromCode(test));
checkTypeAnnotations(testClassDesc,
"lambda$static$0",
this::getAnnotationsFromHeader,
" 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]",
" Test$TypeAnno");
checkTypeAnnotations(testClassDesc,
"lambda$test$0",
this::getAnnotationsFromHeader,
" 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]",
" Test$TypeAnno");
}
@Test
void explicitLambdaHeader4() throws Exception {
Path src = base.resolve("src");
Path classes = base.resolve("classes");
tb.writeJavaFiles(src,
"""
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.function.Consumer;
import java.util.List;
class Test {
@Target(ElementType.TYPE_USE)
@interface TypeAnno { }
static final Consumer<List<String>> TEST =
id((@TypeAnno List<String> arg) -> {});
private static <T> Consumer<T> id(Consumer<T> t) { return t;}
private void test() {
Object test =
id((@TypeAnno List<String> arg) -> {});
}
}
""");
Files.createDirectories(classes);
List<String> actual = new ArrayList<>();
new JavacTask(tb)
.options("-d", classes.toString())
.files(tb.findJavaFiles(src))
.callback(task -> {
task.addTaskListener(new TaskListener() {
@Override
public void finished(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) {
return ;
}
Trees trees = Trees.instance(task);
new TreePathScanner<Void, Void>() {
@Override
public Void visitVariable(VariableTree node, Void p) {
actual.add(node.getName() + ": " + typeToString(trees.getTypeMirror(getCurrentPath())));
return super.visitVariable(node, p);
}
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
actual.add(treeToString(node)+ ": " + typeToString(trees.getTypeMirror(getCurrentPath())));
return super.visitLambdaExpression(node, p);
}
}.scan(e.getCompilationUnit(), null);
}
});
})
.run()
.writeAll();
List<String> expected = List.of(
"TEST: java.util.function.Consumer<java.util.List<java.lang.String>>",
"(@TypeAnno List<String> arg)->{ }: java.util.function.Consumer<java.util.List<java.lang.String>>",
"arg: java.util.@Test.TypeAnno List<java.lang.String>",
"t: java.util.function.Consumer<T>",
"test: java.lang.Object",
"(@TypeAnno List<String> arg)->{ }: java.util.function.Consumer<java.util.List<java.lang.String>>",
"arg: java.util.@Test.TypeAnno List<java.lang.String>"
);
actual.forEach(System.out::println);
if (!expected.equals(actual)) {
throw new AssertionError("Expected: " + expected + ", but got: " + actual);
}
Path testClass = classes.resolve("Test.class");
TestClassDesc testClassDesc = TestClassDesc.create(testClass);
MethodModel clInit = singletonValue(testClassDesc.name2Method().get("<clinit>"));
assertEmpty(getAnnotationsFromHeader(clInit));
assertEmpty(getAnnotationsFromCode(clInit));
MethodModel test = singletonValue(testClassDesc.name2Method().get("test"));
assertEmpty(getAnnotationsFromHeader(test));
assertEmpty(getAnnotationsFromCode(test));
checkTypeAnnotations(testClassDesc,
"lambda$static$0",
this::getAnnotationsFromHeader,
" 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0",
" Test$TypeAnno");
checkTypeAnnotations(testClassDesc,
"lambda$test$0",
this::getAnnotationsFromHeader,
" 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0",
" Test$TypeAnno");
}
private void checkTypeAnnotations(TestClassDesc testClassDesc,
String lambdaMethodName,
String... expectedEntries) throws IOException {