/* * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.*; import java.net.*; import java.util.*; import java.lang.constant.*; import java.nio.file.Paths; import java.lang.classfile.*; import java.lang.classfile.attribute.*; import java.lang.classfile.constantpool.*; /* * @test * @bug 6888367 * @summary classfile library parses signature attributes incorrectly * @enablePreview * @modules java.base/jdk.internal.classfile.impl */ /* * This test is a pretty detailed test both of javac signature generation and classfile * signature parsing. The first part of the test tests all the examples given in the * second part of the test. Each example comes with one or two annotations, @Desc, @Sig, * for the descriptor and signature of the annotated declaration. Annotations are * provided whenever the annotated item is expected to have a corresponding value. * Each annotation has two argument values. The first arg is the expected value of the * descriptor/signature as found in the class file. This value is mostly for documentation * purposes in reading the test. The second value is the rendering of the descriptor or * signature using a custom Type visitor that explicitly includes an indication of the * Type classes being used to represent the descriptor/signature. Thus we test * that the descriptor/signature is being parsed into the expected type tree structure. */ public class T6888367 { public static void main(String... args) throws Exception { new T6888367().run(); } public void run() throws Exception { ClassModel cm = getClassFile("Test"); testFields(cm); testMethods(cm); testInnerClasses(cm); // recursive if (errors > 0) throw new Exception(errors + " errors found"); } void testFields(ClassModel cm) throws Exception { String cn = cm.thisClass().name().stringValue(); for (FieldModel fm: cm.fields()) { test("field " + cn + "." + fm.fieldName(), fm.fieldTypeSymbol(), fm); } } void testMethods(ClassModel cm) throws Exception { String cn = cm.thisClass().name().stringValue(); for (MethodModel mm: cm.methods()) { test("method " + cn + "." + mm.methodName(), mm.methodTypeSymbol(), mm); } } void testInnerClasses(ClassModel cm) throws Exception { InnerClassesAttribute ic = cm.findAttribute(Attributes.INNER_CLASSES).orElse(null); assert ic != null; for (InnerClassInfo info: ic.classes()) { ClassEntry outerClass = info.outerClass().orElse(null); if (outerClass == null || !outerClass.name().equalsString(cm.getClass().getName())) { continue; } String innerClassName = info.innerClass().asInternalName(); ClassModel icm = getClassFile(innerClassName); test("class " + innerClassName, null, icm); testInnerClasses(icm); } } void test(String name, ConstantDesc desc, AttributedElement m) { AnnotValues d = getDescValue(m); AnnotValues s = getSigValue(m); if (d == null && s == null) // not a test field or method if no @Desc or @Sig given return; System.err.println(name); SignatureAttribute sa = m.findAttribute(Attributes.SIGNATURE).orElse(null); if (sa != null) System.err.println(" signature: " + sa.signature()); switch (desc) { case ClassDesc cDesc -> { System.err.println(" descriptor: " + cDesc.descriptorString()); checkEqual(d.raw, cDesc.descriptorString()); Signature dt = Signature.of(cDesc); checkEqual(d.type, tp.print(dt)); if (s != null || sa != null) { if (s != null && sa != null) { checkEqual(s.raw, sa.signature().stringValue()); Signature st = Signature.parseFrom(sa.signature().stringValue()); checkEqual(s.type, tp.print(st)); } else if (s != null) error("@Sig annotation found but not Signature attribute"); else error("Signature attribute found but no @Sig annotation"); } } case MethodTypeDesc mDesc -> { System.err.println(" descriptor: " + mDesc.descriptorString()); checkEqual(d.raw, mDesc.descriptorString()); MethodSignature mdt = MethodSignature.of(mDesc); checkEqual(d.type, tp.print(mdt)); if (s != null || sa != null) { if (s != null && sa != null) { checkEqual(s.raw, sa.signature().stringValue()); MethodSignature mst = MethodSignature.parseFrom(sa.signature().stringValue()); checkEqual(s.type, tp.print(mst)); } else if (s != null) error("@Sig annotation found but not Signature attribute"); else error("Signature attribute found but no @Sig annotation"); } } default -> throw new AssertionError(); } System.err.println(); } ClassModel getClassFile(String name) throws IOException, URISyntaxException { URL rsc = getClass().getResource(name + ".class"); assert rsc != null; return ClassFile.of().parse(Paths.get(rsc.toURI())); } AnnotValues getDescValue(AttributedElement m) { return getAnnotValues(Desc.class.getName(), m); } AnnotValues getSigValue(AttributedElement m) { return getAnnotValues(Sig.class.getName(), m); } static class AnnotValues { AnnotValues(String raw, String type) { this.raw = raw; this.type = type; } final String raw; final String type; } AnnotValues getAnnotValues(String annotName, AttributedElement m) { RuntimeInvisibleAnnotationsAttribute annots = m.findAttribute(Attributes.RUNTIME_INVISIBLE_ANNOTATIONS).orElse(null); if (annots != null) { for (Annotation a: annots.annotations()) { if (a.classSymbol().descriptorString().equals("L" + annotName + ";")) { String pv0 = ((AnnotationValue.OfString) a.elements().get(0).value()).stringValue(); String pv1 = ((AnnotationValue.OfString) a.elements().get(1).value()).stringValue(); return new AnnotValues(pv0, pv1); } } } return null; } void checkEqual(String expect, String found) { if (!(Objects.equals(expect, found))) { System.err.println("expected: " + expect); System.err.println(" found: " + found); error("unexpected values found"); } } void error(String msg) { System.err.println("error: " + msg); errors++; } int errors; TypePrinter tp = new TypePrinter(); class TypePrinter { String print(T t) { switch (t) { case Signature.BaseTypeSig type -> { return visitSimpleType(type); } case Signature.ArrayTypeSig type -> { return visitArrayType(type); } case Signature.ClassTypeSig type -> { return visitClassType(type); } case ClassSignature type -> { return visitClassSigType(type); } case MethodSignature type -> { return visitMethodType(type); } case Signature.TypeVarSig type -> { return "S{" + type.identifier() + "}"; //Consider the TypeVarSig as Simple Type } default -> { return null; } } } String print(String pre, List ts, String post) { if (ts == null) return null; StringBuilder sb = new StringBuilder(); sb.append(pre); String sep = ""; for (T t: ts) { sb.append(sep); switch (t) { case Signature sig -> sb.append(print(sig)); case Signature.TypeParam pSig -> sb.append(visitTypeParamType(pSig)); case Signature.TypeArg aSig -> sb.append(visitWildcardType(aSig)); default -> throw new AssertionError(); } sep = ","; } sb.append(post); return sb.toString(); } public String visitSimpleType(Signature.BaseTypeSig type) { return "S{" + type.baseType() + "}"; } public String visitArrayType(Signature.ArrayTypeSig type) { return "A{" + print(type.componentSignature()) + "}"; } public String visitMethodType(MethodSignature type) { StringBuilder sb = new StringBuilder(); sb.append("M{"); if (!type.typeParameters().isEmpty()) sb.append(print("<", type.typeParameters(), ">")); sb.append(print(type.result())); sb.append(print("(", type.arguments(), ")")); if (!type.throwableSignatures().isEmpty()) sb.append(print("", type.throwableSignatures(), "")); sb.append("}"); return sb.toString(); } public String visitClassSigType(ClassSignature type) { StringBuilder sb = new StringBuilder(); sb.append("CS{"); if (!type.typeParameters().isEmpty()) sb.append(print("<", type.typeParameters(), ">")); sb.append(print(type.superclassSignature())); if (!type.superinterfaceSignatures().isEmpty()) sb.append(print("i(", type.superinterfaceSignatures(), ")")); sb.append("}"); return sb.toString(); } public String visitClassType(Signature.ClassTypeSig type) { StringBuilder sb = new StringBuilder(); sb.append("C{"); if (type.outerType().isPresent()) { sb.append(print(type.outerType().get())); sb.append("."); } sb.append(type.className()); if (!type.typeArgs().isEmpty()) sb.append(print("<", type.typeArgs(), ">")); sb.append("}"); return sb.toString(); } public String visitTypeParamType(Signature.TypeParam type) { StringBuilder sb = new StringBuilder(); sb.append("TA{"); sb.append(type.identifier()); if (type.classBound().isPresent()) { sb.append(":c"); sb.append(print(type.classBound().get())); } if (!type.interfaceBounds().isEmpty()) sb.append(print(":i", type.interfaceBounds(), "")); sb.append("}"); return sb.toString(); } public String visitWildcardType(Signature.TypeArg type) { switch (type.wildcardIndicator()) { case UNBOUNDED -> { return "W{?}"; } case EXTENDS -> { return "W{e," + print(type.boundType().get()) + "}"; } case SUPER -> { return "W{s," + print(type.boundType().get()) + "}"; } default -> { if (type.boundType().isPresent()) return print(type.boundType().get()); else throw new AssertionError(); } } } }; } @interface Desc { String d(); String t(); } @interface Sig { String s(); String t(); } class Clss { } interface Intf { } class GenClss { } class Test { // fields @Desc(d="Z", t="S{Z}") boolean z; @Desc(d="B", t="S{B}") byte b; @Desc(d="C", t="S{C}") char c; @Desc(d="D", t="S{D}") double d; @Desc(d="F", t="S{F}") float f; @Desc(d="I", t="S{I}") int i; @Desc(d="J", t="S{J}") long l; @Desc(d="S", t="S{S}") short s; @Desc(d="LClss;", t="C{Clss}") Clss clss; @Desc(d="LIntf;", t="C{Intf}") Intf intf; @Desc(d="[I", t="A{S{I}}") int[] ai; @Desc(d="[LClss;", t="A{C{Clss}}") Clss[] aClss; @Desc(d="LGenClss;", t="C{GenClss}") @Sig(s="LGenClss;", t="C{GenClss}") GenClss genClass; // methods, return types @Desc(d="()V", t="M{S{V}()}") void mv0() { } @Desc(d="()I", t="M{S{I}()}") int mi0() { return 0; } @Desc(d="()LClss;", t="M{C{Clss}()}") Clss mclss0() { return null; } @Desc(d="()[I", t="M{A{S{I}}()}") int[] mai0() { return null; } @Desc(d="()[LClss;", t="M{A{C{Clss}}()}") Clss[] maClss0() { return null; } @Desc(d="()LGenClss;", t="M{C{GenClss}()}") @Sig(s="()LGenClss;", t="M{C{GenClss}()}") GenClss mgenClss0() { return null; } @Desc(d="()LGenClss;", t="M{C{GenClss}()}") @Sig(s="()LGenClss<*>;", t="M{C{GenClss}()}") GenClss mgenClssW0() { return null; } @Desc(d="()LGenClss;", t="M{C{GenClss}()}") @Sig(s="()LGenClss<+LClss;>;", t="M{C{GenClss}()}") GenClss mgenClssWExtClss0() { return null; } @Desc(d="()LGenClss;", t="M{C{GenClss}()}") @Sig(s="()LGenClss<-LClss;>;", t="M{C{GenClss}()}") GenClss mgenClssWSupClss0() { return null; } @Desc(d="()Ljava/lang/Object;", t="M{C{java/lang/Object}()}") @Sig(s="()TT;", t="M{S{T}()}") T mt0() { return null; } @Desc(d="()LGenClss;", t="M{C{GenClss}()}") @Sig(s="()LGenClss<+TT;>;", t="M{C{GenClss}()}") GenClss mgenClssWExtT0() { return null; } @Desc(d="()LGenClss;", t="M{C{GenClss}()}") @Sig(s="()LGenClss<-TT;>;", t="M{C{GenClss}()}") GenClss mgenClssWSupT0() { return null; } // methods, arg types @Desc(d="(I)V", t="M{S{V}(S{I})}") void mi1(int arg) { } @Desc(d="(LClss;)V", t="M{S{V}(C{Clss})}") void mclss1(Clss arg) { } @Desc(d="([I)V", t="M{S{V}(A{S{I}})}") void mai1(int[] arg) { } @Desc(d="([LClss;)V", t="M{S{V}(A{C{Clss}})}") void maClss1(Clss[] arg) { } @Desc(d="(LGenClss;)V", t="M{S{V}(C{GenClss})}") @Sig(s="(LGenClss;)V", t="M{S{V}(C{GenClss})}") void mgenClss1(GenClss arg) { } @Desc(d="(LGenClss;)V", t="M{S{V}(C{GenClss})}") @Sig(s="(LGenClss<*>;)V", t="M{S{V}(C{GenClss})}") void mgenClssW1(GenClss arg) { } @Desc(d="(LGenClss;)V", t="M{S{V}(C{GenClss})}") @Sig(s="(LGenClss<+LClss;>;)V", t="M{S{V}(C{GenClss})}") void mgenClssWExtClss1(GenClss arg) { } @Desc(d="(LGenClss;)V", t="M{S{V}(C{GenClss})}") @Sig(s="(LGenClss<-LClss;>;)V", t="M{S{V}(C{GenClss})}") void mgenClssWSupClss1(GenClss arg) { } @Desc(d="(Ljava/lang/Object;)V", t="M{S{V}(C{java/lang/Object})}") @Sig(s="(TT;)V", t="M{S{V}(S{T})}") void mt1(T arg) { } @Desc(d="(LGenClss;)V", t="M{S{V}(C{GenClss})}") @Sig(s="(LGenClss<+TT;>;)V", t="M{S{V}(C{GenClss})}") void mgenClssWExtT1(GenClss arg) { } @Desc(d="(LGenClss;)V", t="M{S{V}(C{GenClss})}") @Sig(s="(LGenClss<-TT;>;)V", t="M{S{V}(C{GenClss})}") void mgenClssWSupT1(GenClss arg) { } // methods, throws @Desc(d="()V", t="M{S{V}()}") void m_E() throws Exception { } @Desc(d="()V", t="M{S{V}()}") @Sig(s="()V^TT;", t="M{S{V}()S{T}}") void m_T() throws T { } // inner classes static class X { // no sig class P { } @Sig(s="LTest$X$P;", t="CS{C{Test$X$P}}") class Q extends P { } @Sig(s="LTest$X$Q;", t="CS{C{Test$X$Q}}") class R extends Q { } } @Sig(s="Ljava/lang/Object;", t="CS{C{java/lang/Object}}") static class Y { // no sig class P { } @Sig(s="LTest$Y.P;", t="CS{C{C{Test$Y}.P}}") class Q extends P { } @Sig(s="LTest$Y.Q;", t="CS{C{C{Test$Y}.Q}}") class R extends Q { // no sig class R1 { } @Sig(s="LTest$Y.R.R1;", t="CS{C{C{C{Test$Y}.R}.R1}}") class R2 extends R1 { } } @Sig(s="LTest$Y.Q;", t="C{C{Test$Y}.Q}") class S extends Q { // no sig class S1 { } @Sig(s="LTest$Y.S.S1;", t="CS{C{C{C{Test$Y}.S}.S1}}") class S2 extends S1 { } @Sig(s="LTest$Y.S.S2;", t="C{C{C{Test$Y}.S}.S2}") class S3 extends S2 { } } } }