From f6baab704e868386c66b79072ae05d2b24bf8632 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 8 Apr 2026 06:23:42 +0000 Subject: [PATCH] 8379550: Bad bytecode offset after JDK-8371155 Reviewed-by: vromero, abimpoudis --- .../sun/tools/javac/code/TypeAnnotations.java | 8 + .../TypeAnnotationsOnVariables.java | 245 ++++++++++++++++-- 2 files changed, 237 insertions(+), 16 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java index 0393ed79897..452d15ed219 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java @@ -154,6 +154,14 @@ public class TypeAnnotations { } else { pos.push(env.enclMethod); } + Env env1 = env; + while (env1 != null && !env1.tree.hasTag(Tag.CLASSDEF)) { + if (env1.tree instanceof JCLambda l) { + pos.currentLambda = l; + break; + } + env1 = env1.next; + } pos.scan(tree); } finally { log.useSource(oldSource); diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java index 2e68e18f8f7..e5a1e5650d3 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java @@ -23,15 +23,16 @@ /* * @test - * @bug 8371155 + * @bug 8371155 8379550 * @summary Verify type annotations on local-like variables are propagated to * their types at an appropriate time. * @library /tools/lib * @modules * jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.javap * @build toolbox.ToolBox toolbox.JavacTask - * @run main TypeAnnotationsOnVariables + * @run junit TypeAnnotationsOnVariables */ import com.sun.source.tree.LambdaExpressionTree; @@ -41,31 +42,44 @@ import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; +import java.io.IOException; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.constantpool.Utf8Entry; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.UnionType; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import toolbox.JavacTask; +import toolbox.JavapTask; +import toolbox.Task; import toolbox.ToolBox; public class TypeAnnotationsOnVariables { - public static void main(String... args) throws Exception { - new TypeAnnotationsOnVariables().run(); - } + private static final Pattern CP_REFERENCE = Pattern.compile("#([1-9][0-9]*)"); + final ToolBox tb = new ToolBox(); + Path base; - ToolBox tb = new ToolBox(); - - void run() throws Exception { - typeAnnotationInConstantExpressionFieldInit(Paths.get(".")); - } - - void typeAnnotationInConstantExpressionFieldInit(Path base) throws Exception { + @Test + void typeAnnotationInConstantExpressionFieldInit() throws Exception { Path src = base.resolve("src"); Path classes = base.resolve("classes"); tb.writeJavaFiles(src, @@ -203,9 +217,208 @@ public class TypeAnnotationsOnVariables { } static String treeToString(Tree tree) { - if (tree.toString().contains("\n")) { - System.err.println("!!!"); - } return String.valueOf(tree).replaceAll("\\R", " "); } + + @Test + void properPathForLocalVarsInLambdas() 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.Supplier; + + class Test { + @Target(ElementType.TYPE_USE) + @interface TypeAnno { } + + void o() { + Runnable r = () -> { + @TypeAnno long test1 = 0; + while (true) { + @TypeAnno long test2 = 0; + System.err.println(test2); + try (@TypeAnno AutoCloseable ac = null) { + System.err.println(ac); + } catch (@TypeAnno Exception e1) { + System.err.println(e1); + } + try { + "".length(); + } catch (@TypeAnno final Exception e2) { + System.err.println(e2); + } + try { + "".length(); + } catch (@TypeAnno IllegalStateException | @TypeAnno NullPointerException | IllegalArgumentException e3) { + System.err.println(e3); + } + Runnable r2 = () -> { + @TypeAnno long test3 = 0; + while (true) { + @TypeAnno long test4 = 0; + System.err.println(test4); + } + }; + Object o = null; + if (o instanceof @TypeAnno String s) { + System.err.println(s); + } + } + }; + } + void lambdaInClass() { + class C { + Runnable r = () -> { + @TypeAnno long test1 = 0; + System.err.println(test1); + }; + } + } + void classInLambda() { + Runnable r = () -> { + class C { + void method() { + @TypeAnno long test1 = 0; + System.err.println(test1); + } + } + }; + } + } + """); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(tb.findJavaFiles(src)) + .run() + .writeAll(); + + Path testClass = classes.resolve("Test.class"); + TestClassDesc testClassDesc = TestClassDesc.create(testClass); + MethodModel oMethod = singletonValue(testClassDesc.name2Method().get("o")); + var oTypeAnnos = getAnnotations(oMethod); + assertFalse(oTypeAnnos.isPresent(), () -> oTypeAnnos.toString()); + + checkTypeAnnotations(testClassDesc, + "lambda$o$0", + " 0: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=2, length=151, index=0}", + " Test$TypeAnno", + " 1: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=4, length=146, index=2}", + " Test$TypeAnno", + " 2: LTest$TypeAnno;(): RESOURCE_VARIABLE, {start_pc=14, length=52, index=4}", + " Test$TypeAnno", + " 3: LTest$TypeAnno;(): EXCEPTION_PARAMETER, exception_index=2", + " Test$TypeAnno", + " 4: LTest$TypeAnno;(): EXCEPTION_PARAMETER, exception_index=3", + " Test$TypeAnno", + " 5: LTest$TypeAnno;(): EXCEPTION_PARAMETER, exception_index=4", + " Test$TypeAnno", + " 6: LTest$TypeAnno;(): EXCEPTION_PARAMETER, exception_index=5", + " Test$TypeAnno", + " 7: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=142, length=8, index=6}", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$o$1", + " 0: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=2, length=12, index=0}", + " Test$TypeAnno", + " 1: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=4, length=7, index=2}", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$classInLambda$0"); + + checkTypeAnnotations(TestClassDesc.create(classes.resolve("Test$1C.class")), + "lambda$new$0", + " 0: LTest$TypeAnno;(): LOCAL_VARIABLE, {start_pc=2, length=8, index=0}", + " Test$TypeAnno"); + } + + private void checkTypeAnnotations(TestClassDesc testClassDesc, + String lambdaMethodName, + String... expectedEntries) throws IOException { + MethodModel lambdaMethod = singletonValue(testClassDesc.name2Method().get(lambdaMethodName)); + var lambdaTypeAnnos = getAnnotations(lambdaMethod); + if (expectedEntries.length == 0) { + assertFalse(lambdaTypeAnnos.isPresent(), () -> lambdaTypeAnnos.toString()); + } else { + assertTrue(lambdaTypeAnnos.isPresent(), () -> lambdaTypeAnnos.toString()); + assertEquals(expectedEntries.length / 2, + lambdaTypeAnnos.orElseThrow().annotations().size(), + () -> lambdaTypeAnnos.orElseThrow().annotations().toString()); + + checkJavapOutput(testClassDesc, + List.of(expectedEntries)); + } + } + + private T singletonValue(List values) { + assertEquals(1, values.size()); + return values.get(0); + } + + private Optional getAnnotations(MethodModel m) { + return m.findAttribute(Attributes.code()) + .orElseThrow() + .findAttribute(Attributes.runtimeInvisibleTypeAnnotations()); + } + + void checkJavapOutput(TestClassDesc testClassDesc, List expectedOutput) throws IOException { + String javapOut = new JavapTask(tb) + .options("-v", "-p") + .classes(testClassDesc.pathToClass().toString()) + .run() + .getOutput(Task.OutputKind.DIRECT); + + StringBuilder expandedJavapOutBuilder = new StringBuilder(); + Matcher m = CP_REFERENCE.matcher(javapOut); + + while (m.find()) { + String cpIndexText = m.group(1); + int cpIndex = Integer.parseInt(cpIndexText); + m.appendReplacement(expandedJavapOutBuilder, Matcher.quoteReplacement(testClassDesc.cpIndex2Name().getOrDefault(cpIndex, cpIndexText))); + } + + m.appendTail(expandedJavapOutBuilder); + + String expandedJavapOut = expandedJavapOutBuilder.toString(); + + for (String expected : expectedOutput) { + if (!expandedJavapOut.contains(expected)) { + System.err.println(expandedJavapOut); + throw new AssertionError("unexpected output"); + } + } + } + + record TestClassDesc(Path pathToClass, + Map> name2Method, + Map cpIndex2Name) { + public static TestClassDesc create(Path pathToClass) throws IOException{ + ClassModel model = ClassFile.of().parse(pathToClass); + Map> name2Method = + model.methods() + .stream() + .collect(Collectors.groupingBy(m -> m.methodName().stringValue())); + ConstantPool cp = model.constantPool(); + int cpSize = cp.size(); + Map cpIndex2Name = new HashMap<>(); + + for (int i = 1; i < cpSize; i++) { + if (cp.entryByIndex(i) instanceof Utf8Entry string) { + cpIndex2Name.put(i, string.stringValue()); + } + } + + return new TestClassDesc(pathToClass, name2Method, cpIndex2Name); + } + } + + @BeforeEach + void setUp(TestInfo thisTest) { + base = Path.of(thisTest.getTestMethod().orElseThrow().getName()); + } }