diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 894e09528e01f..50342077645ac 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -592,7 +592,16 @@ define SetupRunMicroTestBody endif # Set library path for native dependencies - $1_JMH_JVM_ARGS := -Djava.library.path=$$(TEST_IMAGE_DIR)/micro/native + $1_JMH_JVM_ARGS := -Djava.library.path=$$(TEST_IMAGE_DIR)/micro/native \ + --add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.attribute=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.constantpool=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.instruction=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.java.lang.constant=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.components=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.impl=ALL-UNNAMED ifneq ($$(MICRO_VM_OPTIONS)$$(MICRO_JAVA_OPTIONS), ) $1_JMH_JVM_ARGS += $$(MICRO_VM_OPTIONS) $$(MICRO_JAVA_OPTIONS) diff --git a/make/modules/java.base/Java.gmk b/make/modules/java.base/Java.gmk index 25c14d2b1d265..3c867e2744070 100644 --- a/make/modules/java.base/Java.gmk +++ b/make/modules/java.base/Java.gmk @@ -25,7 +25,9 @@ DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' -JAVAC_FLAGS += -XDstringConcat=inline +JAVAC_FLAGS += -XDstringConcat=inline \ + --enable-preview +DISABLED_WARNINGS_java += preview COPY += .icu .dat .spp .nrm content-types.properties \ hijrah-config-Hijrah-umalqura_islamic-umalqura.properties CLEAN += intrinsic.properties @@ -33,7 +35,9 @@ CLEAN += intrinsic.properties EXCLUDE_FILES += \ $(TOPDIR)/src/java.base/share/classes/jdk/internal/module/ModuleLoaderMap.java -EXCLUDES += java/lang/doc-files +EXCLUDES += java/lang/doc-files \ + jdk/internal/classfile/snippet-files \ + jdk/internal/classfile/components/snippet-files # Exclude BreakIterator classes that are just used in compile process to generate # data files and shouldn't go in the product diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 1c89328a38861..9c20b237936c6 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -96,6 +96,15 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ BIN := $(MICROBENCHMARK_CLASSES), \ JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \ --add-exports java.base/sun.invoke.util=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.attribute=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.constantpool=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.instruction=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.java.lang.constant=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.components=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.classfile.impl=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ --enable-preview, \ JAVA_FLAGS := --add-modules jdk.unsupported --limit-modules java.management \ diff --git a/src/java.base/share/classes/jdk/internal/classfile/AccessFlags.java b/src/java.base/share/classes/jdk/internal/classfile/AccessFlags.java new file mode 100644 index 0000000000000..d4dacff5a2909 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/AccessFlags.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.Set; +import jdk.internal.classfile.impl.AccessFlagsImpl; +import java.lang.reflect.AccessFlag; + +/** + * Models the access flags for a class, method, or field. Delivered as a + * {@link jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.FieldElement}, or + * {@link jdk.internal.classfile.MethodElement} when traversing + * the corresponding model type. + */ +public sealed interface AccessFlags + extends ClassElement, MethodElement, FieldElement + permits AccessFlagsImpl { + + /** + * {@return the access flags, as a bit mask} + */ + int flagsMask(); + + /** + * {@return the access flags} + */ + Set flags(); + + /** + * {@return whether the specified flag is present} The specified flag + * should be a valid flag for the classfile location associated with this + * element otherwise false is returned. + * @param flag the flag to test + */ + boolean has(AccessFlag flag); + + /** + * {@return the classfile location for this element, which is either class, + * method, or field} + */ + AccessFlag.Location location(); + + /** + * {@return an {@linkplain AccessFlags} for a class} + * @param mask the flags to be set, as a bit mask + */ + static AccessFlags ofClass(int mask) { + return new AccessFlagsImpl(AccessFlag.Location.CLASS, mask); + } + + /** + * {@return an {@linkplain AccessFlags} for a class} + * @param flags the flags to be set + */ + static AccessFlags ofClass(AccessFlag... flags) { + return new AccessFlagsImpl(AccessFlag.Location.CLASS, flags); + } + + /** + * {@return an {@linkplain AccessFlags} for a field} + * @param mask the flags to be set, as a bit mask + */ + static AccessFlags ofField(int mask) { + return new AccessFlagsImpl(AccessFlag.Location.FIELD, mask); + } + + /** + * {@return an {@linkplain AccessFlags} for a field} + * @param flags the flags to be set + */ + static AccessFlags ofField(AccessFlag... flags) { + return new AccessFlagsImpl(AccessFlag.Location.FIELD, flags); + } + + /** + * {@return an {@linkplain AccessFlags} for a method} + * @param mask the flags to be set, as a bit mask + */ + static AccessFlags ofMethod(int mask) { + return new AccessFlagsImpl(AccessFlag.Location.METHOD, mask); + } + + /** + * {@return an {@linkplain AccessFlags} for a method} + * @param flags the flags to be set + */ + static AccessFlags ofMethod(AccessFlag... flags) { + return new AccessFlagsImpl(AccessFlag.Location.METHOD, flags); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Annotation.java b/src/java.base/share/classes/jdk/internal/classfile/Annotation.java new file mode 100644 index 0000000000000..116bedf16ed91 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Annotation.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AnnotationImpl; +import jdk.internal.classfile.impl.TemporaryConstantPool; + +import java.lang.constant.ClassDesc; +import java.util.List; + +/** + * Models an annotation on a declaration. + * + * @see AnnotationElement + * @see AnnotationValue + * @see RuntimeVisibleAnnotationsAttribute + * @see RuntimeInvisibleAnnotationsAttribute + * @see RuntimeVisibleParameterAnnotationsAttribute + * @see RuntimeInvisibleParameterAnnotationsAttribute + */ +public sealed interface Annotation + extends WritableElement + permits TypeAnnotation, AnnotationImpl { + + /** + * {@return the class of the annotation} + */ + Utf8Entry className(); + + /** + * {@return the class of the annotation, as a symbolic descriptor} + */ + default ClassDesc classSymbol() { + return ClassDesc.ofDescriptor(className().stringValue()); + } + + /** + * {@return the elements of the annotation} + */ + List elements(); + + /** + * {@return an annotation} + * @param annotationClass the class of the annotation + * @param elements the elements of the annotation + */ + static Annotation of(Utf8Entry annotationClass, + List elements) { + return new AnnotationImpl(annotationClass, elements); + } + + /** + * {@return an annotation} + * @param annotationClass the class of the annotation + * @param elements the elements of the annotation + */ + static Annotation of(Utf8Entry annotationClass, + AnnotationElement... elements) { + return of(annotationClass, List.of(elements)); + } + + /** + * {@return an annotation} + * @param annotationClass the class of the annotation + * @param elements the elements of the annotation + */ + static Annotation of(ClassDesc annotationClass, + List elements) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(annotationClass.descriptorString()), elements); + } + + /** + * {@return an annotation} + * @param annotationClass the class of the annotation + * @param elements the elements of the annotation + */ + static Annotation of(ClassDesc annotationClass, + AnnotationElement... elements) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(annotationClass.descriptorString()), elements); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/AnnotationElement.java b/src/java.base/share/classes/jdk/internal/classfile/AnnotationElement.java new file mode 100644 index 0000000000000..4f21be3bc2eba --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/AnnotationElement.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.lang.constant.ClassDesc; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AnnotationImpl; +import jdk.internal.classfile.impl.TemporaryConstantPool; + +/** + * Models a key-value pair of an annotation. + * + * @see Annotation + * @see AnnotationValue + */ +public sealed interface AnnotationElement + extends WritableElement + permits AnnotationImpl.AnnotationElementImpl { + + /** + * {@return the element name} + */ + Utf8Entry name(); + + /** + * {@return the element value} + */ + AnnotationValue value(); + + /** + * {@return an annotation key-value pair} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement of(Utf8Entry name, + AnnotationValue value) { + return new AnnotationImpl.AnnotationElementImpl(name, value); + } + + /** + * {@return an annotation key-value pair} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement of(String name, + AnnotationValue value) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(name), value); + } + + /** + * {@return an annotation key-value pair for a class-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofClass(String name, + ClassDesc value) { + return of(name, AnnotationValue.ofClass(value)); + } + + /** + * {@return an annotation key-value pair for a string-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofString(String name, + String value) { + return of(name, AnnotationValue.ofString(value)); + } + + /** + * {@return an annotation key-value pair for a long-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofLong(String name, + long value) { + return of(name, AnnotationValue.ofLong(value)); + } + + /** + * {@return an annotation key-value pair for an int-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofInt(String name, + int value) { + return of(name, AnnotationValue.ofInt(value)); + } + + /** + * {@return an annotation key-value pair for a char-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofChar(String name, + char value) { + return of(name, AnnotationValue.ofChar(value)); + } + + /** + * {@return an annotation key-value pair for a short-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofShort(String name, + short value) { + return of(name, AnnotationValue.ofShort(value)); + } + + /** + * {@return an annotation key-value pair for a byte-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofByte(String name, + byte value) { + return of(name, AnnotationValue.ofByte(value)); + } + + /** + * {@return an annotation key-value pair for a boolean-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofBoolean(String name, + boolean value) { + return of(name, AnnotationValue.ofBoolean(value)); + } + + /** + * {@return an annotation key-value pair for a double-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofDouble(String name, + double value) { + return of(name, AnnotationValue.ofDouble(value)); + } + + /** + * {@return an annotation key-value pair for a float-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofFloat(String name, + float value) { + return of(name, AnnotationValue.ofFloat(value)); + } + + /** + * {@return an annotation key-value pair for an annotation-valued annotation} + * @param name the name of the key + * @param value the associated value + */ + static AnnotationElement ofAnnotation(String name, + Annotation value) { + return of(name, AnnotationValue.ofAnnotation(value)); + } + + /** + * {@return an annotation key-value pair for an array-valued annotation} + * @param name the name of the key + * @param values the associated values + */ + static AnnotationElement ofArray(String name, + AnnotationValue... values) { + return of(name, AnnotationValue.ofArray(values)); + } +} + diff --git a/src/java.base/share/classes/jdk/internal/classfile/AnnotationValue.java b/src/java.base/share/classes/jdk/internal/classfile/AnnotationValue.java new file mode 100644 index 0000000000000..1401b8b4e8118 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/AnnotationValue.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.constantpool.AnnotationConstantValueEntry; +import jdk.internal.classfile.constantpool.DoubleEntry; +import jdk.internal.classfile.constantpool.FloatEntry; +import jdk.internal.classfile.constantpool.IntegerEntry; +import jdk.internal.classfile.constantpool.LongEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AnnotationImpl; +import jdk.internal.classfile.impl.TemporaryConstantPool; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.util.ArrayList; +import java.util.List; + +/** + * Models the value of a key-value pair of an annotation. + * + * @see Annotation + * @see AnnotationElement + */ + +public sealed interface AnnotationValue extends WritableElement + permits AnnotationValue.OfAnnotation, AnnotationValue.OfArray, + AnnotationValue.OfConstant, AnnotationValue.OfClass, + AnnotationValue.OfEnum { + + /** Models an annotation-valued element */ + sealed interface OfAnnotation extends AnnotationValue + permits AnnotationImpl.OfAnnotationImpl { + /** {@return the annotation} */ + Annotation annotation(); + } + + /** Models an array-valued element */ + sealed interface OfArray extends AnnotationValue + permits AnnotationImpl.OfArrayImpl { + /** {@return the values} */ + List values(); + } + + /** Models a constant-valued element */ + sealed interface OfConstant extends AnnotationValue + permits AnnotationValue.OfString, AnnotationValue.OfDouble, + AnnotationValue.OfFloat, AnnotationValue.OfLong, + AnnotationValue.OfInteger, AnnotationValue.OfShort, + AnnotationValue.OfCharacter, AnnotationValue.OfByte, + AnnotationValue.OfBoolean, AnnotationImpl.OfConstantImpl { + /** {@return the constant} */ + AnnotationConstantValueEntry constant(); + /** {@return the constant} */ + ConstantDesc constantValue(); + } + + /** Models a constant-valued element */ + sealed interface OfString extends AnnotationValue.OfConstant + permits AnnotationImpl.OfStringImpl { + /** {@return the constant} */ + String stringValue(); + } + + /** Models a constant-valued element */ + sealed interface OfDouble extends AnnotationValue.OfConstant + permits AnnotationImpl.OfDoubleImpl { + /** {@return the constant} */ + double doubleValue(); + } + + /** Models a constant-valued element */ + sealed interface OfFloat extends AnnotationValue.OfConstant + permits AnnotationImpl.OfFloatImpl { + /** {@return the constant} */ + float floatValue(); + } + + /** Models a constant-valued element */ + sealed interface OfLong extends AnnotationValue.OfConstant + permits AnnotationImpl.OfLongImpl { + /** {@return the constant} */ + long longValue(); + } + + /** Models a constant-valued element */ + sealed interface OfInteger extends AnnotationValue.OfConstant + permits AnnotationImpl.OfIntegerImpl { + /** {@return the constant} */ + int intValue(); + } + + /** Models a constant-valued element */ + sealed interface OfShort extends AnnotationValue.OfConstant + permits AnnotationImpl.OfShortImpl { + /** {@return the constant} */ + short shortValue(); + } + + /** Models a constant-valued element */ + sealed interface OfCharacter extends AnnotationValue.OfConstant + permits AnnotationImpl.OfCharacterImpl { + /** {@return the constant} */ + char charValue(); + } + + /** Models a constant-valued element */ + sealed interface OfByte extends AnnotationValue.OfConstant + permits AnnotationImpl.OfByteImpl { + /** {@return the constant} */ + byte byteValue(); + } + + /** Models a constant-valued element */ + sealed interface OfBoolean extends AnnotationValue.OfConstant + permits AnnotationImpl.OfBooleanImpl { + /** {@return the constant} */ + boolean booleanValue(); + } + + /** Models a class-valued element */ + sealed interface OfClass extends AnnotationValue + permits AnnotationImpl.OfClassImpl { + /** {@return the class name} */ + Utf8Entry className(); + + /** {@return the class symbol} */ + default ClassDesc classSymbol() { + return ClassDesc.ofDescriptor(className().stringValue()); + } + } + + /** Models an enum-valued element */ + sealed interface OfEnum extends AnnotationValue + permits AnnotationImpl.OfEnumImpl { + /** {@return the enum class name} */ + Utf8Entry className(); + + /** {@return the enum class symbol} */ + default ClassDesc classSymbol() { + return ClassDesc.ofDescriptor(className().stringValue()); + } + + /** {@return the enum constant name} */ + Utf8Entry constantName(); + } + + /** + * @return the tag character for this type as per {@jvms 4.7.16.1} + */ + char tag(); + + /** + * {@return an annotation element for a enum-valued element} + * @param className the name of the enum class + * @param constantName the name of the enum constant + */ + static OfEnum ofEnum(Utf8Entry className, + Utf8Entry constantName) { + return new AnnotationImpl.OfEnumImpl(className, constantName); + } + + /** + * {@return an annotation element for a enum-valued element} + * @param className the name of the enum class + * @param constantName the name of the enum constant + */ + static OfEnum ofEnum(ClassDesc className, String constantName) { + return ofEnum(TemporaryConstantPool.INSTANCE.utf8Entry(className.descriptorString()), + TemporaryConstantPool.INSTANCE.utf8Entry(constantName)); + } + + /** + * {@return an annotation element for a class-valued element} + * @param className the name of the enum class + */ + static OfClass ofClass(Utf8Entry className) { + return new AnnotationImpl.OfClassImpl(className); + } + + /** + * {@return an annotation element for a class-valued element} + * @param className the name of the enum class + */ + static OfClass ofClass(ClassDesc className) { + return ofClass(TemporaryConstantPool.INSTANCE.utf8Entry(className.descriptorString())); + } + + /** + * {@return an annotation element for a string-valued element} + * @param value the string + */ + static OfConstant ofString(Utf8Entry value) { + return new AnnotationImpl.OfStringImpl(value); + } + + /** + * {@return an annotation element for a string-valued element} + * @param value the string + */ + static OfConstant ofString(String value) { + return ofString(TemporaryConstantPool.INSTANCE.utf8Entry(value)); + } + + /** + * {@return an annotation element for a double-valued element} + * @param value the double value + */ + static OfConstant ofDouble(DoubleEntry value) { + return new AnnotationImpl.OfDoubleImpl(value); + } + + /** + * {@return an annotation element for a double-valued element} + * @param value the double value + */ + static OfConstant ofDouble(double value) { + return ofDouble(TemporaryConstantPool.INSTANCE.doubleEntry(value)); + } + + /** + * {@return an annotation element for a float-valued element} + * @param value the float value + */ + static OfConstant ofFloat(FloatEntry value) { + return new AnnotationImpl.OfFloatImpl(value); + } + + /** + * {@return an annotation element for a float-valued element} + * @param value the float value + */ + static OfConstant ofFloat(float value) { + return ofFloat(TemporaryConstantPool.INSTANCE.floatEntry(value)); + } + + /** + * {@return an annotation element for a long-valued element} + * @param value the long value + */ + static OfConstant ofLong(LongEntry value) { + return new AnnotationImpl.OfLongImpl(value); + } + + /** + * {@return an annotation element for a long-valued element} + * @param value the long value + */ + static OfConstant ofLong(long value) { + return ofLong(TemporaryConstantPool.INSTANCE.longEntry(value)); + } + + /** + * {@return an annotation element for an int-valued element} + * @param value the int value + */ + static OfConstant ofInt(IntegerEntry value) { + return new AnnotationImpl.OfIntegerImpl(value); + } + + /** + * {@return an annotation element for an int-valued element} + * @param value the int value + */ + static OfConstant ofInt(int value) { + return ofInt(TemporaryConstantPool.INSTANCE.intEntry(value)); + } + + /** + * {@return an annotation element for a short-valued element} + * @param value the short value + */ + static OfConstant ofShort(IntegerEntry value) { + return new AnnotationImpl.OfShortImpl(value); + } + + /** + * {@return an annotation element for a short-valued element} + * @param value the short value + */ + static OfConstant ofShort(short value) { + return ofShort(TemporaryConstantPool.INSTANCE.intEntry(value)); + } + + /** + * {@return an annotation element for a char-valued element} + * @param value the char value + */ + static OfConstant ofChar(IntegerEntry value) { + return new AnnotationImpl.OfCharacterImpl(value); + } + + /** + * {@return an annotation element for a char-valued element} + * @param value the char value + */ + static OfConstant ofChar(char value) { + return ofChar(TemporaryConstantPool.INSTANCE.intEntry(value)); + } + + /** + * {@return an annotation element for a byte-valued element} + * @param value the byte value + */ + static OfConstant ofByte(IntegerEntry value) { + return new AnnotationImpl.OfByteImpl(value); + } + + /** + * {@return an annotation element for a byte-valued element} + * @param value the byte value + */ + static OfConstant ofByte(byte value) { + return ofByte(TemporaryConstantPool.INSTANCE.intEntry(value)); + } + + /** + * {@return an annotation element for a boolean-valued element} + * @param value the boolean value + */ + static OfConstant ofBoolean(IntegerEntry value) { + return new AnnotationImpl.OfBooleanImpl(value); + } + + /** + * {@return an annotation element for a boolean-valued element} + * @param value the boolean value + */ + static OfConstant ofBoolean(boolean value) { + int i = value ? 1 : 0; + return ofBoolean(TemporaryConstantPool.INSTANCE.intEntry(i)); + } + + /** + * {@return an annotation element for an annotation-valued element} + * @param value the annotation + */ + static OfAnnotation ofAnnotation(Annotation value) { + return new AnnotationImpl.OfAnnotationImpl(value); + } + + /** + * {@return an annotation element for an array-valued element} + * @param values the values + */ + static OfArray ofArray(List values) { + return new AnnotationImpl.OfArrayImpl(values); + } + + /** + * {@return an annotation element for an array-valued element} + * @param values the values + */ + static OfArray ofArray(AnnotationValue... values) { + return ofArray(List.of(values)); + } + + /** + * {@return an annotation element} The {@code value} parameter must be + * a primitive, a String, a ClassDesc, an enum constant, or an array of + * one of these. + * + * @param value the annotation value + */ + static AnnotationValue of(Object value) { + if (value instanceof String s) { + return ofString(s); + } else if (value instanceof Byte b) { + return ofByte(b); + } else if (value instanceof Boolean b) { + return ofBoolean(b); + } else if (value instanceof Short s) { + return ofShort(s); + } else if (value instanceof Character c) { + return ofChar(c); + } else if (value instanceof Integer i) { + return ofInt(i); + } else if (value instanceof Long l) { + return ofLong(l); + } else if (value instanceof Float f) { + return ofFloat(f); + } else if (value instanceof Double d) { + return ofDouble(d); + } else if (value instanceof ClassDesc clsDesc) { + return ofClass(clsDesc); + } else if (value instanceof byte[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofByte(el)); + } + return ofArray(els); + } else if (value instanceof boolean[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofBoolean(el)); + } + return ofArray(els); + } else if (value instanceof short[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofShort(el)); + } + return ofArray(els); + } else if (value instanceof char[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofChar(el)); + } + return ofArray(els); + } else if (value instanceof int[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofInt(el)); + } + return ofArray(els); + } else if (value instanceof long[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofLong(el)); + } + return ofArray(els); + } else if (value instanceof float[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofFloat(el)); + } + return ofArray(els); + } else if (value instanceof double[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(ofDouble(el)); + } + return ofArray(els); + } else if (value instanceof Object[] arr) { + var els = new ArrayList(arr.length); + for (var el : arr) { + els.add(of(el)); + } + return ofArray(els); + } else if (value instanceof Enum e) { + return ofEnum(ClassDesc.ofDescriptor(e.getDeclaringClass().descriptorString()), e.name()); + } + throw new IllegalArgumentException("Illegal annotation constant value type " + (value == null ? null : value.getClass())); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Attribute.java b/src/java.base/share/classes/jdk/internal/classfile/Attribute.java new file mode 100644 index 0000000000000..343ba9677e212 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Attribute.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.AnnotationDefaultAttribute; +import jdk.internal.classfile.attribute.BootstrapMethodsAttribute; +import jdk.internal.classfile.attribute.CharacterRangeTableAttribute; +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.attribute.CompilationIDAttribute; +import jdk.internal.classfile.attribute.ConstantValueAttribute; +import jdk.internal.classfile.attribute.DeprecatedAttribute; +import jdk.internal.classfile.attribute.EnclosingMethodAttribute; +import jdk.internal.classfile.attribute.ExceptionsAttribute; +import jdk.internal.classfile.attribute.InnerClassesAttribute; +import jdk.internal.classfile.attribute.LineNumberTableAttribute; +import jdk.internal.classfile.attribute.LocalVariableTableAttribute; +import jdk.internal.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.internal.classfile.attribute.MethodParametersAttribute; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleHashesAttribute; +import jdk.internal.classfile.attribute.ModuleMainClassAttribute; +import jdk.internal.classfile.attribute.ModulePackagesAttribute; +import jdk.internal.classfile.attribute.ModuleResolutionAttribute; +import jdk.internal.classfile.attribute.ModuleTargetAttribute; +import jdk.internal.classfile.attribute.NestHostAttribute; +import jdk.internal.classfile.attribute.NestMembersAttribute; +import jdk.internal.classfile.attribute.PermittedSubclassesAttribute; +import jdk.internal.classfile.attribute.RecordAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.classfile.attribute.SourceIDAttribute; +import jdk.internal.classfile.attribute.StackMapTableAttribute; +import jdk.internal.classfile.attribute.SyntheticAttribute; +import jdk.internal.classfile.attribute.UnknownAttribute; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a classfile attribute {@jvms 4.7}. Many, though not all, subtypes of + * {@linkplain Attribute} will implement {@link ClassElement}, {@link + * MethodElement}, {@link FieldElement}, or {@link CodeElement}; attributes that + * are also elements will be delivered when traversing the elements of the + * corresponding model type. Additionally, all attributes are accessible + * directly from the corresponding model type through {@link + * AttributedElement#findAttribute(AttributeMapper)}. + */ +public sealed interface Attribute> + extends WritableElement + permits AnnotationDefaultAttribute, BootstrapMethodsAttribute, + CharacterRangeTableAttribute, CodeAttribute, CompilationIDAttribute, + ConstantValueAttribute, DeprecatedAttribute, EnclosingMethodAttribute, + ExceptionsAttribute, InnerClassesAttribute, LineNumberTableAttribute, + LocalVariableTableAttribute, LocalVariableTypeTableAttribute, + MethodParametersAttribute, ModuleAttribute, ModuleHashesAttribute, + ModuleMainClassAttribute, ModulePackagesAttribute, ModuleResolutionAttribute, + ModuleTargetAttribute, NestHostAttribute, NestMembersAttribute, + PermittedSubclassesAttribute, + RecordAttribute, RuntimeInvisibleAnnotationsAttribute, + RuntimeInvisibleParameterAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, + RuntimeVisibleAnnotationsAttribute, RuntimeVisibleParameterAnnotationsAttribute, + RuntimeVisibleTypeAnnotationsAttribute, SignatureAttribute, + SourceDebugExtensionAttribute, SourceFileAttribute, SourceIDAttribute, + StackMapTableAttribute, SyntheticAttribute, + UnknownAttribute, BoundAttribute, UnboundAttribute { + /** + * {@return the name of the attribute} + */ + String attributeName(); + + /** + * {@return the {@link AttributeMapper} associated with this attribute} + */ + AttributeMapper attributeMapper(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/AttributeMapper.java b/src/java.base/share/classes/jdk/internal/classfile/AttributeMapper.java new file mode 100644 index 0000000000000..eea5877a4e884 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/AttributeMapper.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +/** + * Bidirectional mapper between the classfile representation of an attribute and + * how that attribute is modeled in the API. The attribute mapper is used + * to parse the classfile representation into a model, and to write the model + * representation back to a classfile. For each standard attribute, there is a + * predefined attribute mapper defined in {@link Attributes}. For nonstandard + * attributes, clients can define their own {@linkplain AttributeMapper}. + * Classes that model nonstandard attributes should extend {@link + * CustomAttribute}. + */ +public interface AttributeMapper { + + /** + * {@return the name of the attribute} + */ + String name(); + + /** + * Create an {@link Attribute} instance from a classfile. + * + * @param enclosing The class, method, field, or code attribute in which + * this attribute appears + * @param cf The {@link ClassReader} describing the classfile to read from + * @param pos The offset into the classfile at which the attribute starts + * @return the new attribute + */ + A readAttribute(AttributedElement enclosing, ClassReader cf, int pos); + + /** + * Write an {@link Attribute} instance to a classfile. + * + * @param buf The {@link BufWriter} to which the attribute should be written + * @param attr The attribute to write + */ + void writeAttribute(BufWriter buf, A attr); + + /** + * {@return The earliest classfile version for which this attribute is + * applicable} + */ + default int validSince() { + return Classfile.JAVA_1_VERSION; + } + + /** + * {@return whether this attribute may appear more than once in a given location} + */ + default boolean allowMultiple() { + return false; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/AttributedElement.java b/src/java.base/share/classes/jdk/internal/classfile/AttributedElement.java new file mode 100644 index 0000000000000..f3f497f30089f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/AttributedElement.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import jdk.internal.classfile.attribute.RecordComponentInfo; +import jdk.internal.classfile.impl.AbstractUnboundModel; + +/** + * A {@link ClassfileElement} describing an entity that has attributes, such + * as a class, field, method, code attribute, or record component. + */ +public sealed interface AttributedElement extends ClassfileElement + permits ClassModel, CodeModel, FieldModel, MethodModel, + RecordComponentInfo, AbstractUnboundModel { + + /** + * {@return the attributes of this element} + */ + List> attributes(); + + /** + * Finds an attribute by name. + * @param attr the attribute mapper + * @param the type of the attribute + * @return the attribute, or an empty {@linkplain Optional} if the attribute + * is not present + */ + default > Optional findAttribute(AttributeMapper attr) { + for (Attribute la : attributes()) { + if (la.attributeMapper() == attr) { + @SuppressWarnings("unchecked") + var res = Optional.of((T) la); + return res; + } + } + return Optional.empty(); + } + + /** + * Finds one or more attributes by name. + * @param attr the attribute mapper + * @param the type of the attribute + * @return the attributes, or an empty {@linkplain List} if the attribute + * is not present + */ + default > List findAttributes(AttributeMapper attr) { + var list = new ArrayList(); + for (var a : attributes()) { + if (a.attributeMapper() == attr) { + @SuppressWarnings("unchecked") + T t = (T)a; + list.add(t); + } + } + return list; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Attributes.java b/src/java.base/share/classes/jdk/internal/classfile/Attributes.java new file mode 100644 index 0000000000000..4b6acfd2f1188 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Attributes.java @@ -0,0 +1,786 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jdk.internal.classfile.attribute.AnnotationDefaultAttribute; +import jdk.internal.classfile.attribute.BootstrapMethodsAttribute; +import jdk.internal.classfile.attribute.CharacterRangeInfo; +import jdk.internal.classfile.attribute.CharacterRangeTableAttribute; +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.attribute.CompilationIDAttribute; +import jdk.internal.classfile.attribute.ConstantValueAttribute; +import jdk.internal.classfile.attribute.DeprecatedAttribute; +import jdk.internal.classfile.attribute.EnclosingMethodAttribute; +import jdk.internal.classfile.attribute.ExceptionsAttribute; +import jdk.internal.classfile.attribute.InnerClassInfo; +import jdk.internal.classfile.attribute.InnerClassesAttribute; +import jdk.internal.classfile.attribute.LineNumberInfo; +import jdk.internal.classfile.attribute.LineNumberTableAttribute; +import jdk.internal.classfile.attribute.LocalVariableInfo; +import jdk.internal.classfile.attribute.LocalVariableTableAttribute; +import jdk.internal.classfile.attribute.LocalVariableTypeInfo; +import jdk.internal.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.internal.classfile.attribute.MethodParameterInfo; +import jdk.internal.classfile.attribute.MethodParametersAttribute; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleExportInfo; +import jdk.internal.classfile.attribute.ModuleHashInfo; +import jdk.internal.classfile.attribute.ModuleHashesAttribute; +import jdk.internal.classfile.attribute.ModuleMainClassAttribute; +import jdk.internal.classfile.attribute.ModuleOpenInfo; +import jdk.internal.classfile.attribute.ModulePackagesAttribute; +import jdk.internal.classfile.attribute.ModuleProvideInfo; +import jdk.internal.classfile.attribute.ModuleRequireInfo; +import jdk.internal.classfile.attribute.ModuleResolutionAttribute; +import jdk.internal.classfile.attribute.ModuleTargetAttribute; +import jdk.internal.classfile.attribute.NestHostAttribute; +import jdk.internal.classfile.attribute.NestMembersAttribute; +import jdk.internal.classfile.attribute.PermittedSubclassesAttribute; +import jdk.internal.classfile.attribute.RecordAttribute; +import jdk.internal.classfile.attribute.RecordComponentInfo; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.classfile.attribute.SourceIDAttribute; +import jdk.internal.classfile.attribute.StackMapTableAttribute; +import jdk.internal.classfile.attribute.SyntheticAttribute; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AbstractAttributeMapper; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.CodeImpl; +import jdk.internal.classfile.impl.AbstractPoolEntry; +import jdk.internal.classfile.impl.StackMapDecoder; + +/** + * Attribute mappers for standard classfile attributes. + * + * @see AttributeMapper + */ +public class Attributes { + public static final String NAME_ANNOTATION_DEFAULT = "AnnotationDefault"; + public static final String NAME_BOOTSTRAP_METHODS = "BootstrapMethods"; + public static final String NAME_CHARACTER_RANGE_TABLE = "CharacterRangeTable"; + public static final String NAME_CODE = "Code"; + public static final String NAME_COMPILATION_ID = "CompilationID"; + public static final String NAME_CONSTANT_VALUE = "ConstantValue"; + public static final String NAME_DEPRECATED = "Deprecated"; + public static final String NAME_ENCLOSING_METHOD = "EnclosingMethod"; + public static final String NAME_EXCEPTIONS = "Exceptions"; + public static final String NAME_INNER_CLASSES = "InnerClasses"; + public static final String NAME_LINE_NUMBER_TABLE = "LineNumberTable"; + public static final String NAME_LOCAL_VARIABLE_TABLE = "LocalVariableTable"; + public static final String NAME_LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; + public static final String NAME_METHOD_PARAMETERS = "MethodParameters"; + public static final String NAME_MODULE = "Module"; + public static final String NAME_MODULE_HASHES = "ModuleHashes"; + public static final String NAME_MODULE_MAIN_CLASS = "ModuleMainClass"; + public static final String NAME_MODULE_PACKAGES = "ModulePackages"; + public static final String NAME_MODULE_RESOLUTION = "ModuleResolution"; + public static final String NAME_MODULE_TARGET = "ModuleTarget"; + public static final String NAME_NEST_HOST = "NestHost"; + public static final String NAME_NEST_MEMBERS = "NestMembers"; + public static final String NAME_PERMITTED_SUBCLASSES = "PermittedSubclasses"; + public static final String NAME_RECORD = "Record"; + public static final String NAME_RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; + public static final String NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = "RuntimeInvisibleParameterAnnotations"; + public static final String NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations"; + public static final String NAME_RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; + public static final String NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations"; + public static final String NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations"; + public static final String NAME_SIGNATURE = "Signature"; + public static final String NAME_SOURCE_DEBUG_EXTENSION = "SourceDebugExtension"; + public static final String NAME_SOURCE_FILE = "SourceFile"; + public static final String NAME_SOURCE_ID = "SourceID"; + public static final String NAME_STACK_MAP_TABLE = "StackMapTable"; + public static final String NAME_SYNTHETIC = "Synthetic"; + + private Attributes() { + } + + /** Attribute mapper for the {@code AnnotationDefault} attribute */ + public static final AttributeMapper + ANNOTATION_DEFAULT = new AbstractAttributeMapper<>(NAME_ANNOTATION_DEFAULT, Classfile.JAVA_5_VERSION) { + @Override + public AnnotationDefaultAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundAnnotationDefaultAttr(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, AnnotationDefaultAttribute attr) { + attr.defaultValue().writeTo(buf); + } + }; + + /** Attribute mapper for the {@code BootstrapMethods} attribute */ + public static final AttributeMapper + BOOTSTRAP_METHODS = new AbstractAttributeMapper<>(NAME_BOOTSTRAP_METHODS, Classfile.JAVA_17_VERSION) { + @Override + public BootstrapMethodsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundBootstrapMethodsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, BootstrapMethodsAttribute attr) { + buf.writeList(attr.bootstrapMethods()); + } + }; + + /** Attribute mapper for the {@code CharacterRangeTable} attribute */ + public static final AttributeMapper + CHARACTER_RANGE_TABLE = new AbstractAttributeMapper<>(NAME_CHARACTER_RANGE_TABLE, true, Classfile.JAVA_4_VERSION) { + @Override + public CharacterRangeTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundCharacterRangeTableAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, CharacterRangeTableAttribute attr) { + List ranges = attr.characterRangeTable(); + buf.writeU2(ranges.size()); + for (CharacterRangeInfo info : ranges) { + buf.writeU2(info.startPc()); + buf.writeU2(info.endPc()); + buf.writeInt(info.characterRangeStart()); + buf.writeInt(info.characterRangeEnd()); + buf.writeU2(info.flags()); + } + } + }; + + /** Attribute mapper for the {@code Code} attribute */ + public static final AttributeMapper + CODE = new AbstractAttributeMapper<>(NAME_CODE) { + @Override + public CodeAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new CodeImpl(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, CodeAttribute attr) { + throw new UnsupportedOperationException("Code attribute does not support direct write"); + } + }; + + + /** Attribute mapper for the {@code CompilationID} attribute */ + public static final AttributeMapper + COMPILATION_ID = new AbstractAttributeMapper<>(NAME_COMPILATION_ID, true) { + @Override + public CompilationIDAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundCompilationIDAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, CompilationIDAttribute attr) { + buf.writeIndex(attr.compilationId()); + } + }; + + /** Attribute mapper for the {@code ConstantValue} attribute */ + public static final AttributeMapper + CONSTANT_VALUE = new AbstractAttributeMapper<>(NAME_CONSTANT_VALUE) { + @Override + public ConstantValueAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundConstantValueAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ConstantValueAttribute attr) { + buf.writeIndex(attr.constant()); + } + }; + + /** Attribute mapper for the {@code Deprecated} attribute */ + public static final AttributeMapper + DEPRECATED = new AbstractAttributeMapper<>(NAME_DEPRECATED, true) { + @Override + public DeprecatedAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundDeprecatedAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, DeprecatedAttribute attr) { + // empty + } + }; + + /** Attribute mapper for the {@code EnclosingMethod} attribute */ + public static final AttributeMapper + ENCLOSING_METHOD = new AbstractAttributeMapper<>(NAME_ENCLOSING_METHOD, Classfile.JAVA_5_VERSION) { + @Override + public EnclosingMethodAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundEnclosingMethodAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, EnclosingMethodAttribute attr) { + buf.writeIndex(attr.enclosingClass()); + buf.writeIndexOrZero(attr.enclosingMethod().orElse(null)); + } + }; + + /** Attribute mapper for the {@code Exceptions} attribute */ + public static final AttributeMapper + EXCEPTIONS = new AbstractAttributeMapper<>(NAME_EXCEPTIONS) { + @Override + public ExceptionsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundExceptionsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ExceptionsAttribute attr) { + buf.writeListIndices(attr.exceptions()); + } + }; + + /** Attribute mapper for the {@code InnerClasses} attribute */ + public static final AttributeMapper + INNER_CLASSES = new AbstractAttributeMapper<>(NAME_INNER_CLASSES) { + @Override + public InnerClassesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundInnerClassesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, InnerClassesAttribute attr) { + List classes = attr.classes(); + buf.writeU2(classes.size()); + for (InnerClassInfo ic : classes) { + buf.writeIndex(ic.innerClass()); + buf.writeIndexOrZero(ic.outerClass().orElse(null)); + buf.writeIndexOrZero(ic.innerName().orElse(null)); + buf.writeU2(ic.flagsMask()); + } + } + }; + + /** Attribute mapper for the {@code LineNumberTable} attribute */ + public static final AttributeMapper + LINE_NUMBER_TABLE = new AbstractAttributeMapper<>(NAME_LINE_NUMBER_TABLE, true) { + @Override + public LineNumberTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLineNumberTableAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LineNumberTableAttribute attr) { + List lines = attr.lineNumbers(); + buf.writeU2(lines.size()); + for (LineNumberInfo line : lines) { + buf.writeU2(line.startPc()); + buf.writeU2(line.lineNumber()); + } + } + }; + + /** Attribute mapper for the {@code LocalVariableTable} attribute */ + public static final AttributeMapper + LOCAL_VARIABLE_TABLE = new AbstractAttributeMapper<>(NAME_LOCAL_VARIABLE_TABLE, true) { + @Override + public LocalVariableTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLocalVariableTableAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LocalVariableTableAttribute attr) { + List infos = attr.localVariables(); + buf.writeU2(infos.size()); + for (LocalVariableInfo info : infos) { + buf.writeU2(info.startPc()); + buf.writeU2(info.length()); + buf.writeIndex(info.name()); + buf.writeIndex(info.type()); + buf.writeU2(info.slot()); + } + } + }; + + /** Attribute mapper for the {@code LocalVariableTypeTable} attribute */ + public static final AttributeMapper + LOCAL_VARIABLE_TYPE_TABLE = new AbstractAttributeMapper<>(NAME_LOCAL_VARIABLE_TYPE_TABLE, true, Classfile.JAVA_5_VERSION) { + @Override + public LocalVariableTypeTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLocalVariableTypeTableAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LocalVariableTypeTableAttribute attr) { + List infos = attr.localVariableTypes(); + buf.writeU2(infos.size()); + for (LocalVariableTypeInfo info : infos) { + buf.writeU2(info.startPc()); + buf.writeU2(info.length()); + buf.writeIndex(info.name()); + buf.writeIndex(info.signature()); + buf.writeU2(info.slot()); + } + } + }; + + /** Attribute mapper for the {@code MethodParameters} attribute */ + public static final AttributeMapper + METHOD_PARAMETERS = new AbstractAttributeMapper<>(NAME_METHOD_PARAMETERS, Classfile.JAVA_8_VERSION) { + @Override + public MethodParametersAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundMethodParametersAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, MethodParametersAttribute attr) { + List parameters = attr.parameters(); + buf.writeU1(parameters.size()); + for (MethodParameterInfo info : parameters) { + buf.writeIndexOrZero(info.name().orElse(null)); + buf.writeU2(info.flagsMask()); + } + } + }; + + /** Attribute mapper for the {@code Module} attribute */ + public static final AttributeMapper + MODULE = new AbstractAttributeMapper<>(NAME_MODULE, Classfile.JAVA_9_VERSION) { + @Override + public ModuleAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleAttribute attr) { + buf.writeIndex(attr.moduleName()); + buf.writeU2(attr.moduleFlagsMask()); + buf.writeIndexOrZero(attr.moduleVersion().orElse(null)); + buf.writeU2(attr.requires().size()); + for (ModuleRequireInfo require : attr.requires()) { + buf.writeIndex(require.requires()); + buf.writeU2(require.requiresFlagsMask()); + buf.writeIndexOrZero(require.requiresVersion().orElse(null)); + } + buf.writeU2(attr.exports().size()); + for (ModuleExportInfo export : attr.exports()) { + buf.writeIndex(export.exportedPackage()); + buf.writeU2(export.exportsFlagsMask()); + buf.writeListIndices(export.exportsTo()); + } + buf.writeU2(attr.opens().size()); + for (ModuleOpenInfo open : attr.opens()) { + buf.writeIndex(open.openedPackage()); + buf.writeU2(open.opensFlagsMask()); + buf.writeListIndices(open.opensTo()); + } + buf.writeListIndices(attr.uses()); + buf.writeU2(attr.provides().size()); + for (ModuleProvideInfo provide : attr.provides()) { + buf.writeIndex(provide.provides()); + buf.writeListIndices(provide.providesWith()); + } + } + }; + + /** Attribute mapper for the {@code ModuleHashes} attribute */ + public static final AttributeMapper + MODULE_HASHES = new AbstractAttributeMapper<>(NAME_MODULE_HASHES, Classfile.JAVA_9_VERSION) { + @Override + public ModuleHashesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleHashesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleHashesAttribute attr) { + buf.writeIndex(attr.algorithm()); + List hashes = attr.hashes(); + buf.writeU2(hashes.size()); + for (ModuleHashInfo hash : hashes) { + buf.writeIndex(hash.moduleName()); + buf.writeU2(hash.hash().length); + buf.writeBytes(hash.hash()); + } + } + }; + + /** Attribute mapper for the {@code ModuleMainClass} attribute */ + public static final AttributeMapper + MODULE_MAIN_CLASS = new AbstractAttributeMapper<>(NAME_MODULE_MAIN_CLASS, Classfile.JAVA_9_VERSION) { + @Override + public ModuleMainClassAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleMainClassAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleMainClassAttribute attr) { + buf.writeIndex(attr.mainClass()); + } + }; + + /** Attribute mapper for the {@code ModulePackages} attribute */ + public static final AttributeMapper + MODULE_PACKAGES = new AbstractAttributeMapper<>(NAME_MODULE_PACKAGES, Classfile.JAVA_9_VERSION) { + @Override + public ModulePackagesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModulePackagesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModulePackagesAttribute attr) { + buf.writeListIndices(attr.packages()); + } + }; + + /** Attribute mapper for the {@code ModuleResolution} attribute */ + public static final AttributeMapper + MODULE_RESOLUTION = new AbstractAttributeMapper<>(NAME_MODULE_RESOLUTION, true, Classfile.JAVA_9_VERSION) { + @Override + public ModuleResolutionAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleResolutionAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleResolutionAttribute attr) { + buf.writeU2(attr.resolutionFlags()); + } + }; + + /** Attribute mapper for the {@code ModuleTarget} attribute */ + public static final AttributeMapper + MODULE_TARGET = new AbstractAttributeMapper<>(NAME_MODULE_TARGET, true, Classfile.JAVA_9_VERSION) { + @Override + public ModuleTargetAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleTargetAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleTargetAttribute attr) { + buf.writeIndex(attr.targetPlatform()); + } + }; + + /** Attribute mapper for the {@code NestHost} attribute */ + public static final AttributeMapper + NEST_HOST = new AbstractAttributeMapper<>(NAME_NEST_HOST, Classfile.JAVA_11_VERSION) { + @Override + public NestHostAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundNestHostAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, NestHostAttribute attr) { + buf.writeIndex(attr.nestHost()); + } + }; + + /** Attribute mapper for the {@code NestMembers} attribute */ + public static final AttributeMapper + NEST_MEMBERS = new AbstractAttributeMapper<>(NAME_NEST_MEMBERS, Classfile.JAVA_11_VERSION) { + @Override + public NestMembersAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundNestMembersAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, NestMembersAttribute attr) { + buf.writeListIndices(attr.nestMembers()); + } + }; + + /** Attribute mapper for the {@code PermittedSubclasses} attribute */ + public static final AttributeMapper + PERMITTED_SUBCLASSES = new AbstractAttributeMapper<>(NAME_PERMITTED_SUBCLASSES, Classfile.JAVA_15_VERSION) { + @Override + public PermittedSubclassesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundPermittedSubclassesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, PermittedSubclassesAttribute attr) { + buf.writeListIndices(attr.permittedSubclasses()); + } + }; + + /** Attribute mapper for the {@code Record} attribute */ + public static final AttributeMapper + RECORD = new AbstractAttributeMapper<>(NAME_RECORD, Classfile.JAVA_16_VERSION) { + @Override + public RecordAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRecordAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RecordAttribute attr) { + List components = attr.components(); + buf.writeU2(components.size()); + for (RecordComponentInfo info : components) { + buf.writeIndex(info.name()); + buf.writeIndex(info.descriptor()); + buf.writeList(info.attributes()); + } + } + }; + + /** Attribute mapper for the {@code RuntimeInvisibleAnnotations} attribute */ + public static final AttributeMapper + RUNTIME_INVISIBLE_ANNOTATIONS = new AbstractAttributeMapper<>(NAME_RUNTIME_INVISIBLE_ANNOTATIONS, Classfile.JAVA_5_VERSION) { + @Override + public RuntimeInvisibleAnnotationsAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + return new BoundAttribute.BoundRuntimeInvisibleAnnotationsAttribute(cf, pos); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeInvisibleAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + }; + + /** Attribute mapper for the {@code RuntimeInvisibleParameterAnnotations} attribute */ + public static final AttributeMapper + RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = new AbstractAttributeMapper<>(NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, Classfile.JAVA_5_VERSION) { + @Override + public RuntimeInvisibleParameterAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeInvisibleParameterAnnotationsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeInvisibleParameterAnnotationsAttribute attr) { + List> lists = attr.parameterAnnotations(); + buf.writeU1(lists.size()); + for (List list : lists) + buf.writeList(list); + } + }; + + /** Attribute mapper for the {@code RuntimeInvisibleTypeAnnotations} attribute */ + public static final AttributeMapper + RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = new AbstractAttributeMapper<>(NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, Classfile.JAVA_8_VERSION) { + @Override + public RuntimeInvisibleTypeAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeInvisibleTypeAnnotationsAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeInvisibleTypeAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + }; + + /** Attribute mapper for the {@code RuntimeVisibleAnnotations} attribute */ + public static final AttributeMapper + RUNTIME_VISIBLE_ANNOTATIONS = new AbstractAttributeMapper<>(NAME_RUNTIME_VISIBLE_ANNOTATIONS, Classfile.JAVA_5_VERSION) { + @Override + public RuntimeVisibleAnnotationsAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + return new BoundAttribute.BoundRuntimeVisibleAnnotationsAttribute(cf, pos); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeVisibleAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + }; + + /** Attribute mapper for the {@code RuntimeVisibleParameterAnnotations} attribute */ + public static final AttributeMapper + RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = new AbstractAttributeMapper<>(NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, Classfile.JAVA_5_VERSION) { + @Override + public RuntimeVisibleParameterAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeVisibleParameterAnnotationsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeVisibleParameterAnnotationsAttribute attr) { + List> lists = attr.parameterAnnotations(); + buf.writeU1(lists.size()); + for (List list : lists) + buf.writeList(list); + } + }; + + /** Attribute mapper for the {@code RuntimeVisibleTypeAnnotations} attribute */ + public static final AttributeMapper + RUNTIME_VISIBLE_TYPE_ANNOTATIONS = new AbstractAttributeMapper<>(NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, Classfile.JAVA_8_VERSION) { + @Override + public RuntimeVisibleTypeAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeVisibleTypeAnnotationsAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeVisibleTypeAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + }; + + /** Attribute mapper for the {@code Signature} attribute */ + public static final AttributeMapper + SIGNATURE = new AbstractAttributeMapper<>(NAME_SIGNATURE, Classfile.JAVA_5_VERSION) { + @Override + public SignatureAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSignatureAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SignatureAttribute attr) { + buf.writeIndex(attr.signature()); + } + }; + + /** Attribute mapper for the {@code SourceDebug} attribute */ + public static final AttributeMapper + SOURCE_DEBUG_EXTENSION = new AbstractAttributeMapper<>(NAME_SOURCE_DEBUG_EXTENSION, Classfile.JAVA_5_VERSION) { + @Override + public SourceDebugExtensionAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceDebugExtensionAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SourceDebugExtensionAttribute attr) { + buf.writeBytes(attr.contents()); + } + }; + + /** Attribute mapper for the {@code SourceFile} attribute */ + public static final AttributeMapper + SOURCE_FILE = new AbstractAttributeMapper<>(NAME_SOURCE_FILE) { + @Override + public SourceFileAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceFileAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SourceFileAttribute attr) { + buf.writeIndex(attr.sourceFile()); + } + }; + + /** Attribute mapper for the {@code SourceID} attribute */ + public static final AttributeMapper + SOURCE_ID = new AbstractAttributeMapper<>(NAME_SOURCE_ID) { + @Override + public SourceIDAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceIDAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SourceIDAttribute attr) { + buf.writeIndex(attr.sourceId()); + } + }; + + /** Attribute mapper for the {@code StackMapTable} attribute */ + public static final AttributeMapper + STACK_MAP_TABLE = new AbstractAttributeMapper<>(NAME_STACK_MAP_TABLE, Classfile.JAVA_6_VERSION) { + @Override + public StackMapTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundStackMapTableAttribute((CodeImpl)e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter b, StackMapTableAttribute attr) { + StackMapDecoder.writeFrames(b, attr.entries()); + } + }; + + + /** Attribute mapper for the {@code Synthetic} attribute */ + public static final AttributeMapper + SYNTHETIC = new AbstractAttributeMapper<>(NAME_SYNTHETIC) { + @Override + public SyntheticAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSyntheticAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SyntheticAttribute attr) { + // empty + } + }; + + /** + * {@return the attribute mapper for a standard attribute} + * + * @param name the name of the attribute to find + */ + public static AttributeMapper standardAttribute(Utf8Entry name) { + return _ATTR_MAP.get(name); + } + + /** + * All standard attribute mappers. + */ + public static final Set> PREDEFINED_ATTRIBUTES = Set.of( + ANNOTATION_DEFAULT, + BOOTSTRAP_METHODS, + CHARACTER_RANGE_TABLE, + CODE, + COMPILATION_ID, + CONSTANT_VALUE, + DEPRECATED, + ENCLOSING_METHOD, + EXCEPTIONS, + INNER_CLASSES, + LINE_NUMBER_TABLE, + LOCAL_VARIABLE_TABLE, + LOCAL_VARIABLE_TYPE_TABLE, + METHOD_PARAMETERS, + MODULE, + MODULE_HASHES, + MODULE_MAIN_CLASS, + MODULE_PACKAGES, + MODULE_RESOLUTION, + MODULE_TARGET, + NEST_HOST, + NEST_MEMBERS, + PERMITTED_SUBCLASSES, + RECORD, + RUNTIME_INVISIBLE_ANNOTATIONS, + RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, + RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, + RUNTIME_VISIBLE_ANNOTATIONS, + RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + RUNTIME_VISIBLE_TYPE_ANNOTATIONS, + SIGNATURE, + SOURCE_DEBUG_EXTENSION, + SOURCE_FILE, + SOURCE_ID, + STACK_MAP_TABLE, + SYNTHETIC); + + private static final Map> _ATTR_MAP; + //no lambdas here as this is on critical JDK boostrap path + static { + var map = new HashMap>(64); + for (var am : PREDEFINED_ATTRIBUTES) { + map.put(AbstractPoolEntry.rawUtf8EntryFromStandardAttributeName(am.name()), am); + } + _ATTR_MAP = Collections.unmodifiableMap(map); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/BootstrapMethodEntry.java b/src/java.base/share/classes/jdk/internal/classfile/BootstrapMethodEntry.java new file mode 100644 index 0000000000000..fb7a8d1401f38 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/BootstrapMethodEntry.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.util.List; + +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.impl.BootstrapMethodEntryImpl; + +/** + * Models an entry in the bootstrap method table. The bootstrap method table + * is stored in the {@code BootstrapMethods} attribute, but is modeled by + * the {@link ConstantPool}, since the bootstrap method table is logically + * part of the constant pool. + */ +public sealed interface BootstrapMethodEntry + extends WritableElement + permits BootstrapMethodEntryImpl { + + /** + * {@return the constant pool associated with this entry} + */ + ConstantPool constantPool(); + + /** + * {@return the index into the bootstrap method table corresponding to this entry} + */ + int bsmIndex(); + + /** + * {@return the bootstrap method} + */ + MethodHandleEntry bootstrapMethod(); + + /** + * {@return the bootstrap arguments} + */ + List arguments(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/BufWriter.java b/src/java.base/share/classes/jdk/internal/classfile/BufWriter.java new file mode 100644 index 0000000000000..a572dd16a53a8 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/BufWriter.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.nio.ByteBuffer; +import java.util.List; + +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.impl.BufWriterImpl; + +/** + * Supports writing portions of a classfile to a growable buffer. Method + * are provided to write various standard entities (e.g., {@code u2}, {@code u4}) + * to the end of the buffer, as well as to create constant pool entries. + */ +public sealed interface BufWriter + permits BufWriterImpl { + + /** {@return the constant pool builder associated with this buffer} */ + ConstantPoolBuilder constantPool(); + + /** + * {@return whether the provided constant pool is index-compatible with this + * one} This may be because they are the same constant pool, or because this + * constant pool was copied from the other. + * + * @param other the other constant pool + */ + boolean canWriteDirect(ConstantPool other); + + /** + * Ensure that the buffer has at least {@code freeBytes} bytes of unused space + * @param freeBytes the number of bytes to reserve + */ + void reserveSpace(int freeBytes); + + /** + * Write an unsigned byte to the buffer + * + * @param x the byte value + */ + void writeU1(int x); + + /** + * Write an unsigned short to the buffer + * + * @param x the short value + */ + void writeU2(int x); + + /** + * Write a signed int to the buffer + * + * @param x the int value + */ + void writeInt(int x); + + /** + * Write a float value to the buffer + * + * @param x the float value + */ + void writeFloat(float x); + + /** + * Write a long value to the buffer + * + * @param x the long value + */ + void writeLong(long x); + + /** + * Write a double value to the buffer + * + * @param x the int value + */ + void writeDouble(double x); + + /** + * Write the contents of a byte array to the buffer + * + * @param arr the byte array + */ + void writeBytes(byte[] arr); + + /** + * Write the contents of another {@link BufWriter} to the buffer + * + * @param other the other {@linkplain BufWriter} + */ + void writeBytes(BufWriter other); + + /** + * Write a range of a byte array to the buffer + * + * @param arr the byte array + * @param start the offset within the byte array of the range + * @param length the length of the range + */ + void writeBytes(byte[] arr, int start, int length); + + /** + * Patch a previously written integer value. Depending on the specified + * size, the entire value, or the low 1 or 2 bytes, may be written. + * + * @param offset the offset at which to patch + * @param size the size of the integer value being written, in bytes + * @param value the integer value + */ + void patchInt(int offset, int size, int value); + + /** + * Write a 1, 2, 4, or 8 byte integer value to the buffer. Depending on + * the specified size, the entire value, or the low 1, 2, or 4 bytes, may + * be written. + * + * @param intSize the size of the integer value being written, in bytes + * @param intValue the integer value + */ + void writeIntBytes(int intSize, long intValue); + + /** + * Write the index of the specified constant pool entry, as a {@code u2}, + * to the buffer + * + * @param entry the constant pool entry + * @throws NullPointerException if the entry is null + */ + void writeIndex(PoolEntry entry); + + /** + * Write the index of the specified constant pool entry, as a {@code u2}, + * to the buffer, or zero if the entry is null + * + * @param entry the constant pool entry + */ + void writeIndexOrZero(PoolEntry entry); + + /** + * Write a list of entities to the buffer. The length of the list is + * written as a {@code u2}, followed by the bytes corresponding to each + * element in the list. Writing of the entities is delegated to the entry. + * + * @param list the entities + * @param the type of entity + */ + > void writeList(List list); + + /** + * Write a list of constant pool entry indexes to the buffer. The length + * of the list is written as a {@code u2}, followed by a {@code u2} for each + * entry in the list. + * + * @param list the list of entries + */ + void writeListIndices(List list); + + /** + * {@return the number of bytes that have been written to the buffer} + */ + int size(); + + /** + * {@return a {@link java.nio.ByteBuffer ByteBuffer} view of the bytes in the buffer} + */ + ByteBuffer asByteBuffer(); + + /** + * Copy the contents of the buffer into a byte array. + * + * @param array the byte array + * @param bufferOffset the offset into the array at which to write the + * contents of the buffer + */ + void copyTo(byte[] array, int bufferOffset); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/ClassBuilder.java new file mode 100644 index 0000000000000..13af4df047aba --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassBuilder.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.ChainedClassBuilder; +import jdk.internal.classfile.impl.DirectClassBuilder; +import jdk.internal.classfile.impl.Util; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.attribute.CodeAttribute; + +/** + * A builder for classfiles. Builders are not created directly; they are passed + * to handlers by methods such as {@link Classfile#build(ClassDesc, Consumer)} + * or to class transforms. The elements of a classfile can be specified + * abstractly (by passing a {@link ClassElement} to {@link #with(ClassfileElement)}) + * or concretely by calling the various {@code withXxx} methods. + * + * @see ClassTransform + */ +public sealed interface ClassBuilder + extends ClassfileBuilder + permits ChainedClassBuilder, DirectClassBuilder { + + /** + * {@return the {@link ClassModel} representing the class being transformed, + * if this class builder represents the transformation of some {@link ClassModel}} + */ + Optional original(); + + /** + * Sets the classfile version. + * @param major the major version number + * @param minor the minor version number + * @return this builder + */ + default ClassBuilder withVersion(int major, int minor) { + return with(ClassfileVersion.of(major, minor)); + } + + /** + * Sets the classfile access flags. + * @param flags the access flags, as a bit mask + * @return this builder + */ + default ClassBuilder withFlags(int flags) { + return with(AccessFlags.ofClass(flags)); + } + + /** + * Sets the classfile access flags. + * @param flags the access flags + * @return this builder + */ + default ClassBuilder withFlags(AccessFlag... flags) { + return with(AccessFlags.ofClass(flags)); + } + + /** + * Sets the superclass of this class. + * @param superclassEntry the superclass + * @return this builder + */ + default ClassBuilder withSuperclass(ClassEntry superclassEntry) { + return with(Superclass.of(superclassEntry)); + } + + /** + * Sets the superclass of this class. + * @param desc the superclass + * @return this builder + */ + default ClassBuilder withSuperclass(ClassDesc desc) { + return withSuperclass(constantPool().classEntry(desc)); + } + + /** + * Sets the interfaces of this class. + * @param interfaces the interfaces + * @return this builder + */ + default ClassBuilder withInterfaces(List interfaces) { + return with(Interfaces.of(interfaces)); + } + + /** + * Sets the interfaces of this class. + * @param interfaces the interfaces + * @return this builder + */ + default ClassBuilder withInterfaces(ClassEntry... interfaces) { + return withInterfaces(List.of(interfaces)); + } + + /** + * Sets the interfaces of this class. + * @param interfaces the interfaces + * @return this builder + */ + default ClassBuilder withInterfaceSymbols(List interfaces) { + return withInterfaces(Util.entryList(interfaces)); + } + + /** + * Sets the interfaces of this class. + * @param interfaces the interfaces + * @return this builder + */ + default ClassBuilder withInterfaceSymbols(ClassDesc... interfaces) { + // List view, since ref to interfaces is temporary + return withInterfaceSymbols(Arrays.asList(interfaces)); + } + + /** + * Adds a field. + * @param name the name of the field + * @param descriptor the field descriptor + * @param handler handler which receives a {@link FieldBuilder} which can + * further define the contents of the field + * @return this builder + */ + ClassBuilder withField(Utf8Entry name, + Utf8Entry descriptor, + Consumer handler); + + /** + * Adds a field. + * @param name the name of the field + * @param descriptor the field descriptor + * @param flags the access flags for this field + * @return this builder + */ + default ClassBuilder withField(Utf8Entry name, + Utf8Entry descriptor, + int flags) { + return withField(name, descriptor, fb -> fb.withFlags(flags)); + } + + /** + * Adds a field. + * @param name the name of the field + * @param descriptor the field descriptor + * @param handler handler which receives a {@link FieldBuilder} which can + * further define the contents of the field + * @return this builder + */ + default ClassBuilder withField(String name, + ClassDesc descriptor, + Consumer handler) { + return withField(constantPool().utf8Entry(name), + constantPool().utf8Entry(descriptor), + handler); + } + + /** + * Adds a field. + * @param name the name of the field + * @param descriptor the field descriptor + * @param flags the access flags for this field + * @return this builder + */ + default ClassBuilder withField(String name, + ClassDesc descriptor, + int flags) { + return withField(name, descriptor, fb -> fb.withFlags(flags)); + } + + /** + * Adds a field by transforming a field from another class. + * + * @implNote + *

This method behaves as if: + * {@snippet lang=java : + * withField(field.fieldName(), field.fieldType(), + * b -> b.transformField(field, transform)); + * } + * + * @param field the field to be transformed + * @param transform the transform to apply to the field + * @return this builder + */ + ClassBuilder transformField(FieldModel field, FieldTransform transform); + + /** + * Adds a method. + * @param name the name of the method + * @param descriptor the method descriptor + * @param methodFlags the access flags + * @param handler handler which receives a {@link MethodBuilder} which can + * further define the contents of the method + * @return this builder + */ + ClassBuilder withMethod(Utf8Entry name, + Utf8Entry descriptor, + int methodFlags, + Consumer handler); + + /** + * Adds a method, with only a {@code Code} attribute. + * + * @param name the name of the method + * @param descriptor the method descriptor + * @param methodFlags the access flags + * @param handler handler which receives a {@link CodeBuilder} which can + * define the contents of the method body + * @return this builder + */ + default ClassBuilder withMethodBody(Utf8Entry name, + Utf8Entry descriptor, + int methodFlags, + Consumer handler) { + return withMethod(name, descriptor, methodFlags, mb -> mb.withCode(handler)); + } + + /** + * Adds a method. + * @param name the name of the method + * @param descriptor the method descriptor + * @param methodFlags the access flags + * @param handler handler which receives a {@link MethodBuilder} which can + * further define the contents of the method + * @return this builder + */ + default ClassBuilder withMethod(String name, + MethodTypeDesc descriptor, + int methodFlags, + Consumer handler) { + return withMethod(constantPool().utf8Entry(name), + constantPool().utf8Entry(descriptor), + methodFlags, + handler); + } + + /** + * Adds a method, with only a {@link CodeAttribute}. + * @param name the name of the method + * @param descriptor the method descriptor + * @param methodFlags the access flags + * @param handler handler which receives a {@link CodeBuilder} which can + * define the contents of the method body + * @return this builder + */ + default ClassBuilder withMethodBody(String name, + MethodTypeDesc descriptor, + int methodFlags, + Consumer handler) { + return withMethodBody(constantPool().utf8Entry(name), + constantPool().utf8Entry(descriptor), + methodFlags, + handler); + } + + /** + * Adds a method by transforming a method from another class. + * + * @implNote + *

This method behaves as if: + * {@snippet lang=java : + * withMethod(method.methodName(), method.methodType(), + * b -> b.transformMethod(method, transform)); + * } + * @param method the method to be transformed + * @param transform the transform to apply to the method + * @return this builder + */ + ClassBuilder transformMethod(MethodModel method, MethodTransform transform); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassElement.java b/src/java.base/share/classes/jdk/internal/classfile/ClassElement.java new file mode 100644 index 0000000000000..29c07cafcaaa0 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassElement.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.CompilationIDAttribute; +import jdk.internal.classfile.attribute.DeprecatedAttribute; +import jdk.internal.classfile.attribute.EnclosingMethodAttribute; +import jdk.internal.classfile.attribute.InnerClassesAttribute; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleHashesAttribute; +import jdk.internal.classfile.attribute.ModuleMainClassAttribute; +import jdk.internal.classfile.attribute.ModulePackagesAttribute; +import jdk.internal.classfile.attribute.ModuleResolutionAttribute; +import jdk.internal.classfile.attribute.ModuleTargetAttribute; +import jdk.internal.classfile.attribute.NestHostAttribute; +import jdk.internal.classfile.attribute.NestMembersAttribute; +import jdk.internal.classfile.attribute.PermittedSubclassesAttribute; +import jdk.internal.classfile.attribute.RecordAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.classfile.attribute.SourceIDAttribute; +import jdk.internal.classfile.attribute.SyntheticAttribute; +import jdk.internal.classfile.attribute.UnknownAttribute; + +/** + * A {@link ClassfileElement} that can appear when traversing the elements + * of a {@link ClassModel} or be presented to a {@link ClassBuilder}. + */ +public sealed interface ClassElement extends ClassfileElement + permits AccessFlags, Superclass, Interfaces, ClassfileVersion, + FieldModel, MethodModel, + CustomAttribute, CompilationIDAttribute, DeprecatedAttribute, + EnclosingMethodAttribute, InnerClassesAttribute, + ModuleAttribute, ModuleHashesAttribute, ModuleMainClassAttribute, + ModulePackagesAttribute, ModuleResolutionAttribute, ModuleTargetAttribute, + NestHostAttribute, NestMembersAttribute, PermittedSubclassesAttribute, + RecordAttribute, + RuntimeInvisibleAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, + RuntimeVisibleAnnotationsAttribute, RuntimeVisibleTypeAnnotationsAttribute, + SignatureAttribute, SourceDebugExtensionAttribute, + SourceFileAttribute, SourceIDAttribute, SyntheticAttribute, UnknownAttribute { +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java b/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java new file mode 100644 index 0000000000000..1e74393dd9ed9 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.io.InputStream; +import java.lang.constant.ClassDesc; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import jdk.internal.classfile.impl.Util; + +import jdk.internal.classfile.impl.ClassHierarchyImpl; + +/** + * Provides class hierarchy information for generating correct stack maps + * during code building. + */ +@FunctionalInterface +public interface ClassHierarchyResolver { + + /** + * Default singleton instance of {@linkplain ClassHierarchyResolver} + * using {@link ClassLoader#getSystemResourceAsStream(String)} + * as the {@code ClassStreamResolver} + */ + ClassHierarchyResolver DEFAULT_CLASS_HIERARCHY_RESOLVER + = new ClassHierarchyImpl.CachedClassHierarchyResolver( + new Function() { + @Override + public InputStream apply(ClassDesc classDesc) { + return ClassLoader.getSystemResourceAsStream(Util.toInternalName(classDesc) + ".class"); + } + }); + + /** + * {@return the {@link ClassHierarchyInfo} for a given class name, or null + * if the name is unknown to the resolver} + * @param classDesc descriptor of the class + */ + ClassHierarchyInfo getClassInfo(ClassDesc classDesc); + + /** + * Chains this {@linkplain ClassHierarchyResolver} with another to be + * consulted if this resolver does not know about the specified class. + * + * @param other the other resolver + * @return the chained resolver + */ + default ClassHierarchyResolver orElse(ClassHierarchyResolver other) { + return new ClassHierarchyResolver() { + @Override + public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + var chi = ClassHierarchyResolver.this.getClassInfo(classDesc); + if (chi == null) + chi = other.getClassInfo(classDesc); + return chi; + } + }; + } + + /** + * Information about a resolved class. + * @param thisClass descriptor of this class + * @param isInterface whether this class is an interface + * @param superClass descriptor of the superclass (not relevant for interfaces) + */ + public record ClassHierarchyInfo(ClassDesc thisClass, boolean isInterface, ClassDesc superClass) { + } + + /** + * Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy + * information from classfiles located by a mapping function + * + * @param classStreamResolver maps class descriptors to classfile input streams + * @return the {@linkplain ClassHierarchyResolver} + */ + public static ClassHierarchyResolver ofCached(Function classStreamResolver) { + return new ClassHierarchyImpl.CachedClassHierarchyResolver(classStreamResolver); + } + + /** + * Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy + * information from collections of class hierarchy metadata + * + * @param interfaces a collection of classes known to be interfaces + * @param classToSuperClass a map from classes to their super classes + * @return the {@linkplain ClassHierarchyResolver} + */ + public static ClassHierarchyResolver of(Collection interfaces, + Map classToSuperClass) { + return new ClassHierarchyImpl.StaticClassHierarchyResolver(interfaces, classToSuperClass); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassModel.java b/src/java.base/share/classes/jdk/internal/classfile/ClassModel.java new file mode 100644 index 0000000000000..b2371cca7a697 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassModel.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.impl.ClassImpl; +import jdk.internal.classfile.impl.verifier.VerifierImpl; + +/** + * Models a classfile. The contents of the classfile can be traversed via + * a streaming view (e.g., {@link #elements()}), or via random access (e.g., + * {@link #flags()}), or by freely mixing the two. + */ +public sealed interface ClassModel + extends CompoundElement, AttributedElement + permits ClassImpl { + + /** + * {@return the constant pool for this class} + */ + ConstantPool constantPool(); + + /** {@return the access flags} */ + AccessFlags flags(); + + /** {@return the constant pool entry describing the name of this class} */ + ClassEntry thisClass(); + + /** {@return the major classfile version} */ + int majorVersion(); + + /** {@return the minor classfile version} */ + int minorVersion(); + + /** {@return the fields of this class} */ + List fields(); + + /** {@return the methods of this class} */ + List methods(); + + /** {@return the superclass of this class, if there is one} */ + Optional superclass(); + + /** {@return the interfaces implemented by this class} */ + List interfaces(); + + /** + * Transform this classfile into a new classfile with the aid of a + * {@link ClassTransform}. The transform will receive each element of + * this class, as well as a {@link ClassBuilder} for building the new class. + * The transform is free to preserve, remove, or replace elements as it + * sees fit. + * + * @implNote + *

This method behaves as if: + * {@snippet lang=java : + * Classfile.build(thisClass(), ConstantPoolBuilder.of(this), + * b -> b.transform(this, transform)); + * } + * + * @param transform the transform + * @return the bytes of the new class + */ + byte[] transform(ClassTransform transform); + + /** {@return whether this class is a module descriptor} */ + boolean isModuleInfo(); + + /** + * Verify this classfile. Any verification errors found will be returned. + * + * @param debugOutput handler to receive debug information + * @return a list of verification errors, or an empty list if no errors are + * found + */ + default List verify(Consumer debugOutput) { + return VerifierImpl.verify(this, debugOutput); + } + + /** + * Verify this classfile. Any verification errors found will be returned. + * + * @param debugOutput handler to receive debug information + * @param classHierarchyResolver class hierarchy resolver to provide + * additional information about the class hiearchy + * @return a list of verification errors, or an empty list if no errors are + * found + */ + default List verify(ClassHierarchyResolver classHierarchyResolver, + Consumer debugOutput) { + return VerifierImpl.verify(this, classHierarchyResolver, debugOutput); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassReader.java b/src/java.base/share/classes/jdk/internal/classfile/ClassReader.java new file mode 100644 index 0000000000000..20da4743def32 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassReader.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.ClassReaderImpl; + +import java.util.Optional; +import java.util.function.Function; + +/** + * Supports reading from a classfile. Methods are provided to read data of + * various numeric types (e.g., {@code u2}, {@code u4}) at a given offset within + * the classfile, copying raw bytes, and reading constant pool entries. + * Encapsulates additional reading context such as mappers for custom attributes + * and processing options. + */ +public sealed interface ClassReader extends ConstantPool + permits ClassReaderImpl { + + // Processing context + + /** + * {@return the table of custom attribute mappers} This is derived from + * the processing option {@link Classfile.Option#attributeMapper(Function)}. + */ + Function> customAttributes(); + + // Class context + + /** {@return the access flags for the class, as a bit mask } */ + int flags(); + + /** {@return the constant pool entry describing the name of class} */ + ClassEntry thisClassEntry(); + + /** {@return the constant pool entry describing the name of the superclass, if any} */ + Optional superclassEntry(); + + /** {@return the offset into the classfile of the {@code this_class} field} */ + int thisClassPos(); + + /** {@return the length of the classfile, in bytes} */ + int classfileLength(); + + // Buffer related + + /** + * {@return the offset following the block of attributes starting at the + * specified position} + * @param offset the offset into the classfile at which the attribute block + * starts + */ + int skipAttributeHolder(int offset); + + // Constant pool + + /** + * {@return the UTF8 constant pool entry at the given index of the constant + * pool} The given index must correspond to a valid constant pool index + * whose slot holds a UTF8 constant. + * @param index the index into the constant pool + */ + Utf8Entry utf8EntryByIndex(int index); + + /** + * {@return the constant pool entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + */ + PoolEntry readEntry(int offset); + + /** + * {@return the constant pool entry whose index is given at the specified + * offset within the classfile, or null if the index at the specified + * offset is zero} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size + */ + PoolEntry readEntryOrNull(int offset); + + /** + * {@return the UTF8 entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + * @throws IllegalArgumentException if the index does not correspond to + * a UTF8 entry + */ + Utf8Entry readUtf8Entry(int offset); + + /** + * {@return the UTF8 entry whose index is given at the specified + * offset within the classfile, or null if the index at the specified + * offset is zero} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size + * @throws IllegalArgumentException if the index does not correspond to + * a UTF8 entry + */ + Utf8Entry readUtf8EntryOrNull(int offset); + + /** + * {@return the module entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + * @throws IllegalArgumentException if the index does not correspond to + * a module entry + */ + ModuleEntry readModuleEntry(int offset); + + /** + * {@return the package entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + * @throws IllegalArgumentException if the index does not correspond to + * a package entry + */ + PackageEntry readPackageEntry(int offset); + + /** + * {@return the class entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + * @throws IllegalArgumentException if the index does not correspond to + * a class entry + */ + ClassEntry readClassEntry(int offset); + + /** + * {@return the name-and-type entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + * @throws IllegalArgumentException if the index does not correspond to + * a name-and-type entry + */ + NameAndTypeEntry readNameAndTypeEntry(int offset); + + /** + * {@return the method handle entry whose index is given at the specified + * offset within the classfile} + * @param offset the offset of the index within the classfile + * @throws IndexOutOfBoundsException if the index is out of range of the + * constant pool size, or zero + * @throws IllegalArgumentException if the index does not correspond to + * a method handle entry + */ + MethodHandleEntry readMethodHandleEntry(int offset); + + /** + * {@return the unsigned byte at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + int readU1(int offset); + + /** + * {@return the unsigned short at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + int readU2(int offset); + + /** + * {@return the signed byte at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + int readS1(int offset); + + /** + * {@return the signed byte at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + int readS2(int offset); + + /** + * {@return the signed int at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + int readInt(int offset); + + /** + * {@return the signed long at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + long readLong(int offset); + + /** + * {@return the float value at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + float readFloat(int offset); + + /** + * {@return the double value at the specified offset within the classfile} + * @param offset the offset within the classfile + */ + double readDouble(int offset); + + /** + * {@return a copy of the bytes at the specified range in the classfile} + * @param offset the offset within the classfile + * @param len the length of the range + */ + byte[] readBytes(int offset, int len); + + /** + * Copy a range of bytes from the classfile to a {@link BufWriter} + * + * @param buf the {@linkplain BufWriter} + * @param offset the offset within the classfile + * @param len the length of the range + */ + void copyBytesTo(BufWriter buf, int offset, int len); + + /** + * Compare a range of bytes from the classfile to a range of bytes within + * a {@link BufWriter}. + * + * @param bufWriter the {@linkplain BufWriter} + * @param bufWriterOffset the offset within the {@linkplain BufWriter} + * @param classReaderOffset the offset within the classfile + * @param length the length of the range + * @return whether the two ranges were identical + */ + boolean compare(BufWriter bufWriter, + int bufWriterOffset, + int classReaderOffset, + int length); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassSignature.java b/src/java.base/share/classes/jdk/internal/classfile/ClassSignature.java new file mode 100644 index 0000000000000..8199d67ed096d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassSignature.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.List; +import jdk.internal.classfile.impl.SignaturesImpl; +import static java.util.Objects.requireNonNull; + +/** + * Models the generic signature of a class file, as defined by {@jvms 4.7.9}. + */ +public sealed interface ClassSignature + permits SignaturesImpl.ClassSignatureImpl { + + /** {@return the type parameters of this class} */ + List typeParameters(); + + /** {@return the instantiation of the superclass in this signature} */ + Signature.RefTypeSig superclassSignature(); + + /** {@return the instantiation of the interfaces in this signature} */ + List superinterfaceSignatures(); + + /** {@return the raw signature string} */ + String signatureString(); + + /** + * @return class signature + * @param superclassSignature the superclass + * @param superinterfaceSignatures the interfaces + */ + public static ClassSignature of(Signature.RefTypeSig superclassSignature, + Signature.RefTypeSig... superinterfaceSignatures) { + return of(List.of(), superclassSignature, superinterfaceSignatures); + } + + /** + * @return class signature + * @param typeParameters the type parameters + * @param superclassSignature the superclass + * @param superinterfaceSignatures the interfaces + */ + public static ClassSignature of(List typeParameters, + Signature.RefTypeSig superclassSignature, + Signature.RefTypeSig... superinterfaceSignatures) { + return new SignaturesImpl.ClassSignatureImpl( + requireNonNull(typeParameters), + requireNonNull(superclassSignature), + List.of(superinterfaceSignatures)); + } + + /** + * Parses a raw class signature string into a {@linkplain Signature} + * @param classSignature the raw class signature string + * @return class signature + */ + public static ClassSignature parseFrom(String classSignature) { + return new SignaturesImpl().parseClassSignature(requireNonNull(classSignature)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassTransform.java b/src/java.base/share/classes/jdk/internal/classfile/ClassTransform.java new file mode 100644 index 0000000000000..8d714ebed61c7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassTransform.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.impl.TransformImpl; + +/** + * A transformation on streams of {@link ClassElement}. + * + * @see ClassfileTransform + */ +@FunctionalInterface +public non-sealed interface ClassTransform + extends ClassfileTransform { + + /** + * A class transform that sends all elements to the builder. + */ + static final ClassTransform ACCEPT_ALL = new ClassTransform() { + @Override + public void accept(ClassBuilder builder, ClassElement element) { + builder.with(element); + } + }; + + /** + * Create a stateful class transform from a {@link Supplier}. The supplier + * will be invoked for each transformation. + * + * @param supplier a {@link Supplier} that produces a fresh transform object + * for each traversal + * @return the stateful class transform + */ + static ClassTransform ofStateful(Supplier supplier) { + return new TransformImpl.SupplierClassTransform(supplier); + } + + /** + * Create a class transform that passes each element through to the builder, + * and calls the specified function when transformation is complete. + * + * @param finisher the function to call when transformation is complete + * @return the class transform + */ + static ClassTransform endHandler(Consumer finisher) { + return new ClassTransform() { + @Override + public void accept(ClassBuilder builder, ClassElement element) { + builder.with(element); + } + + @Override + public void atEnd(ClassBuilder builder) { + finisher.accept(builder); + } + }; + } + + /** + * Create a class transform that passes each element through to the builder, + * except for those that the supplied {@link Predicate} is true for. + * + * @param filter the predicate that determines which elements to drop + * @return the class transform + */ + static ClassTransform dropping(Predicate filter) { + return (b, e) -> { + if (!filter.test(e)) + b.with(e); + }; + } + + /** + * Create a class transform that transforms {@link MethodModel} elements + * with the supplied method transform. + * + * @param filter a predicate that determines which methods to transform + * @param xform the method transform + * @return the class transform + */ + static ClassTransform transformingMethods(Predicate filter, + MethodTransform xform) { + return new TransformImpl.ClassMethodTransform(xform, filter); + } + + /** + * Create a class transform that transforms {@link MethodModel} elements + * with the supplied method transform. + * + * @param xform the method transform + * @return the class transform + */ + static ClassTransform transformingMethods(MethodTransform xform) { + return transformingMethods(mm -> true, xform); + } + + /** + * Create a class transform that transforms the {@link CodeAttribute} (method body) + * of {@link MethodModel} elements with the supplied code transform. + * + * @param filter a predicate that determines which methods to transform + * @param xform the code transform + * @return the class transform + */ + static ClassTransform transformingMethodBodies(Predicate filter, + CodeTransform xform) { + return transformingMethods(filter, MethodTransform.transformingCode(xform)); + } + + /** + * Create a class transform that transforms the {@link CodeAttribute} (method body) + * of {@link MethodModel} elements with the supplied code transform. + * + * @param xform the code transform + * @return the class transform + */ + static ClassTransform transformingMethodBodies(CodeTransform xform) { + return transformingMethods(MethodTransform.transformingCode(xform)); + } + + /** + * Create a class transform that transforms {@link FieldModel} elements + * with the supplied field transform. + * + * @param xform the field transform + * @return the class transform + */ + static ClassTransform transformingFields(FieldTransform xform) { + return new TransformImpl.ClassFieldTransform(xform, f -> true); + } + + @Override + default ClassTransform andThen(ClassTransform t) { + return new TransformImpl.ChainedClassTransform(this, t); + } + + @Override + default ResolvedTransform resolve(ClassBuilder builder) { + return new TransformImpl.ResolvedTransformImpl<>(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Classfile.java b/src/java.base/share/classes/jdk/internal/classfile/Classfile.java new file mode 100644 index 0000000000000..a4bd38adb25fb --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Classfile.java @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.UnknownAttribute; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.ClassImpl; +import jdk.internal.classfile.impl.AbstractPoolEntry; +import jdk.internal.classfile.impl.DirectClassBuilder; +import jdk.internal.classfile.impl.Options; +import jdk.internal.classfile.impl.SplitConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.attribute.CharacterRangeInfo; +import jdk.internal.classfile.attribute.LocalVariableInfo; +import jdk.internal.classfile.attribute.LocalVariableTypeInfo; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.java.lang.constant.PackageDesc; + +/** + * Main entry points for parsing, transforming, and generating classfiles. + */ +public class Classfile { + private Classfile() { + } + + /** + * An option that affects the writing of classfiles. + */ + public sealed interface Option permits Options.OptionValue { + + /** + * {@return an option describing whether or not to generate stackmaps} + * Default is to generate stack maps. + * @param b whether to generate stack maps + */ + static Option generateStackmap(boolean b) { return new Options.OptionValue(Options.Key.GENERATE_STACK_MAPS, b); } + + /** + * {@return an option describing whether to process or discard debug elements} + * Debug elements include the local variable table, local variable type + * table, and character range table. Discarding debug elements may + * reduce the overhead of parsing or transforming classfiles. + * Default is to process debug elements. + * @param b whether or not to process debug elements + */ + static Option processDebug(boolean b) { return new Options.OptionValue(Options.Key.PROCESS_DEBUG, b); } + + /** + * {@return an option describing whether to process or discard line numbers} + * Discarding line numbers may reduce the overhead of parsing or transforming + * classfiles. + * Default is to process line numbers. + * @param b whether or not to process line numbers + */ + static Option processLineNumbers(boolean b) { return new Options.OptionValue(Options.Key.PROCESS_LINE_NUMBERS, b); } + + /** + * {@return an option describing whether to process or discard unrecognized + * attributes} + * Default is to process unrecognized attributes, and deliver as instances + * of {@link UnknownAttribute}. + * @param b whether or not to process unrecognized attributes + */ + static Option processUnknownAttributes(boolean b) { return new Options.OptionValue(Options.Key.PROCESS_UNKNOWN_ATTRIBUTES, b); } + + /** + * {@return an option describing whether to preserve the original constant + * pool when transforming a classfile} Reusing the constant pool enables significant + * optimizations in processing time and minimizes differences between the + * original and transformed classfile, but may result in a bigger classfile + * when a classfile is significantly transformed. + * Default is to preserve the original constant pool. + * @param b whether or not to preserve the original constant pool + */ + static Option constantPoolSharing(boolean b) { return new Options.OptionValue(Options.Key.CP_SHARING, b); } + + /** + * {@return an option describing whether or not to automatically rewrite + * short jumps to long when necessary} + * Default is to automatically rewrite jump instructions. + * @param b whether or not to automatically rewrite short jumps to long when necessary + */ + static Option fixShortJumps(boolean b) { return new Options.OptionValue(Options.Key.FIX_SHORT_JUMPS, b); } + + /** + * {@return an option describing whether or not to patch out unreachable code} + * Default is to automatically patch out unreachable code with NOPs. + * @param b whether or not to automatically patch out unreachable code + */ + static Option patchDeadCode(boolean b) { return new Options.OptionValue(Options.Key.PATCH_DEAD_CODE, b); } + + /** + * {@return an option describing the class hierarchy resolver to use when + * generating stack maps} + * @param r the resolver + */ + static Option classHierarchyResolver(ClassHierarchyResolver r) { return new Options.OptionValue(Options.Key.HIERARCHY_RESOLVER, r); } + + /** + * {@return an option describing attribute mappers for custom attributes} + * Default is only to process standard attributes. + * @param r a function mapping attribute names to attribute mappers + */ + static Option attributeMapper(Function> r) { return new Options.OptionValue(Options.Key.ATTRIBUTE_MAPPER, r); } + + /** + * {@return an option describing whether or not to filter unresolved labels} + * Default is to throw IllegalStateException when any {@link ExceptionCatch}, + * {@link LocalVariableInfo}, {@link LocalVariableTypeInfo}, or {@link CharacterRangeInfo} + * reference to unresolved {@link Label} during bytecode serialization. + * Setting this option to true filters the above elements instead. + * @param b whether or not to automatically patch out unreachable code + */ + static Option filterDeadLabels(boolean b) { return new Options.OptionValue(Options.Key.FILTER_DEAD_LABELS, b); } + } + + /** + * Parse a classfile into a {@link ClassModel}. + * @param bytes the bytes of the classfile + * @param options the desired processing options + * @return the class model + */ + public static ClassModel parse(byte[] bytes, Option... options) { + Collection

The subtypes of {@linkplain + * ClassfileTransform} (e.g., {@link ClassTransform}) are functional interfaces + * that accept an element and a corresponding builder. Since any element can be + * reproduced on the builder via {@link ClassBuilder#with(ClassfileElement)}, a + * transform can easily leave elements in place, remove them, replace them, or + * augment them with other elements. This enables localized transforms to be + * represented concisely. + * + *

Transforms also have an {@link #atEnd(ClassfileBuilder)} method, for + * which the default implementation does nothing, so that a transform can + * perform additional building after the stream of elements is exhausted. + * + *

Transforms can be chained together via the {@link + * #andThen(ClassfileTransform)} method, so that the output of one becomes the + * input to another. This allows smaller units of transformation to be captured + * and reused. + * + *

Some transforms are stateful; for example, a transform that injects an + * annotation on a class may watch for the {@link RuntimeVisibleAnnotationsAttribute} + * element and transform it if found, but if it is not found, will generate a + * {@linkplain RuntimeVisibleAnnotationsAttribute} element containing the + * injected annotation from the {@linkplain #atEnd(ClassfileBuilder)} handler. + * To do this, the transform must accumulate some state during the traversal so + * that the end handler knows what to do. If such a transform is to be reused, + * its state must be reset for each traversal; this will happen automatically if + * the transform is created with {@link ClassTransform#ofStateful(Supplier)} (or + * corresponding methods for other classfile locations.) + *

+ * Class transformation sample where code transformation is stateful: + * {@snippet lang="java" class="PackageSnippets" region="codeRelabeling"} + *

+ * Complex class instrumentation sample chaining multiple transformations: + * {@snippet lang="java" class="PackageSnippets" region="classInstrumentation"} + */ +public sealed interface ClassfileTransform< + C extends ClassfileTransform, + E extends ClassfileElement, + B extends ClassfileBuilder> + permits ClassTransform, FieldTransform, MethodTransform, CodeTransform { + /** + * Transform an element by taking the appropriate actions on the builder. + * Used when transforming a classfile entity (class, method, field, method + * body.) If no transformation is desired, the element can be presented to + * {@link B#with(ClassfileElement)}. If the element is to be dropped, no + * action is required. + * + * @param builder the builder for the new entity + * @param element the element + */ + void accept(B builder, E element); + + /** + * Take any final action during transformation of a classfile entity. Called + * after all elements of the class are presented to {@link + * #accept(ClassfileBuilder, ClassfileElement)}. + * + * @param builder the builder for the new entity + * @implSpec The default implementation does nothing. + */ + default void atEnd(B builder) { + } + + /** + * Take any preliminary action during transformation of a classfile entity. + * Called before any elements of the class are presented to {@link + * #accept(ClassfileBuilder, ClassfileElement)}. + * + * @param builder the builder for the new entity + * @implSpec The default implementation does nothing. + */ + default void atStart(B builder) { + } + + /** + * Chain this transform with another; elements presented to the builder of + * this transform will become the input to the next transform. + * + * @param next the downstream transform + * @return the chained transform + */ + C andThen(C next); + + /** + * The result of binding a transform to a builder. Used primarily within + * the implementation to perform transformation. + * + * @param the element type + */ + interface ResolvedTransform { + /** + * {@return a {@link Consumer} to receive elements} + */ + Consumer consumer(); + + /** + * {@return an action to call at the end of transformation} + */ + Runnable endHandler(); + + /** + * {@return an action to call at the start of transformation} + */ + Runnable startHandler(); + } + + /** + * Bind a transform to a builder. If the transform is chained, intermediate + * builders are created for each chain link. If the transform is stateful + * (see, e.g., {@link ClassTransform#ofStateful(Supplier)}), the supplier is + * invoked to get a fresh transform object. + * + *

This method is a low-level method that should rarely be used by + * user code; most of the time, user code should prefer + * {@link ClassfileBuilder#transform(CompoundElement, ClassfileTransform)}, + * which resolves the transform and executes it on the current builder. + * + * @param builder the builder to bind to + * @return the bound result + */ + ResolvedTransform resolve(B builder); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassfileVersion.java b/src/java.base/share/classes/jdk/internal/classfile/ClassfileVersion.java new file mode 100644 index 0000000000000..a3989e92b2686 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassfileVersion.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.impl.ClassfileVersionImpl; + +/** + * Models the classfile version information for a class. Delivered as a {@link + * jdk.internal.classfile.ClassElement} when traversing the elements of a {@link + * ClassModel}. + */ +public sealed interface ClassfileVersion + extends ClassElement + permits ClassfileVersionImpl { + /** + * {@return the major classfile version} + */ + int majorVersion(); + + /** + * {@return the minor classfile version} + */ + int minorVersion(); + + /** + * {@return a {@link ClassfileVersion} element} + * @param majorVersion the major classfile version + * @param minorVersion the minor classfile version + */ + static ClassfileVersion of(int majorVersion, int minorVersion) { + return new ClassfileVersionImpl(majorVersion, minorVersion); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/CodeBuilder.java new file mode 100644 index 0000000000000..6f8273495edb7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/CodeBuilder.java @@ -0,0 +1,1385 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BlockCodeBuilderImpl; +import jdk.internal.classfile.impl.BytecodeHelpers; +import jdk.internal.classfile.impl.CatchBuilderImpl; +import jdk.internal.classfile.impl.ChainedCodeBuilder; +import jdk.internal.classfile.impl.LabelImpl; +import jdk.internal.classfile.impl.NonterminalCodeBuilder; +import jdk.internal.classfile.impl.TerminalCodeBuilder; +import jdk.internal.classfile.instruction.ArrayLoadInstruction; +import jdk.internal.classfile.instruction.ArrayStoreInstruction; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.CharacterRange; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.ConvertInstruction; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.IncrementInstruction; +import jdk.internal.classfile.instruction.InvokeDynamicInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; +import jdk.internal.classfile.instruction.LineNumber; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; +import jdk.internal.classfile.instruction.LookupSwitchInstruction; +import jdk.internal.classfile.instruction.MonitorInstruction; +import jdk.internal.classfile.instruction.NewMultiArrayInstruction; +import jdk.internal.classfile.instruction.NewObjectInstruction; +import jdk.internal.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.internal.classfile.instruction.NewReferenceArrayInstruction; +import jdk.internal.classfile.instruction.NopInstruction; +import jdk.internal.classfile.instruction.OperatorInstruction; +import jdk.internal.classfile.instruction.ReturnInstruction; +import jdk.internal.classfile.instruction.StackInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; +import jdk.internal.classfile.instruction.SwitchCase; +import jdk.internal.classfile.instruction.TableSwitchInstruction; +import jdk.internal.classfile.instruction.ThrowInstruction; +import jdk.internal.classfile.instruction.TypeCheckInstruction; + +import static java.util.Objects.requireNonNull; +import static jdk.internal.classfile.impl.BytecodeHelpers.handleDescToHandleInfo; +import jdk.internal.classfile.impl.TransformingCodeBuilder; + +/** + * A builder for code attributes (method bodies). Builders are not created + * directly; they are passed to handlers by methods such as {@link + * MethodBuilder#withCode(Consumer)} or to code transforms. The elements of a + * code can be specified abstractly, by passing a {@link CodeElement} to {@link + * #with(ClassfileElement)} or concretely by calling the various {@code withXxx} + * methods. + * + * @see CodeTransform + */ +public sealed interface CodeBuilder + extends ClassfileBuilder + permits CodeBuilder.BlockCodeBuilder, ChainedCodeBuilder, TerminalCodeBuilder, NonterminalCodeBuilder { + + /** + * {@return the {@link CodeModel} representing the method body being transformed, + * if this code builder represents the transformation of some {@link CodeModel}} + */ + Optional original(); + + /** {@return a fresh unbound label} */ + Label newLabel(); + + /** {@return the label associated with the beginning of the current block} + * If the current {@linkplain CodeBuilder} is not a "block" builder, such as + * those provided by {@link #block(Consumer)} or {@link #ifThenElse(Consumer, Consumer)}, + * the current block will be the entire method body. */ + Label startLabel(); + + /** {@return the label associated with the end of the current block} + * If the current {@linkplain CodeBuilder} is not a "block" builder, such as + * those provided by {@link #block(Consumer)} or {@link #ifThenElse(Consumer, Consumer)}, + * the current block will be the entire method body. */ + Label endLabel(); + + /** + * {@return the local variable slot associated with the receiver}. + * + * @throws IllegalStateException if this is not a static method + */ + int receiverSlot(); + + /** + * {@return the local variable slot associated with the specified parameter}. + * The returned value is adjusted for the receiver slot (if the method is + * an instance method) and for the requirement that {@code long} and {@code double} + * values require two slots. + * + * @param paramNo the index of the parameter + */ + int parameterSlot(int paramNo); + + /** + * {@return the local variable slot of a fresh local variable} This method + * makes reasonable efforts to determine which slots are in use and which + * are not. When transforming a method, fresh locals begin at the {@code maxLocals} + * of the original method. For a method being built directly, fresh locals + * begin after the last parameter slot. + * + *

If the current code builder is a "block" code builder provided by + * {@link #block(Consumer)}, {@link #ifThen(Consumer)}, or + * {@link #ifThenElse(Consumer, Consumer)}, at the end of the block, locals + * are reset to their value at the beginning of the block. + * + * @param typeKind the type of the local variable + */ + int allocateLocal(TypeKind typeKind); + + /** + * Apply a transform to the code built by a handler, directing results to this builder. + * + * @param transform the transform to apply to the code built by the handler + * @param handler the handler that receives a {@linkplain CodeBuilder} to + * build the code. + * @return this builder + */ + default CodeBuilder transforming(CodeTransform transform, Consumer handler) { + var resolved = transform.resolve(this); + resolved.startHandler().run(); + handler.accept(new TransformingCodeBuilder(this, resolved.consumer())); + resolved.endHandler().run(); + return this; + } + + /** + * A builder for blocks of code. + */ + sealed interface BlockCodeBuilder extends CodeBuilder + permits BlockCodeBuilderImpl { + /** + * {@return the label locating where control is passed back to the parent block.} + * A branch to this label "break"'s out of the current block. + *

+ * If an instruction occurring immediately after the built block's last instruction would + * be reachable from that last instruction, then a {@linkplain #goto_ goto} instruction + * targeting the "break" label is appended to the built block. + */ + Label breakLabel(); + } + + /** + * Add a lexical block to the method being built. + *

+ * Within this block, the {@link #startLabel()} and {@link #endLabel()} correspond + * to the start and end of the block, and the {@link BlockCodeBuilder#breakLabel()} + * also corresponds to the end of the block. + * + * @param handler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the lexical block. + */ + default CodeBuilder block(Consumer handler) { + Label breakLabel = newLabel(); + BlockCodeBuilderImpl child = new BlockCodeBuilderImpl(this, breakLabel); + child.start(); + handler.accept(child); + child.end(); + labelBinding(breakLabel); + return this; + } + + /** + * Add an "if-then" block that is conditional on the boolean value + * on top of the operand stack. + *

+ * The {@link BlockCodeBuilder#breakLabel()} for the "then" block corresponds to the + * end of that block. + * + * @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the {@code if} + * @return this builder + */ + default CodeBuilder ifThen(Consumer thenHandler) { + return ifThen(Opcode.IFNE, thenHandler); + } + + /** + * Add an "if-then" block that is conditional on the value(s) on top of the operand stack + * in accordance with the given opcode. + *

+ * The {@link BlockCodeBuilder#breakLabel()} for the "then" block corresponds to the + * end of that block. + * + * @param opcode the operation code for a branch instructions that accepts one or two operands on the stack + * @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the {@code if} + * @return this builder + * @throws java.lang.IllegalArgumentException if the operation code is not for a branch instruction that accepts + * one or two operands + */ + default CodeBuilder ifThen(Opcode opcode, + Consumer thenHandler) { + if (opcode.kind() != Opcode.Kind.BRANCH || opcode.primaryTypeKind() == TypeKind.VoidType) { + throw new IllegalArgumentException("Illegal branch opcode: " + opcode); + } + + Label breakLabel = newLabel(); + BlockCodeBuilderImpl thenBlock = new BlockCodeBuilderImpl(this, breakLabel); + branchInstruction(BytecodeHelpers.reverseBranchOpcode(opcode), thenBlock.endLabel()); + thenBlock.start(); + thenHandler.accept(thenBlock); + thenBlock.end(); + labelBinding(breakLabel); + return this; + } + + /** + * Add an "if-then-else" block that is conditional on the boolean value + * on top of the operand stack. + *

+ * The {@link BlockCodeBuilder#breakLabel()} for each block corresponds to the + * end of the "else" block. + * + * @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the {@code if} + * @param elseHandler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the {@code else} + * @return this builder + */ + default CodeBuilder ifThenElse(Consumer thenHandler, + Consumer elseHandler) { + return ifThenElse(Opcode.IFNE, thenHandler, elseHandler); + } + + /** + * Add an "if-then-else" block that is conditional on the value(s) on top of the operand stack + * in accordance with the given opcode. + *

+ * The {@link BlockCodeBuilder#breakLabel()} for each block corresponds to the + * end of the "else" block. + * + * @param opcode the operation code for a branch instructions that accepts one or two operands on the stack + * @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the {@code if} + * @param elseHandler handler that receives a {@linkplain BlockCodeBuilder} to + * generate the body of the {@code else} + * @return this builder + * @throws java.lang.IllegalArgumentException if the operation code is not for a branch instruction that accepts + * one or two operands + */ + default CodeBuilder ifThenElse(Opcode opcode, + Consumer thenHandler, + Consumer elseHandler) { + if (opcode.kind() != Opcode.Kind.BRANCH || opcode.primaryTypeKind() == TypeKind.VoidType) { + throw new IllegalArgumentException("Illegal branch opcode: " + opcode); + } + + Label breakLabel = newLabel(); + BlockCodeBuilderImpl thenBlock = new BlockCodeBuilderImpl(this, breakLabel); + BlockCodeBuilderImpl elseBlock = new BlockCodeBuilderImpl(this, breakLabel); + branchInstruction(BytecodeHelpers.reverseBranchOpcode(opcode), elseBlock.startLabel()); + thenBlock.start(); + thenHandler.accept(thenBlock); + if (thenBlock.reachable()) + thenBlock.branchInstruction(Opcode.GOTO, thenBlock.breakLabel()); + thenBlock.end(); + elseBlock.start(); + elseHandler.accept(elseBlock); + elseBlock.end(); + labelBinding(breakLabel); + return this; + } + + /** + * A builder to add catch blocks. + * + * @see #trying + */ + sealed interface CatchBuilder permits CatchBuilderImpl { + /** + * Adds a catch block that catches an exception of the given type. + *

+ * The caught exception will be on top of the operand stack when the catch block is entered. + *

+ * If the type of exception is {@code null} then the catch block catches all exceptions. + * + * @param exceptionType the type of exception to catch. + * @param catchHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the catch block. + * @return this builder + * @throws java.lang.IllegalArgumentException if an existing catch block catches an exception of the given type. + * @see #catchingMulti + * @see #catchingAll + */ + CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler); + + /** + * Adds a catch block that catches exceptions of the given types. + *

+ * The caught exception will be on top of the operand stack when the catch block is entered. + *

+ * If the type of exception is {@code null} then the catch block catches all exceptions. + * + * @param exceptionTypes the types of exception to catch. + * @param catchHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the catch block. + * @return this builder + * @throws java.lang.IllegalArgumentException if an existing catch block catches one or more exceptions of the given types. + * @see #catching + * @see #catchingAll + */ + CatchBuilder catchingMulti(List exceptionTypes, Consumer catchHandler); + + /** + * Adds a "catch" block that catches all exceptions. + *

+ * The caught exception will be on top of the operand stack when the catch block is entered. + * + * @param catchAllHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the catch block + * @throws java.lang.IllegalArgumentException if an existing catch block catches all exceptions. + * @see #catching + * @see #catchingMulti + */ + void catchingAll(Consumer catchAllHandler); + } + + /** + * Adds a "try-catch" block comprising one try block and zero or more catch blocks. + * Exceptions thrown by instructions in the try block may be caught by catch blocks. + * + * @param tryHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the try block. + * @param catchesHandler a handler that receives a {@linkplain CatchBuilder} + * to generate bodies of catch blocks. + * @return this builder + * @see CatchBuilder + */ + default CodeBuilder trying(Consumer tryHandler, + Consumer catchesHandler) { + Label tryCatchEnd = newLabel(); + + BlockCodeBuilderImpl tryBlock = new BlockCodeBuilderImpl(this, tryCatchEnd); + tryBlock.start(); + tryHandler.accept(tryBlock); + tryBlock.end(); + + // Check for empty try block + if (tryBlock.isEmpty()) { + throw new IllegalStateException("The body of the try block is empty"); + } + + var catchBuilder = new CatchBuilderImpl(this, tryBlock, tryCatchEnd); + catchesHandler.accept(catchBuilder); + catchBuilder.finish(); + + return this; + } + + // Base convenience methods + + default CodeBuilder loadInstruction(TypeKind tk, int slot) { + with(LoadInstruction.of(tk, slot)); + return this; + } + + default CodeBuilder storeInstruction(TypeKind tk, int slot) { + with(StoreInstruction.of(tk, slot)); + return this; + } + + default CodeBuilder incrementInstruction(int slot, int val) { + with(IncrementInstruction.of(slot, val)); + return this; + } + + default CodeBuilder branchInstruction(Opcode op, Label target) { + with(BranchInstruction.of(op, target)); + return this; + } + + default CodeBuilder lookupSwitchInstruction(Label defaultTarget, List cases) { + with(LookupSwitchInstruction.of(defaultTarget, cases)); + return this; + } + + default CodeBuilder tableSwitchInstruction(int lowValue, int highValue, Label defaultTarget, List cases) { + with(TableSwitchInstruction.of(lowValue, highValue, defaultTarget, cases)); + return this; + } + + default CodeBuilder returnInstruction(TypeKind tk) { + with(ReturnInstruction.of(tk)); + return this; + } + + default CodeBuilder throwInstruction() { + with(ThrowInstruction.of()); + return this; + } + + default CodeBuilder fieldInstruction(Opcode opcode, FieldRefEntry ref) { + with(FieldInstruction.of(opcode, ref)); + return this; + } + + default CodeBuilder fieldInstruction(Opcode opcode, ClassDesc owner, String name, ClassDesc type) { + return fieldInstruction(opcode, constantPool().fieldRefEntry(owner, name, type)); + } + + default CodeBuilder invokeInstruction(Opcode opcode, MemberRefEntry ref) { + return with(InvokeInstruction.of(opcode, ref)); + } + + default CodeBuilder invokeInstruction(Opcode opcode, ClassDesc owner, String name, MethodTypeDesc desc, boolean isInterface) { + return invokeInstruction(opcode, + isInterface ? constantPool().interfaceMethodRefEntry(owner, name, desc) + : constantPool().methodRefEntry(owner, name, desc)); + } + + default CodeBuilder invokeDynamicInstruction(InvokeDynamicEntry ref) { + with(InvokeDynamicInstruction.of(ref)); + return this; + } + + default CodeBuilder invokeDynamicInstruction(DynamicCallSiteDesc desc) { + MethodHandleEntry bsMethod = handleDescToHandleInfo(constantPool(), (DirectMethodHandleDesc) desc.bootstrapMethod()); + var cpArgs = desc.bootstrapArgs(); + List bsArguments = new ArrayList<>(cpArgs.length); + for (var constantValue : cpArgs) { + bsArguments.add(BytecodeHelpers.constantEntry(constantPool(), constantValue)); + } + BootstrapMethodEntry bm = constantPool().bsmEntry(bsMethod, bsArguments); + NameAndTypeEntry nameAndType = constantPool().nameAndTypeEntry(desc.invocationName(), desc.invocationType()); + invokeDynamicInstruction(constantPool().invokeDynamicEntry(bm, nameAndType)); + return this; + } + + default CodeBuilder newObjectInstruction(ClassEntry type) { + with(NewObjectInstruction.of(type)); + return this; + } + + default CodeBuilder newObjectInstruction(ClassDesc type) { + return newObjectInstruction(constantPool().classEntry(type)); + } + + default CodeBuilder newPrimitiveArrayInstruction(TypeKind typeKind) { + with(NewPrimitiveArrayInstruction.of(typeKind)); + return this; + } + + default CodeBuilder newReferenceArrayInstruction(ClassEntry type) { + with(NewReferenceArrayInstruction.of(type)); + return this; + } + + default CodeBuilder newReferenceArrayInstruction(ClassDesc type) { + return newReferenceArrayInstruction(constantPool().classEntry(type)); + } + + default CodeBuilder newMultidimensionalArrayInstruction(int dimensions, + ClassEntry type) { + with(NewMultiArrayInstruction.of(type, dimensions)); + return this; + } + + default CodeBuilder newMultidimensionalArrayInstruction(int dimensions, + ClassDesc type) { + return newMultidimensionalArrayInstruction(dimensions, constantPool().classEntry(type)); + } + + default CodeBuilder arrayLoadInstruction(TypeKind tk) { + Opcode opcode = BytecodeHelpers.arrayLoadOpcode(tk); + with(ArrayLoadInstruction.of(opcode)); + return this; + } + + default CodeBuilder arrayStoreInstruction(TypeKind tk) { + Opcode opcode = BytecodeHelpers.arrayStoreOpcode(tk); + with(ArrayStoreInstruction.of(opcode)); + return this; + } + + default CodeBuilder typeCheckInstruction(Opcode opcode, + ClassEntry type) { + with(TypeCheckInstruction.of(opcode, type)); + return this; + } + + default CodeBuilder typeCheckInstruction(Opcode opcode, ClassDesc type) { + return typeCheckInstruction(opcode, constantPool().classEntry(type)); + } + + default CodeBuilder convertInstruction(TypeKind fromType, TypeKind toType) { + with(ConvertInstruction.of(fromType, toType)); + return this; + } + + default CodeBuilder stackInstruction(Opcode opcode) { + with(StackInstruction.of(opcode)); + return this; + } + + default CodeBuilder operatorInstruction(Opcode opcode) { + with(OperatorInstruction.of(opcode)); + return this; + } + + default CodeBuilder constantInstruction(Opcode opcode, ConstantDesc value) { + BytecodeHelpers.validateValue(opcode, value); + return with(switch (opcode) { + case SIPUSH, BIPUSH -> ConstantInstruction.ofArgument(opcode, ((Number)value).intValue()); + case LDC, LDC_W, LDC2_W -> ConstantInstruction.ofLoad(opcode, BytecodeHelpers.constantEntry(constantPool(), value)); + default -> ConstantInstruction.ofIntrinsic(opcode); + }); + } + + default CodeBuilder constantInstruction(ConstantDesc value) { + //avoid switch expressions here + if (value == null || value == ConstantDescs.NULL) + return aconst_null(); + if (value instanceof Integer iVal) + return switch (iVal) { + case -1 -> iconst_m1(); + case 0 -> iconst_0(); + case 1 -> iconst_1(); + case 2 -> iconst_2(); + case 3 -> iconst_3(); + case 4 -> iconst_4(); + case 5 -> iconst_5(); + default -> (iVal >= Byte.MIN_VALUE && iVal <= Byte.MAX_VALUE) ? bipush(iVal) + : (iVal >= Short.MIN_VALUE && iVal <= Short.MAX_VALUE) ? sipush(iVal) + : ldc(constantPool().intEntry(iVal)); + }; + if (value instanceof Long lVal) + return lVal == 0l ? lconst_0() + : lVal == 1l ? lconst_1() + : ldc(constantPool().longEntry(lVal)); + if (value instanceof Float fVal) + return Float.floatToRawIntBits(fVal) == 0 ? fconst_0() + : fVal == 1.0f ? fconst_1() + : fVal == 2.0f ? fconst_2() + : ldc(constantPool().floatEntry(fVal)); + if (value instanceof Double dVal) + return Double.doubleToRawLongBits(dVal) == 0l ? dconst_0() + : dVal == 1.0d ? dconst_1() + : ldc(constantPool().doubleEntry(dVal)); + return ldc(BytecodeHelpers.constantEntry(constantPool(), value)); + } + + default CodeBuilder monitorInstruction(Opcode opcode) { + with(MonitorInstruction.of(opcode)); + return null; + } + + default CodeBuilder nopInstruction() { + with(NopInstruction.of()); + return this; + } + + + default CodeBuilder nop() { + return nopInstruction(); + } + + // Base pseudo-instruction builder methods + + default Label newBoundLabel() { + var label = newLabel(); + labelBinding(label); + return label; + } + + default CodeBuilder labelBinding(Label label) { + with((LabelImpl) label); + return this; + } + + default CodeBuilder lineNumber(int line) { + with(LineNumber.of(line)); + return this; + } + + default CodeBuilder exceptionCatch(Label start, Label end, Label handler, ClassEntry catchType) { + with(ExceptionCatch.of(handler, start, end, Optional.of(catchType))); + return this; + } + + default CodeBuilder exceptionCatch(Label start, Label end, Label handler, Optional catchType) { + with(ExceptionCatch.of(handler, start, end, catchType)); + return this; + } + + default CodeBuilder exceptionCatch(Label start, Label end, Label handler, ClassDesc catchType) { + requireNonNull(catchType); + return exceptionCatch(start, end, handler, constantPool().classEntry(catchType)); + } + + default CodeBuilder exceptionCatchAll(Label start, Label end, Label handler) { + with(ExceptionCatch.of(handler, start, end)); + return this; + } + + default CodeBuilder characterRange(Label startScope, Label endScope, int characterRangeStart, int characterRangeEnd, int flags) { + with(CharacterRange.of(startScope, endScope, characterRangeStart, characterRangeEnd, flags)); + return this; + } + + default CodeBuilder localVariable(int slot, Utf8Entry nameEntry, Utf8Entry descriptorEntry, Label startScope, Label endScope) { + with(LocalVariable.of(slot, nameEntry, descriptorEntry, startScope, endScope)); + return this; + } + + default CodeBuilder localVariable(int slot, String name, ClassDesc descriptor, Label startScope, Label endScope) { + return localVariable(slot, + constantPool().utf8Entry(name), + constantPool().utf8Entry(descriptor.descriptorString()), + startScope, endScope); + } + + default CodeBuilder localVariableType(int slot, Utf8Entry nameEntry, Utf8Entry signatureEntry, Label startScope, Label endScope) { + with(LocalVariableType.of(slot, nameEntry, signatureEntry, startScope, endScope)); + return this; + } + + default CodeBuilder localVariableType(int slot, String name, Signature signature, Label startScope, Label endScope) { + return localVariableType(slot, + constantPool().utf8Entry(name), + constantPool().utf8Entry(signature.signatureString()), + startScope, endScope); + } + + // Bytecode conveniences + + default CodeBuilder aconst_null() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ACONST_NULL)); + } + + default CodeBuilder aaload() { + return arrayLoadInstruction(TypeKind.ReferenceType); + } + + default CodeBuilder aastore() { + return arrayStoreInstruction(TypeKind.ReferenceType); + } + + default CodeBuilder aload(int slot) { + return loadInstruction(TypeKind.ReferenceType, slot); + } + + default CodeBuilder anewarray(ClassEntry classEntry) { + return newReferenceArrayInstruction(classEntry); + } + + default CodeBuilder anewarray(ClassDesc className) { + return newReferenceArrayInstruction(constantPool().classEntry(className)); + } + + default CodeBuilder areturn() { + return returnInstruction(TypeKind.ReferenceType); + } + + default CodeBuilder arraylength() { + return operatorInstruction(Opcode.ARRAYLENGTH); + } + + default CodeBuilder astore(int slot) { + return storeInstruction(TypeKind.ReferenceType, slot); + } + + default CodeBuilder athrow() { + return throwInstruction(); + } + + default CodeBuilder baload() { + return arrayLoadInstruction(TypeKind.ByteType); + } + + default CodeBuilder bastore() { + return arrayStoreInstruction(TypeKind.ByteType); + } + + default CodeBuilder bipush(int b) { + return constantInstruction(Opcode.BIPUSH, b); + } + + default CodeBuilder caload() { + return arrayLoadInstruction(TypeKind.CharType); + } + + default CodeBuilder castore() { + return arrayStoreInstruction(TypeKind.CharType); + } + + default CodeBuilder checkcast(ClassEntry type) { + return typeCheckInstruction(Opcode.CHECKCAST, type); + } + + default CodeBuilder checkcast(ClassDesc type) { + return typeCheckInstruction(Opcode.CHECKCAST, type); + } + + default CodeBuilder d2f() { + return convertInstruction(TypeKind.DoubleType, TypeKind.FloatType); + } + + default CodeBuilder d2i() { + return convertInstruction(TypeKind.DoubleType, TypeKind.IntType); + } + + default CodeBuilder d2l() { + return convertInstruction(TypeKind.DoubleType, TypeKind.LongType); + } + + default CodeBuilder dadd() { + return operatorInstruction(Opcode.DADD); + } + + default CodeBuilder daload() { + return arrayLoadInstruction(TypeKind.DoubleType); + } + + default CodeBuilder dastore() { + return arrayStoreInstruction(TypeKind.DoubleType); + } + + default CodeBuilder dcmpg() { + return operatorInstruction(Opcode.DCMPG); + } + + default CodeBuilder dcmpl() { + return operatorInstruction(Opcode.DCMPL); + } + + default CodeBuilder dconst_0() { + return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_0)); + } + + default CodeBuilder dconst_1() { + return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_1)); + } + + default CodeBuilder ddiv() { + return operatorInstruction(Opcode.DDIV); + } + + default CodeBuilder dload(int slot) { + return loadInstruction(TypeKind.DoubleType, slot); + } + + default CodeBuilder dmul() { + return operatorInstruction(Opcode.DMUL); + } + + default CodeBuilder dneg() { + return operatorInstruction(Opcode.DNEG); + } + + default CodeBuilder drem() { + return operatorInstruction(Opcode.DREM); + } + + default CodeBuilder dreturn() { + return returnInstruction(TypeKind.DoubleType); + } + + default CodeBuilder dstore(int slot) { + return storeInstruction(TypeKind.DoubleType, slot); + } + + default CodeBuilder dsub() { + return operatorInstruction(Opcode.DSUB); + } + + default CodeBuilder dup() { + return stackInstruction(Opcode.DUP); + } + + default CodeBuilder dup2() { + return stackInstruction(Opcode.DUP2); + } + + default CodeBuilder dup2_x1() { + return stackInstruction(Opcode.DUP2_X1); + } + + default CodeBuilder dup2_x2() { + return stackInstruction(Opcode.DUP2_X2); + } + + default CodeBuilder dup_x1() { + return stackInstruction(Opcode.DUP_X1); + } + + default CodeBuilder dup_x2() { + return stackInstruction(Opcode.DUP_X2); + } + + default CodeBuilder f2d() { + return convertInstruction(TypeKind.FloatType, TypeKind.DoubleType); + } + + default CodeBuilder f2i() { + return convertInstruction(TypeKind.FloatType, TypeKind.IntType); + } + + default CodeBuilder f2l() { + return convertInstruction(TypeKind.FloatType, TypeKind.LongType); + } + + default CodeBuilder fadd() { + return operatorInstruction(Opcode.FADD); + } + + default CodeBuilder faload() { + return arrayLoadInstruction(TypeKind.FloatType); + } + + default CodeBuilder fastore() { + return arrayStoreInstruction(TypeKind.FloatType); + } + + default CodeBuilder fcmpg() { + return operatorInstruction(Opcode.FCMPG); + } + + default CodeBuilder fcmpl() { + return operatorInstruction(Opcode.FCMPL); + } + + default CodeBuilder fconst_0() { + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_0)); + } + + default CodeBuilder fconst_1() { + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_1)); + } + + default CodeBuilder fconst_2() { + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_2)); + } + + default CodeBuilder fdiv() { + return operatorInstruction(Opcode.FDIV); + } + + default CodeBuilder fload(int slot) { + return loadInstruction(TypeKind.FloatType, slot); + } + + default CodeBuilder fmul() { + return operatorInstruction(Opcode.FMUL); + } + + default CodeBuilder fneg() { + return operatorInstruction(Opcode.FNEG); + } + + default CodeBuilder frem() { + return operatorInstruction(Opcode.FREM); + } + + default CodeBuilder freturn() { + return returnInstruction(TypeKind.FloatType); + } + + default CodeBuilder fstore(int slot) { + return storeInstruction(TypeKind.FloatType, slot); + } + + default CodeBuilder fsub() { + return operatorInstruction(Opcode.FSUB); + } + + default CodeBuilder getfield(FieldRefEntry ref) { + return fieldInstruction(Opcode.GETFIELD, ref); + } + + default CodeBuilder getfield(ClassDesc owner, String name, ClassDesc type) { + return fieldInstruction(Opcode.GETFIELD, owner, name, type); + } + + default CodeBuilder getstatic(FieldRefEntry ref) { + return fieldInstruction(Opcode.GETSTATIC, ref); + } + + default CodeBuilder getstatic(ClassDesc owner, String name, ClassDesc type) { + return fieldInstruction(Opcode.GETSTATIC, owner, name, type); + } + + default CodeBuilder goto_(Label target) { + return branchInstruction(Opcode.GOTO, target); + } + + default CodeBuilder goto_w(Label target) { + return branchInstruction(Opcode.GOTO_W, target); + } + + default CodeBuilder i2b() { + return convertInstruction(TypeKind.IntType, TypeKind.ByteType); + } + + default CodeBuilder i2c() { + return convertInstruction(TypeKind.IntType, TypeKind.CharType); + } + + default CodeBuilder i2d() { + return convertInstruction(TypeKind.IntType, TypeKind.DoubleType); + } + + default CodeBuilder i2f() { + return convertInstruction(TypeKind.IntType, TypeKind.FloatType); + } + + default CodeBuilder i2l() { + return convertInstruction(TypeKind.IntType, TypeKind.LongType); + } + + default CodeBuilder i2s() { + return convertInstruction(TypeKind.IntType, TypeKind.ShortType); + } + + default CodeBuilder iadd() { + return operatorInstruction(Opcode.IADD); + } + + default CodeBuilder iaload() { + return arrayLoadInstruction(TypeKind.IntType); + } + + default CodeBuilder iand() { + return operatorInstruction(Opcode.IAND); + } + + default CodeBuilder iastore() { + return arrayStoreInstruction(TypeKind.IntType); + } + + default CodeBuilder iconst_0() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_0)); + } + + default CodeBuilder iconst_1() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_1)); + } + + default CodeBuilder iconst_2() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_2)); + } + + default CodeBuilder iconst_3() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_3)); + } + + default CodeBuilder iconst_4() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_4)); + } + + default CodeBuilder iconst_5() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_5)); + } + + default CodeBuilder iconst_m1() { + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1)); + } + + default CodeBuilder idiv() { + return operatorInstruction(Opcode.IDIV); + } + + default CodeBuilder if_acmpeq(Label target) { + return branchInstruction(Opcode.IF_ACMPEQ, target); + } + + default CodeBuilder if_acmpne(Label target) { + return branchInstruction(Opcode.IF_ACMPNE, target); + } + + default CodeBuilder if_icmpeq(Label target) { + return branchInstruction(Opcode.IF_ICMPEQ, target); + } + + default CodeBuilder if_icmpge(Label target) { + return branchInstruction(Opcode.IF_ICMPGE, target); + } + + default CodeBuilder if_icmpgt(Label target) { + return branchInstruction(Opcode.IF_ICMPGT, target); + } + + default CodeBuilder if_icmple(Label target) { + return branchInstruction(Opcode.IF_ICMPLE, target); + } + + default CodeBuilder if_icmplt(Label target) { + return branchInstruction(Opcode.IF_ICMPLT, target); + } + + default CodeBuilder if_icmpne(Label target) { + return branchInstruction(Opcode.IF_ICMPNE, target); + } + + default CodeBuilder if_nonnull(Label target) { + return branchInstruction(Opcode.IFNONNULL, target); + } + + default CodeBuilder if_null(Label target) { + return branchInstruction(Opcode.IFNULL, target); + } + + default CodeBuilder ifeq(Label target) { + return branchInstruction(Opcode.IFEQ, target); + } + + default CodeBuilder ifge(Label target) { + return branchInstruction(Opcode.IFGE, target); + } + + default CodeBuilder ifgt(Label target) { + return branchInstruction(Opcode.IFGT, target); + } + + default CodeBuilder ifle(Label target) { + return branchInstruction(Opcode.IFLE, target); + } + + default CodeBuilder iflt(Label target) { + return branchInstruction(Opcode.IFLT, target); + } + + default CodeBuilder ifne(Label target) { + return branchInstruction(Opcode.IFNE, target); + } + + default CodeBuilder iinc(int slot, int val) { + return incrementInstruction(slot, val); + } + + default CodeBuilder iload(int slot) { + return loadInstruction(TypeKind.IntType, slot); + } + + default CodeBuilder imul() { + return operatorInstruction(Opcode.IMUL); + } + + default CodeBuilder ineg() { + return operatorInstruction(Opcode.INEG); + } + + default CodeBuilder instanceof_(ClassEntry target) { + return typeCheckInstruction(Opcode.INSTANCEOF, target); + } + + default CodeBuilder instanceof_(ClassDesc target) { + return typeCheckInstruction(Opcode.INSTANCEOF, constantPool().classEntry(target)); + } + + default CodeBuilder invokedynamic(InvokeDynamicEntry ref) { + return invokeDynamicInstruction(ref); + } + + default CodeBuilder invokedynamic(DynamicCallSiteDesc ref) { + return invokeDynamicInstruction(ref); + } + + default CodeBuilder invokeinterface(InterfaceMethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKEINTERFACE, ref); + } + + default CodeBuilder invokeinterface(ClassDesc owner, String name, MethodTypeDesc type) { + return invokeInstruction(Opcode.INVOKEINTERFACE, constantPool().interfaceMethodRefEntry(owner, name, type)); + } + + default CodeBuilder invokespecial(InterfaceMethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKESPECIAL, ref); + } + + default CodeBuilder invokespecial(MethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKESPECIAL, ref); + } + + default CodeBuilder invokespecial(ClassDesc owner, String name, MethodTypeDesc type) { + return invokeInstruction(Opcode.INVOKESPECIAL, owner, name, type, false); + } + + default CodeBuilder invokespecial(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { + return invokeInstruction(Opcode.INVOKESPECIAL, owner, name, type, isInterface); + } + + default CodeBuilder invokestatic(InterfaceMethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKESTATIC, ref); + } + + default CodeBuilder invokestatic(MethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKESTATIC, ref); + } + + default CodeBuilder invokestatic(ClassDesc owner, String name, MethodTypeDesc type) { + return invokeInstruction(Opcode.INVOKESTATIC, owner, name, type, false); + } + + default CodeBuilder invokestatic(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { + return invokeInstruction(Opcode.INVOKESTATIC, owner, name, type, isInterface); + } + + default CodeBuilder invokevirtual(MethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKEVIRTUAL, ref); + } + + default CodeBuilder invokevirtual(ClassDesc owner, String name, MethodTypeDesc type) { + return invokeInstruction(Opcode.INVOKEVIRTUAL, owner, name, type, false); + } + + default CodeBuilder ior() { + return operatorInstruction(Opcode.IOR); + } + + default CodeBuilder irem() { + return operatorInstruction(Opcode.IREM); + } + + default CodeBuilder ireturn() { + return returnInstruction(TypeKind.IntType); + } + + default CodeBuilder ishl() { + return operatorInstruction(Opcode.ISHL); + } + + default CodeBuilder ishr() { + return operatorInstruction(Opcode.ISHR); + } + + default CodeBuilder istore(int slot) { + return storeInstruction(TypeKind.IntType, slot); + } + + default CodeBuilder isub() { + return operatorInstruction(Opcode.ISUB); + } + + default CodeBuilder iushr() { + return operatorInstruction(Opcode.IUSHR); + } + + default CodeBuilder ixor() { + return operatorInstruction(Opcode.IXOR); + } + + default CodeBuilder lookupswitch(Label defaultTarget, List cases) { + return lookupSwitchInstruction(defaultTarget, cases); + } + + default CodeBuilder l2d() { + return convertInstruction(TypeKind.LongType, TypeKind.DoubleType); + } + + default CodeBuilder l2f() { + return convertInstruction(TypeKind.LongType, TypeKind.FloatType); + } + + default CodeBuilder l2i() { + return convertInstruction(TypeKind.LongType, TypeKind.IntType); + } + + default CodeBuilder ladd() { + return operatorInstruction(Opcode.LADD); + } + + default CodeBuilder laload() { + return arrayLoadInstruction(TypeKind.LongType); + } + + default CodeBuilder land() { + return operatorInstruction(Opcode.LAND); + } + + default CodeBuilder lastore() { + return arrayStoreInstruction(TypeKind.LongType); + } + + default CodeBuilder lcmp() { + return operatorInstruction(Opcode.LCMP); + } + + default CodeBuilder lconst_0() { + return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_0)); + } + + default CodeBuilder lconst_1() { + return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_1)); + } + + default CodeBuilder ldc(LoadableConstantEntry entry) { + return with(ConstantInstruction.ofLoad( + entry.typeKind().slotSize() == 2 ? Opcode.LDC2_W + : entry.index() > 0xff ? Opcode.LDC_W + : Opcode.LDC, entry)); + } + + default CodeBuilder ldiv() { + return operatorInstruction(Opcode.LDIV); + } + + default CodeBuilder lload(int slot) { + return loadInstruction(TypeKind.LongType, slot); + } + + default CodeBuilder lmul() { + return operatorInstruction(Opcode.LMUL); + } + + default CodeBuilder lneg() { + return operatorInstruction(Opcode.LNEG); + } + + default CodeBuilder lor() { + return operatorInstruction(Opcode.LOR); + } + + default CodeBuilder lrem() { + return operatorInstruction(Opcode.LREM); + } + + default CodeBuilder lreturn() { + return returnInstruction(TypeKind.LongType); + } + + default CodeBuilder lshl() { + return operatorInstruction(Opcode.LSHL); + } + + default CodeBuilder lshr() { + return operatorInstruction(Opcode.LSHR); + } + + default CodeBuilder lstore(int slot) { + return storeInstruction(TypeKind.LongType, slot); + } + + default CodeBuilder lsub() { + return operatorInstruction(Opcode.LSUB); + } + + default CodeBuilder lushr() { + return operatorInstruction(Opcode.LUSHR); + } + + default CodeBuilder lxor() { + return operatorInstruction(Opcode.LXOR); + } + + default CodeBuilder monitorenter() { + return monitorInstruction(Opcode.MONITORENTER); + } + + default CodeBuilder monitorexit() { + return monitorInstruction(Opcode.MONITOREXIT); + } + + default CodeBuilder multianewarray(ClassEntry array, int dims) { + return newMultidimensionalArrayInstruction(dims, array); + } + + default CodeBuilder multianewarray(ClassDesc array, int dims) { + return newMultidimensionalArrayInstruction(dims, constantPool().classEntry(array)); + } + + default CodeBuilder new_(ClassEntry clazz) { + return newObjectInstruction(clazz); + } + + default CodeBuilder new_(ClassDesc clazz) { + return newObjectInstruction(constantPool().classEntry(clazz)); + } + + default CodeBuilder newarray(TypeKind typeKind) { + return newPrimitiveArrayInstruction(typeKind); + } + + default CodeBuilder pop() { + return stackInstruction(Opcode.POP); + } + + default CodeBuilder pop2() { + return stackInstruction(Opcode.POP2); + } + + default CodeBuilder putfield(FieldRefEntry ref) { + return fieldInstruction(Opcode.PUTFIELD, ref); + } + + default CodeBuilder putfield(ClassDesc owner, String name, ClassDesc type) { + return fieldInstruction(Opcode.PUTFIELD, owner, name, type); + } + + default CodeBuilder putstatic(FieldRefEntry ref) { + return fieldInstruction(Opcode.PUTSTATIC, ref); + } + + default CodeBuilder putstatic(ClassDesc owner, String name, ClassDesc type) { + return fieldInstruction(Opcode.PUTSTATIC, owner, name, type); + } + + default CodeBuilder return_() { + return returnInstruction(TypeKind.VoidType); + } + + default CodeBuilder saload() { + return arrayLoadInstruction(TypeKind.ShortType); + } + + default CodeBuilder sastore() { + return arrayStoreInstruction(TypeKind.ShortType); + } + + default CodeBuilder sipush(int s) { + return constantInstruction(Opcode.SIPUSH, s); + } + + default CodeBuilder swap() { + return stackInstruction(Opcode.SWAP); + } + + default CodeBuilder tableswitch(int low, int high, Label defaultTarget, List cases) { + return tableSwitchInstruction(low, high, defaultTarget, cases); + } + + default CodeBuilder tableswitch(Label defaultTarget, List cases) { + int low = Integer.MAX_VALUE; + int high = Integer.MIN_VALUE; + for (var c : cases) { + int i = c.caseValue(); + if (i < low) low = i; + if (i > high) high = i; + } + return tableSwitchInstruction(low, high, defaultTarget, cases); + } + + // Structured conveniences: + + // allocLocal(type) + // returnFromMethod(inferred) +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/CodeElement.java b/src/java.base/share/classes/jdk/internal/classfile/CodeElement.java new file mode 100644 index 0000000000000..8578e19fcac97 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/CodeElement.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.StackMapTableAttribute; + +/** + * A {@link ClassfileElement} that can appear when traversing the elements + * of a {@link CodeModel} or be presented to a {@link CodeBuilder}. Code elements + * are either an {@link Instruction}, which models an instruction in the body + * of a method, or a {@link PseudoInstruction}, which models metadata from + * the code attribute, such as line number metadata, local variable metadata, + * exception metadata, label target metadata, etc. + */ +public sealed interface CodeElement extends ClassfileElement + permits Instruction, PseudoInstruction, + CustomAttribute, RuntimeVisibleTypeAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, + StackMapTableAttribute { +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/CodeModel.java b/src/java.base/share/classes/jdk/internal/classfile/CodeModel.java new file mode 100644 index 0000000000000..8f52f9196a121 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/CodeModel.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.util.List; +import java.util.Optional; + +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.impl.BufferedCodeBuilder; +import jdk.internal.classfile.impl.CodeImpl; +import jdk.internal.classfile.instruction.ExceptionCatch; + +/** + * Models the body of a method (the {@code Code} attribute). The instructions + * of the method body are accessed via a streaming view (e.g., {@link + * #elements()}). + */ +public sealed interface CodeModel + extends CompoundElement, AttributedElement, MethodElement + permits CodeAttribute, BufferedCodeBuilder.Model, CodeImpl { + + /** + * {@return the maximum size of the local variable table} + */ + int maxLocals(); + + /** + * {@return the maximum size of the operand stack} + */ + int maxStack(); + + /** + * {@return the enclosing method, if known} + */ + Optional parent(); + + /** + * {@return the exception table of the method} The exception table is also + * modeled by {@link ExceptionCatch} elements in the streaming view. + */ + List exceptionHandlers(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/CodeTransform.java b/src/java.base/share/classes/jdk/internal/classfile/CodeTransform.java new file mode 100644 index 0000000000000..3aa41baea6c02 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/CodeTransform.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import jdk.internal.classfile.impl.TransformImpl; + +/** + * A transformation on streams of {@link CodeElement}. + * + * @see ClassfileTransform + */ +@FunctionalInterface +public non-sealed interface CodeTransform + extends ClassfileTransform { + + /** + * A code transform that sends all elements to the builder. + */ + CodeTransform ACCEPT_ALL = new CodeTransform() { + @Override + public void accept(CodeBuilder builder, CodeElement element) { + builder.with(element); + } + }; + + /** + * Create a stateful code transform from a {@link Supplier}. The supplier + * will be invoked for each transformation. + * + * @param supplier a {@link Supplier} that produces a fresh transform object + * for each traversal + * @return the stateful code transform + */ + static CodeTransform ofStateful(Supplier supplier) { + return new TransformImpl.SupplierCodeTransform(supplier); + } + + /** + * Create a code transform that passes each element through to the builder, + * and calls the specified function when transformation is complete. + * + * @param finisher the function to call when transformation is complete + * @return the code transform + */ + static CodeTransform endHandler(Consumer finisher) { + return new CodeTransform() { + @Override + public void accept(CodeBuilder builder, CodeElement element) { + builder.with(element); + } + + @Override + public void atEnd(CodeBuilder builder) { + finisher.accept(builder); + } + }; + } + + @Override + default CodeTransform andThen(CodeTransform t) { + return new TransformImpl.ChainedCodeTransform(this, t); + } + + @Override + default ResolvedTransform resolve(CodeBuilder builder) { + return new TransformImpl.ResolvedTransformImpl<>(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/CompoundElement.java b/src/java.base/share/classes/jdk/internal/classfile/CompoundElement.java new file mode 100644 index 0000000000000..3ba7d73760bf0 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/CompoundElement.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A {@link ClassfileElement} that has complex structure defined in terms of + * other classfile elements, such as a method, field, method body, or entire + * class. When encountering a {@linkplain CompoundElement}, clients have the + * option to treat the element as a single entity (e.g., an entire method) + * or to traverse the contents of that element with the methods in this class + * (e.g., {@link #elements()}, {@link #forEachElement(Consumer)}, etc.) + */ +public sealed interface CompoundElement + extends ClassfileElement, Iterable + permits ClassModel, CodeModel, FieldModel, MethodModel, jdk.internal.classfile.impl.AbstractUnboundModel { + /** + * Invoke the provided handler with each element contained in this + * compound element + * @param consumer the handler + */ + void forEachElement(Consumer consumer); + + /** + * {@return an {@link Iterable} describing all the elements contained in this + * compound element} + */ + default Iterable elements() { + return elementList(); + } + + /** + * {@return an {@link Iterator} describing all the elements contained in this + * compound element} + */ + @Override + default Iterator iterator() { + return elements().iterator(); + } + + /** + * {@return a {@link Stream} containing all the elements contained in this + * compound element} + */ + default Stream elementStream() { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize( + iterator(), + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED), + false); + } + + /** + * {@return an {@link List} containing all the elements contained in this + * compound element} + */ + default List elementList() { + List list = new ArrayList<>(); + forEachElement(new Consumer<>() { + @Override + public void accept(E e) { + list.add(e); + } + }); + return list; + } + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/CustomAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/CustomAttribute.java new file mode 100644 index 0000000000000..d84e2628b9c85 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/CustomAttribute.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a non-standard attribute of a classfile. Clients should extend + * this class to provide an implementation class for non-standard attributes, + * and provide an {@link AttributeMapper} to mediate between the classfile + * format and the {@linkplain CustomAttribute} representation. + */ +@SuppressWarnings("exports") +public abstract non-sealed class CustomAttribute> + extends UnboundAttribute.CustomAttribute + implements CodeElement, ClassElement, MethodElement, FieldElement { + + /** + * Construct a {@linkplain CustomAttribute}. + * @param mapper the attribute mapper + */ + protected CustomAttribute(AttributeMapper mapper) { + super(mapper); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/FieldBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/FieldBuilder.java new file mode 100644 index 0000000000000..610a8aae76fe2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/FieldBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.ChainedFieldBuilder; +import jdk.internal.classfile.impl.TerminalFieldBuilder; +import java.lang.reflect.AccessFlag; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * A builder for fields. Builders are not created directly; they are passed + * to handlers by methods such as {@link ClassBuilder#withField(Utf8Entry, Utf8Entry, Consumer)} + * or to field transforms. The elements of a field can be specified + * abstractly (by passing a {@link FieldElement} to {@link #with(ClassfileElement)} + * or concretely by calling the various {@code withXxx} methods. + * + * @see FieldTransform + */ +public sealed interface FieldBuilder + extends ClassfileBuilder + permits TerminalFieldBuilder, ChainedFieldBuilder { + + /** + * Sets the field access flags. + * @param flags the access flags, as a bit mask + * @return this builder + */ + default FieldBuilder withFlags(int flags) { + return with(AccessFlags.ofField(flags)); + } + + /** + * Sets the field access flags. + * @param flags the access flags, as a bit mask + * @return this builder + */ + default FieldBuilder withFlags(AccessFlag... flags) { + return with(AccessFlags.ofField(flags)); + } + + /** + * {@return the {@link FieldModel} representing the field being transformed, + * if this field builder represents the transformation of some {@link FieldModel}} + */ + Optional original(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/FieldElement.java b/src/java.base/share/classes/jdk/internal/classfile/FieldElement.java new file mode 100644 index 0000000000000..6a25045482cce --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/FieldElement.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.ConstantValueAttribute; +import jdk.internal.classfile.attribute.DeprecatedAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.attribute.SyntheticAttribute; +import jdk.internal.classfile.attribute.UnknownAttribute; + +/** + * A {@link ClassfileElement} that can appear when traversing the elements + * of a {@link FieldModel} or be presented to a {@link FieldBuilder}. + */ +public sealed interface FieldElement extends ClassfileElement + permits AccessFlags, + CustomAttribute, ConstantValueAttribute, DeprecatedAttribute, + RuntimeInvisibleAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, + RuntimeVisibleAnnotationsAttribute, RuntimeVisibleTypeAnnotationsAttribute, + SignatureAttribute, SyntheticAttribute, UnknownAttribute { + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/FieldModel.java b/src/java.base/share/classes/jdk/internal/classfile/FieldModel.java new file mode 100644 index 0000000000000..c12098cfd8226 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/FieldModel.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.lang.constant.ClassDesc; +import java.util.Optional; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BufferedFieldBuilder; +import jdk.internal.classfile.impl.FieldImpl; + +/** + * Models a field. The contents of the field can be traversed via + * a streaming view (e.g., {@link #elements()}), or via random access (e.g., + * {@link #flags()}), or by freely mixing the two. + */ +public sealed interface FieldModel + extends WritableElement, CompoundElement, AttributedElement, ClassElement + permits BufferedFieldBuilder.Model, FieldImpl { + + /** {@return the access flags} */ + AccessFlags flags(); + + /** {@return the class model this field is a member of, if known} */ + Optional parent(); + + /** {@return the name of this field} */ + Utf8Entry fieldName(); + + /** {@return the field descriptor of this field} */ + Utf8Entry fieldType(); + + /** {@return the field descriptor of this field, as a symbolic descriptor} */ + default ClassDesc fieldTypeSymbol() { + return ClassDesc.ofDescriptor(fieldType().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/FieldTransform.java b/src/java.base/share/classes/jdk/internal/classfile/FieldTransform.java new file mode 100644 index 0000000000000..05ffa167a0821 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/FieldTransform.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.internal.classfile.impl.TransformImpl; + +/** + * A transformation on streams of {@link FieldElement}. + * + * @see ClassfileTransform + */ +@FunctionalInterface +public non-sealed interface FieldTransform + extends ClassfileTransform { + + /** + * A field transform that sends all elements to the builder. + */ + FieldTransform ACCEPT_ALL = new FieldTransform() { + @Override + public void accept(FieldBuilder builder, FieldElement element) { + builder.with(element); + } + }; + + /** + * Create a stateful field transform from a {@link Supplier}. The supplier + * will be invoked for each transformation. + * + * @param supplier a {@link Supplier} that produces a fresh transform object + * for each traversal + * @return the stateful field transform + */ + static FieldTransform ofStateful(Supplier supplier) { + return new TransformImpl.SupplierFieldTransform(supplier); + } + + /** + * Create a field transform that passes each element through to the builder, + * and calls the specified function when transformation is complete. + * + * @param finisher the function to call when transformation is complete + * @return the field transform + */ + static FieldTransform endHandler(Consumer finisher) { + return new FieldTransform() { + @Override + public void accept(FieldBuilder builder, FieldElement element) { + builder.with(element); + } + + @Override + public void atEnd(FieldBuilder builder) { + finisher.accept(builder); + } + }; + } + + /** + * Create a field transform that passes each element through to the builder, + * except for those that the supplied {@link Predicate} is true for. + * + * @param filter the predicate that determines which elements to drop + * @return the field transform + */ + static FieldTransform dropping(Predicate filter) { + return (b, e) -> { + if (!filter.test(e)) + b.with(e); + }; + } + + @Override + default FieldTransform andThen(FieldTransform t) { + return new TransformImpl.ChainedFieldTransform(this, t); + } + + @Override + default ResolvedTransform resolve(FieldBuilder builder) { + return new TransformImpl.ResolvedTransformImpl<>(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Instruction.java b/src/java.base/share/classes/jdk/internal/classfile/Instruction.java new file mode 100644 index 0000000000000..7b0643c453378 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Instruction.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.instruction.ArrayLoadInstruction; +import jdk.internal.classfile.instruction.ArrayStoreInstruction; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.ConvertInstruction; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.IncrementInstruction; +import jdk.internal.classfile.instruction.InvokeDynamicInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.LookupSwitchInstruction; +import jdk.internal.classfile.instruction.MonitorInstruction; +import jdk.internal.classfile.instruction.NewMultiArrayInstruction; +import jdk.internal.classfile.instruction.NewObjectInstruction; +import jdk.internal.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.internal.classfile.instruction.NewReferenceArrayInstruction; +import jdk.internal.classfile.instruction.NopInstruction; +import jdk.internal.classfile.instruction.OperatorInstruction; +import jdk.internal.classfile.instruction.ReturnInstruction; +import jdk.internal.classfile.instruction.StackInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; +import jdk.internal.classfile.instruction.TableSwitchInstruction; +import jdk.internal.classfile.instruction.ThrowInstruction; +import jdk.internal.classfile.instruction.TypeCheckInstruction; + +/** + * Models an executable instruction in a method body. + */ +public sealed interface Instruction extends CodeElement + permits ArrayLoadInstruction, ArrayStoreInstruction, BranchInstruction, + ConstantInstruction, ConvertInstruction, FieldInstruction, + InvokeDynamicInstruction, InvokeInstruction, LoadInstruction, + StoreInstruction, IncrementInstruction, + LookupSwitchInstruction, MonitorInstruction, NewMultiArrayInstruction, + NewObjectInstruction, NewPrimitiveArrayInstruction, NewReferenceArrayInstruction, + NopInstruction, OperatorInstruction, ReturnInstruction, + StackInstruction, TableSwitchInstruction, + ThrowInstruction, TypeCheckInstruction, AbstractInstruction { + + /** + * {@return the opcode of this instruction} + */ + Opcode opcode(); + + /** + * {@return the size in bytes of this instruction} + */ + int sizeInBytes(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Interfaces.java b/src/java.base/share/classes/jdk/internal/classfile/Interfaces.java new file mode 100644 index 0000000000000..f6e6fa7ed558c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Interfaces.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.InterfacesImpl; +import jdk.internal.classfile.impl.Util; + +/** + * Models the interfaces of a class. Delivered as a {@link + * jdk.internal.classfile.ClassElement} when traversing a {@link ClassModel}. + */ +public sealed interface Interfaces + extends ClassElement + permits InterfacesImpl { + + /** {@return the interfaces of this class} */ + List interfaces(); + + /** + * {@return an {@linkplain Interfaces} element} + * @param interfaces the interfaces + */ + static Interfaces of(List interfaces) { + return new InterfacesImpl(interfaces); + } + + /** + * {@return an {@linkplain Interfaces} element} + * @param interfaces the interfaces + */ + static Interfaces of(ClassEntry... interfaces) { + return of(List.of(interfaces)); + } + + /** + * {@return an {@linkplain Interfaces} element} + * @param interfaces the interfaces + */ + static Interfaces ofSymbols(List interfaces) { + return of(Util.entryList(interfaces)); + } + + /** + * {@return an {@linkplain Interfaces} element} + * @param interfaces the interfaces + */ + static Interfaces ofSymbols(ClassDesc... interfaces) { + return ofSymbols(Arrays.asList(interfaces)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Label.java b/src/java.base/share/classes/jdk/internal/classfile/Label.java new file mode 100644 index 0000000000000..06dccf42fe9ae --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Label.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.impl.LabelImpl; + +/** + * A marker for a position within the instructions of a method body. The + * assocation between a label's identity and the position it represents is + * managed by the entity managing the method body (a {@link CodeModel} or {@link + * CodeBuilder}), not the label itself; this allows the same label to have a + * meaning both in an existing method (as managed by a {@linkplain CodeModel}) + * and in the transformation of that method (as managed by a {@linkplain + * CodeBuilder}), while corresponding to different positions in each. When + * traversing the elements of a {@linkplain CodeModel}, {@linkplain Label} + * markers will be delivered at the position to which they correspond. A label + * can be bound to the current position within a {@linkplain CodeBuilder} via + * {@link CodeBuilder#labelBinding(Label)} or {@link CodeBuilder#with(ClassfileElement)}. + */ +public sealed interface Label + permits LabelImpl { +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/MethodBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/MethodBuilder.java new file mode 100644 index 0000000000000..6b57d0599d1ae --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/MethodBuilder.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.ChainedMethodBuilder; +import jdk.internal.classfile.impl.TerminalMethodBuilder; +import java.lang.reflect.AccessFlag; + +/** + * A builder for methods. Builders are not created directly; they are passed + * to handlers by methods such as {@link ClassBuilder#withMethod(Utf8Entry, Utf8Entry, int, Consumer)} + * or to method transforms. The elements of a method can be specified + * abstractly (by passing a {@link MethodElement} to {@link #with(ClassfileElement)} + * or concretely by calling the various {@code withXxx} methods. + * + * @see MethodTransform + */ +public sealed interface MethodBuilder + extends ClassfileBuilder + permits ChainedMethodBuilder, TerminalMethodBuilder { + + /** + * {@return the {@link MethodModel} representing the method being transformed, + * if this method builder represents the transformation of some {@link MethodModel}} + */ + Optional original(); + + /** + * Sets the method access flags. + * @param flags the access flags, as a bit mask + * @return this builder + */ + default MethodBuilder withFlags(int flags) { + return with(AccessFlags.ofMethod(flags)); + } + + /** + * Sets the method access flags. + * @param flags the access flags, as a bit mask + * @return this builder + */ + default MethodBuilder withFlags(AccessFlag... flags) { + return with(AccessFlags.ofMethod(flags)); + } + + /** + * Build the method body for this method. + * @param code a handler receiving a {@link CodeBuilder} + * @return this builder + */ + MethodBuilder withCode(Consumer code); + + /** + * Build the method body for this method by transforming the body of another + * method. + * + * @implNote + *

This method behaves as if: + * {@snippet lang=java : + * withCode(b -> b.transformCode(code, transform)); + * } + * + * @param code the method body to be transformed + * @param transform the transform to apply to the method body + * @return this builder + */ + MethodBuilder transformCode(CodeModel code, CodeTransform transform); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/MethodElement.java b/src/java.base/share/classes/jdk/internal/classfile/MethodElement.java new file mode 100644 index 0000000000000..28334b57e8fc1 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/MethodElement.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.AnnotationDefaultAttribute; +import jdk.internal.classfile.attribute.DeprecatedAttribute; +import jdk.internal.classfile.attribute.ExceptionsAttribute; +import jdk.internal.classfile.attribute.MethodParametersAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.attribute.SyntheticAttribute; +import jdk.internal.classfile.attribute.UnknownAttribute; + +/** + * A {@link ClassfileElement} that can appear when traversing the elements + * of a {@link MethodModel} or be presented to a {@link MethodBuilder}. + */ +public sealed interface MethodElement + extends ClassfileElement + permits AccessFlags, CodeModel, CustomAttribute, + AnnotationDefaultAttribute, DeprecatedAttribute, + ExceptionsAttribute, MethodParametersAttribute, + RuntimeInvisibleAnnotationsAttribute, RuntimeInvisibleParameterAnnotationsAttribute, + RuntimeInvisibleTypeAnnotationsAttribute, RuntimeVisibleAnnotationsAttribute, + RuntimeVisibleParameterAnnotationsAttribute, RuntimeVisibleTypeAnnotationsAttribute, + SignatureAttribute, SyntheticAttribute, UnknownAttribute { + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/MethodModel.java b/src/java.base/share/classes/jdk/internal/classfile/MethodModel.java new file mode 100644 index 0000000000000..84d0aeb69c86d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/MethodModel.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.lang.constant.MethodTypeDesc; +import java.util.Optional; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BufferedMethodBuilder; +import jdk.internal.classfile.impl.MethodImpl; + +/** + * Models a method. The contents of the method can be traversed via + * a streaming view (e.g., {@link #elements()}), or via random access (e.g., + * {@link #flags()}), or by freely mixing the two. + */ +public sealed interface MethodModel + extends WritableElement, CompoundElement, AttributedElement, ClassElement + permits BufferedMethodBuilder.Model, MethodImpl { + + /** {@return the access flags} */ + AccessFlags flags(); + + /** {@return the class model this method is a member of, if known} */ + Optional parent(); + + /** {@return the name of this method} */ + Utf8Entry methodName(); + + /** {@return the method descriptor of this method} */ + Utf8Entry methodType(); + + /** {@return the method descriptor of this method, as a symbolic descriptor} */ + default MethodTypeDesc methodTypeSymbol() { + return MethodTypeDesc.ofDescriptor(methodType().stringValue()); + } + + /** {@return the body of this method, if there is one} */ + Optional code(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/MethodSignature.java b/src/java.base/share/classes/jdk/internal/classfile/MethodSignature.java new file mode 100644 index 0000000000000..574c41d599180 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/MethodSignature.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import jdk.internal.classfile.impl.SignaturesImpl; +import static java.util.Objects.requireNonNull; +import jdk.internal.classfile.impl.Util; + +/** + * Models the generic signature of a method, as defined by {@jvms 4.7.9}. + */ +public sealed interface MethodSignature + permits SignaturesImpl.MethodSignatureImpl { + + /** {@return the type parameters of this method} */ + List typeParameters(); + + /** {@return the signatures of the parameters of this method} */ + List arguments(); + + /** {@return the signatures of the return value of this method} */ + Signature result(); + + /** {@return the signatures of the exceptions thrown by this method} */ + List throwableSignatures(); + + /** {@return the raw signature string} */ + String signatureString(); + + /** + * @return method signature for a raw (no generic information) method descriptor + * @param methodDescriptor the method descriptor + */ + public static MethodSignature of(MethodTypeDesc methodDescriptor) { + + requireNonNull(methodDescriptor); + return new SignaturesImpl.MethodSignatureImpl( + List.of(), + List.of(), + Signature.of(methodDescriptor.returnType()), + Util.mappedList(methodDescriptor.parameterList(), Signature::of)); + } + + /** + * @return method signature + * @param result signature for the return type + * @param arguments signatures for the method arguments + */ + public static MethodSignature of(Signature result, + Signature... arguments) { + + return new SignaturesImpl.MethodSignatureImpl(List.of(), + List.of(), + requireNonNull(result), + List.of(arguments)); + } + + /** + * @return method signature + * @param typeParameters signatures for the type parameters + * @param exceptions sigantures for the exceptions + * @param result signature for the return type + * @param arguments signatures for the method arguments + */ + public static MethodSignature of(List typeParameters, + List exceptions, + Signature result, + Signature... arguments) { + + return new SignaturesImpl.MethodSignatureImpl( + List.copyOf(requireNonNull(typeParameters)), + List.copyOf(requireNonNull(exceptions)), + requireNonNull(result), + List.of(arguments)); + } + + /** + * Parses a raw method signature string into a {@linkplain MethodSignature} + * @param methodSignature the raw method signature string + * @return method signature + */ + public static MethodSignature parseFrom(String methodSignature) { + + return new SignaturesImpl().parseMethodSignature(requireNonNull(methodSignature)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/MethodTransform.java b/src/java.base/share/classes/jdk/internal/classfile/MethodTransform.java new file mode 100644 index 0000000000000..1aa1f5e6a08ef --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/MethodTransform.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.internal.classfile.impl.TransformImpl; + +/** + * A transformation on streams of {@link MethodElement}. + * + * @see ClassfileTransform + */ +@FunctionalInterface +public non-sealed interface MethodTransform + extends ClassfileTransform { + + /** + * A method transform that sends all elements to the builder. + */ + MethodTransform ACCEPT_ALL = new MethodTransform() { + @Override + public void accept(MethodBuilder builder, MethodElement element) { + builder.with(element); + } + }; + + /** + * Create a stateful method transform from a {@link Supplier}. The supplier + * will be invoked for each transformation. + * + * @param supplier a {@link Supplier} that produces a fresh transform object + * for each traversal + * @return the stateful method transform + */ + static MethodTransform ofStateful(Supplier supplier) { + return new TransformImpl.SupplierMethodTransform(supplier); + } + + /** + * Create a method transform that passes each element through to the builder, + * and calls the specified function when transformation is complete. + * + * @param finisher the function to call when transformation is complete + * @return the method transform + */ + static MethodTransform endHandler(Consumer finisher) { + return new MethodTransform() { + @Override + public void accept(MethodBuilder builder, MethodElement element) { + builder.with(element); + } + + @Override + public void atEnd(MethodBuilder builder) { + finisher.accept(builder); + } + }; + } + + /** + * Create a method transform that passes each element through to the builder, + * except for those that the supplied {@link Predicate} is true for. + * + * @param filter the predicate that determines which elements to drop + * @return the method transform + */ + static MethodTransform dropping(Predicate filter) { + return (b, e) -> { + if (!filter.test(e)) + b.with(e); + }; + } + + /** + * Create a method transform that transforms {@link CodeModel} elements + * with the supplied code transform. + * + * @param xform the method transform + * @return the class transform + */ + static MethodTransform transformingCode(CodeTransform xform) { + return new TransformImpl.MethodCodeTransform(xform); + } + + @Override + default ResolvedTransform resolve(MethodBuilder builder) { + return new TransformImpl.ResolvedTransformImpl<>(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } + + @Override + default MethodTransform andThen(MethodTransform t) { + return new TransformImpl.ChainedMethodTransform(this, t); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Opcode.java b/src/java.base/share/classes/jdk/internal/classfile/Opcode.java new file mode 100644 index 0000000000000..5aaf8778cabe7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Opcode.java @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.ConstantDescs; + +/** + * Describes the opcodes of the JVM instruction set, as well as a number of + * pseudo-instructions that may be encountered when traversing the instructions + * of a method. + * + * @see Instruction + * @see PseudoInstruction + */ +public enum Opcode { + NOP(Classfile.NOP, 1, Kind.NOP), + ACONST_NULL(Classfile.ACONST_NULL, 1, Kind.CONSTANT, TypeKind.ReferenceType, 0, ConstantDescs.NULL), + ICONST_M1(Classfile.ICONST_M1, 1, Kind.CONSTANT, TypeKind.IntType, 0, -1), + ICONST_0(Classfile.ICONST_0, 1, Kind.CONSTANT, TypeKind.IntType, 0, 0), + ICONST_1(Classfile.ICONST_1, 1, Kind.CONSTANT, TypeKind.IntType, 0, 1), + ICONST_2(Classfile.ICONST_2, 1, Kind.CONSTANT, TypeKind.IntType, 0, 2), + ICONST_3(Classfile.ICONST_3, 1, Kind.CONSTANT, TypeKind.IntType, 0, 3), + ICONST_4(Classfile.ICONST_4, 1, Kind.CONSTANT, TypeKind.IntType, 0, 4), + ICONST_5(Classfile.ICONST_5, 1, Kind.CONSTANT, TypeKind.IntType, 0, 5), + LCONST_0(Classfile.LCONST_0, 1, Kind.CONSTANT, TypeKind.LongType, 0, 0L), + LCONST_1(Classfile.LCONST_1, 1, Kind.CONSTANT, TypeKind.LongType, 0, 1L), + FCONST_0(Classfile.FCONST_0, 1, Kind.CONSTANT, TypeKind.FloatType, 0, 0.0f), + FCONST_1(Classfile.FCONST_1, 1, Kind.CONSTANT, TypeKind.FloatType, 0, 1.0f), + FCONST_2(Classfile.FCONST_2, 1, Kind.CONSTANT, TypeKind.FloatType, 0, 2.0f), + DCONST_0(Classfile.DCONST_0, 1, Kind.CONSTANT, TypeKind.DoubleType, 0, 0.0d), + DCONST_1(Classfile.DCONST_1, 1, Kind.CONSTANT, TypeKind.DoubleType, 0, 1.0d), + BIPUSH(Classfile.BIPUSH, 2, Kind.CONSTANT, TypeKind.ByteType), + SIPUSH(Classfile.SIPUSH, 3, Kind.CONSTANT, TypeKind.ShortType), + LDC(Classfile.LDC, 2, Kind.CONSTANT), + LDC_W(Classfile.LDC_W, 3, Kind.CONSTANT), + LDC2_W(Classfile.LDC2_W, 3, Kind.CONSTANT), + ILOAD(Classfile.ILOAD, 2, Kind.LOAD, TypeKind.IntType, -1), + LLOAD(Classfile.LLOAD, 2, Kind.LOAD, TypeKind.LongType, -1), + FLOAD(Classfile.FLOAD, 2, Kind.LOAD, TypeKind.FloatType, -1), + DLOAD(Classfile.DLOAD, 2, Kind.LOAD, TypeKind.DoubleType, -1), + ALOAD(Classfile.ALOAD, 2, Kind.LOAD, TypeKind.ReferenceType, -1), + ILOAD_0(Classfile.ILOAD_0, 1, Kind.LOAD, TypeKind.IntType, 0), + ILOAD_1(Classfile.ILOAD_1, 1, Kind.LOAD, TypeKind.IntType, 1), + ILOAD_2(Classfile.ILOAD_2, 1, Kind.LOAD, TypeKind.IntType, 2), + ILOAD_3(Classfile.ILOAD_3, 1, Kind.LOAD, TypeKind.IntType, 3), + LLOAD_0(Classfile.LLOAD_0, 1, Kind.LOAD, TypeKind.LongType, 0), + LLOAD_1(Classfile.LLOAD_1, 1, Kind.LOAD, TypeKind.LongType, 1), + LLOAD_2(Classfile.LLOAD_2, 1, Kind.LOAD, TypeKind.LongType, 2), + LLOAD_3(Classfile.LLOAD_3, 1, Kind.LOAD, TypeKind.LongType, 3), + FLOAD_0(Classfile.FLOAD_0, 1, Kind.LOAD, TypeKind.FloatType, 0), + FLOAD_1(Classfile.FLOAD_1, 1, Kind.LOAD, TypeKind.FloatType, 1), + FLOAD_2(Classfile.FLOAD_2, 1, Kind.LOAD, TypeKind.FloatType, 2), + FLOAD_3(Classfile.FLOAD_3, 1, Kind.LOAD, TypeKind.FloatType, 3), + DLOAD_0(Classfile.DLOAD_0, 1, Kind.LOAD, TypeKind.DoubleType, 0), + DLOAD_1(Classfile.DLOAD_1, 1, Kind.LOAD, TypeKind.DoubleType, 1), + DLOAD_2(Classfile.DLOAD_2, 1, Kind.LOAD, TypeKind.DoubleType, 2), + DLOAD_3(Classfile.DLOAD_3, 1, Kind.LOAD, TypeKind.DoubleType, 3), + ALOAD_0(Classfile.ALOAD_0, 1, Kind.LOAD, TypeKind.ReferenceType, 0), + ALOAD_1(Classfile.ALOAD_1, 1, Kind.LOAD, TypeKind.ReferenceType, 1), + ALOAD_2(Classfile.ALOAD_2, 1, Kind.LOAD, TypeKind.ReferenceType, 2), + ALOAD_3(Classfile.ALOAD_3, 1, Kind.LOAD, TypeKind.ReferenceType, 3), + IALOAD(Classfile.IALOAD, 1, Kind.ARRAY_LOAD, TypeKind.IntType), + LALOAD(Classfile.LALOAD, 1, Kind.ARRAY_LOAD, TypeKind.LongType), + FALOAD(Classfile.FALOAD, 1, Kind.ARRAY_LOAD, TypeKind.FloatType), + DALOAD(Classfile.DALOAD, 1, Kind.ARRAY_LOAD, TypeKind.DoubleType), + AALOAD(Classfile.AALOAD, 1, Kind.ARRAY_LOAD, TypeKind.ReferenceType), + BALOAD(Classfile.BALOAD, 1, Kind.ARRAY_LOAD, TypeKind.ByteType), + CALOAD(Classfile.CALOAD, 1, Kind.ARRAY_LOAD, TypeKind.CharType), + SALOAD(Classfile.SALOAD, 1, Kind.ARRAY_LOAD, TypeKind.ShortType), + ISTORE(Classfile.ISTORE, 2, Kind.STORE, TypeKind.IntType, -1), + LSTORE(Classfile.LSTORE, 2, Kind.STORE, TypeKind.LongType, -1), + FSTORE(Classfile.FSTORE, 2, Kind.STORE, TypeKind.FloatType, -1), + DSTORE(Classfile.DSTORE, 2, Kind.STORE, TypeKind.DoubleType, -1), + ASTORE(Classfile.ASTORE, 2, Kind.STORE, TypeKind.ReferenceType, -1), + ISTORE_0(Classfile.ISTORE_0, 1, Kind.STORE, TypeKind.IntType, 0), + ISTORE_1(Classfile.ISTORE_1, 1, Kind.STORE, TypeKind.IntType, 1), + ISTORE_2(Classfile.ISTORE_2, 1, Kind.STORE, TypeKind.IntType, 2), + ISTORE_3(Classfile.ISTORE_3, 1, Kind.STORE, TypeKind.IntType, 3), + LSTORE_0(Classfile.LSTORE_0, 1, Kind.STORE, TypeKind.LongType, 0), + LSTORE_1(Classfile.LSTORE_1, 1, Kind.STORE, TypeKind.LongType, 1), + LSTORE_2(Classfile.LSTORE_2, 1, Kind.STORE, TypeKind.LongType, 2), + LSTORE_3(Classfile.LSTORE_3, 1, Kind.STORE, TypeKind.LongType, 3), + FSTORE_0(Classfile.FSTORE_0, 1, Kind.STORE, TypeKind.FloatType, 0), + FSTORE_1(Classfile.FSTORE_1, 1, Kind.STORE, TypeKind.FloatType, 1), + FSTORE_2(Classfile.FSTORE_2, 1, Kind.STORE, TypeKind.FloatType, 2), + FSTORE_3(Classfile.FSTORE_3, 1, Kind.STORE, TypeKind.FloatType, 3), + DSTORE_0(Classfile.DSTORE_0, 1, Kind.STORE, TypeKind.DoubleType, 0), + DSTORE_1(Classfile.DSTORE_1, 1, Kind.STORE, TypeKind.DoubleType, 1), + DSTORE_2(Classfile.DSTORE_2, 1, Kind.STORE, TypeKind.DoubleType, 2), + DSTORE_3(Classfile.DSTORE_3, 1, Kind.STORE, TypeKind.DoubleType, 3), + ASTORE_0(Classfile.ASTORE_0, 1, Kind.STORE, TypeKind.ReferenceType, 0), + ASTORE_1(Classfile.ASTORE_1, 1, Kind.STORE, TypeKind.ReferenceType, 1), + ASTORE_2(Classfile.ASTORE_2, 1, Kind.STORE, TypeKind.ReferenceType, 2), + ASTORE_3(Classfile.ASTORE_3, 1, Kind.STORE, TypeKind.ReferenceType, 3), + IASTORE(Classfile.IASTORE, 1, Kind.ARRAY_STORE, TypeKind.IntType), + LASTORE(Classfile.LASTORE, 1, Kind.ARRAY_STORE, TypeKind.LongType), + FASTORE(Classfile.FASTORE, 1, Kind.ARRAY_STORE, TypeKind.FloatType), + DASTORE(Classfile.DASTORE, 1, Kind.ARRAY_STORE, TypeKind.DoubleType), + AASTORE(Classfile.AASTORE, 1, Kind.ARRAY_STORE, TypeKind.ReferenceType), + BASTORE(Classfile.BASTORE, 1, Kind.ARRAY_STORE, TypeKind.ByteType), + CASTORE(Classfile.CASTORE, 1, Kind.ARRAY_STORE, TypeKind.CharType), + SASTORE(Classfile.SASTORE, 1, Kind.ARRAY_STORE, TypeKind.ShortType), + POP(Classfile.POP, 1, Kind.STACK), + POP2(Classfile.POP2, 1, Kind.STACK), + DUP(Classfile.DUP, 1, Kind.STACK), + DUP_X1(Classfile.DUP_X1, 1, Kind.STACK), + DUP_X2(Classfile.DUP_X2, 1, Kind.STACK), + DUP2(Classfile.DUP2, 1, Kind.STACK), + DUP2_X1(Classfile.DUP2_X1, 1, Kind.STACK), + DUP2_X2(Classfile.DUP2_X2, 1, Kind.STACK), + SWAP(Classfile.SWAP, 1, Kind.STACK), + IADD(Classfile.IADD, 1, Kind.OPERATOR, TypeKind.IntType), + LADD(Classfile.LADD, 1, Kind.OPERATOR, TypeKind.LongType), + FADD(Classfile.FADD, 1, Kind.OPERATOR, TypeKind.FloatType), + DADD(Classfile.DADD, 1, Kind.OPERATOR, TypeKind.DoubleType), + ISUB(Classfile.ISUB, 1, Kind.OPERATOR, TypeKind.IntType), + LSUB(Classfile.LSUB, 1, Kind.OPERATOR, TypeKind.LongType), + FSUB(Classfile.FSUB, 1, Kind.OPERATOR, TypeKind.FloatType), + DSUB(Classfile.DSUB, 1, Kind.OPERATOR, TypeKind.DoubleType), + IMUL(Classfile.IMUL, 1, Kind.OPERATOR, TypeKind.IntType), + LMUL(Classfile.LMUL, 1, Kind.OPERATOR, TypeKind.LongType), + FMUL(Classfile.FMUL, 1, Kind.OPERATOR, TypeKind.FloatType), + DMUL(Classfile.DMUL, 1, Kind.OPERATOR, TypeKind.DoubleType), + IDIV(Classfile.IDIV, 1, Kind.OPERATOR, TypeKind.IntType), + LDIV(Classfile.LDIV, 1, Kind.OPERATOR, TypeKind.LongType), + FDIV(Classfile.FDIV, 1, Kind.OPERATOR, TypeKind.FloatType), + DDIV(Classfile.DDIV, 1, Kind.OPERATOR, TypeKind.DoubleType), + IREM(Classfile.IREM, 1, Kind.OPERATOR, TypeKind.IntType), + LREM(Classfile.LREM, 1, Kind.OPERATOR, TypeKind.LongType), + FREM(Classfile.FREM, 1, Kind.OPERATOR, TypeKind.FloatType), + DREM(Classfile.DREM, 1, Kind.OPERATOR, TypeKind.DoubleType), + INEG(Classfile.INEG, 1, Kind.OPERATOR, TypeKind.IntType), + LNEG(Classfile.LNEG, 1, Kind.OPERATOR, TypeKind.LongType), + FNEG(Classfile.FNEG, 1, Kind.OPERATOR, TypeKind.FloatType), + DNEG(Classfile.DNEG, 1, Kind.OPERATOR, TypeKind.DoubleType), + ISHL(Classfile.ISHL, 1, Kind.OPERATOR, TypeKind.IntType), + LSHL(Classfile.LSHL, 1, Kind.OPERATOR, TypeKind.LongType), + ISHR(Classfile.ISHR, 1, Kind.OPERATOR, TypeKind.IntType), + LSHR(Classfile.LSHR, 1, Kind.OPERATOR, TypeKind.LongType), + IUSHR(Classfile.IUSHR, 1, Kind.OPERATOR, TypeKind.IntType), + LUSHR(Classfile.LUSHR, 1, Kind.OPERATOR, TypeKind.LongType), + IAND(Classfile.IAND, 1, Kind.OPERATOR, TypeKind.IntType), + LAND(Classfile.LAND, 1, Kind.OPERATOR, TypeKind.LongType), + IOR(Classfile.IOR, 1, Kind.OPERATOR, TypeKind.IntType), + LOR(Classfile.LOR, 1, Kind.OPERATOR, TypeKind.LongType), + IXOR(Classfile.IXOR, 1, Kind.OPERATOR, TypeKind.IntType), + LXOR(Classfile.LXOR, 1, Kind.OPERATOR, TypeKind.LongType), + IINC(Classfile.IINC, 3, Kind.INCREMENT, TypeKind.IntType, -1), + I2L(Classfile.I2L, 1, Kind.CONVERT, TypeKind.IntType, TypeKind.LongType), + I2F(Classfile.I2F, 1, Kind.CONVERT, TypeKind.IntType, TypeKind.FloatType), + I2D(Classfile.I2D, 1, Kind.CONVERT, TypeKind.IntType, TypeKind.DoubleType), + L2I(Classfile.L2I, 1, Kind.CONVERT, TypeKind.LongType, TypeKind.IntType), + L2F(Classfile.L2F, 1, Kind.CONVERT, TypeKind.LongType, TypeKind.FloatType), + L2D(Classfile.L2D, 1, Kind.CONVERT, TypeKind.LongType, TypeKind.DoubleType), + F2I(Classfile.F2I, 1, Kind.CONVERT, TypeKind.FloatType, TypeKind.IntType), + F2L(Classfile.F2L, 1, Kind.CONVERT, TypeKind.FloatType, TypeKind.LongType), + F2D(Classfile.F2D, 1, Kind.CONVERT, TypeKind.FloatType, TypeKind.DoubleType), + D2I(Classfile.D2I, 1, Kind.CONVERT, TypeKind.DoubleType, TypeKind.IntType), + D2L(Classfile.D2L, 1, Kind.CONVERT, TypeKind.DoubleType, TypeKind.LongType), + D2F(Classfile.D2F, 1, Kind.CONVERT, TypeKind.DoubleType, TypeKind.FloatType), + I2B(Classfile.I2B, 1, Kind.CONVERT, TypeKind.IntType, TypeKind.ByteType), + I2C(Classfile.I2C, 1, Kind.CONVERT, TypeKind.IntType, TypeKind.CharType), + I2S(Classfile.I2S, 1, Kind.CONVERT, TypeKind.IntType, TypeKind.ShortType), + LCMP(Classfile.LCMP, 1, Kind.OPERATOR, TypeKind.LongType), + FCMPL(Classfile.FCMPL, 1, Kind.OPERATOR, TypeKind.FloatType), + FCMPG(Classfile.FCMPG, 1, Kind.OPERATOR, TypeKind.FloatType), + DCMPL(Classfile.DCMPL, 1, Kind.OPERATOR, TypeKind.DoubleType), + DCMPG(Classfile.DCMPG, 1, Kind.OPERATOR, TypeKind.DoubleType), + IFEQ(Classfile.IFEQ, 3, Kind.BRANCH, TypeKind.IntType), + IFNE(Classfile.IFNE, 3, Kind.BRANCH, TypeKind.IntType), + IFLT(Classfile.IFLT, 3, Kind.BRANCH, TypeKind.IntType), + IFGE(Classfile.IFGE, 3, Kind.BRANCH, TypeKind.IntType), + IFGT(Classfile.IFGT, 3, Kind.BRANCH, TypeKind.IntType), + IFLE(Classfile.IFLE, 3, Kind.BRANCH, TypeKind.IntType), + IF_ICMPEQ(Classfile.IF_ICMPEQ, 3, Kind.BRANCH, TypeKind.IntType), + IF_ICMPNE(Classfile.IF_ICMPNE, 3, Kind.BRANCH, TypeKind.IntType), + IF_ICMPLT(Classfile.IF_ICMPLT, 3, Kind.BRANCH, TypeKind.IntType), + IF_ICMPGE(Classfile.IF_ICMPGE, 3, Kind.BRANCH, TypeKind.IntType), + IF_ICMPGT(Classfile.IF_ICMPGT, 3, Kind.BRANCH, TypeKind.IntType), + IF_ICMPLE(Classfile.IF_ICMPLE, 3, Kind.BRANCH, TypeKind.IntType), + IF_ACMPEQ(Classfile.IF_ACMPEQ, 3, Kind.BRANCH, TypeKind.ReferenceType), + IF_ACMPNE(Classfile.IF_ACMPNE, 3, Kind.BRANCH, TypeKind.ReferenceType), + GOTO(Classfile.GOTO, 3, Kind.BRANCH, TypeKind.VoidType), + JSR(Classfile.JSR, 3, Kind.UNSUPPORTED), + RET(Classfile.RET, 2, Kind.UNSUPPORTED), + TABLESWITCH(Classfile.TABLESWITCH, -1, Kind.TABLE_SWITCH), + LOOKUPSWITCH(Classfile.LOOKUPSWITCH, -1, Kind.LOOKUP_SWITCH), + IRETURN(Classfile.IRETURN, 1, Kind.RETURN, TypeKind.IntType), + LRETURN(Classfile.LRETURN, 1, Kind.RETURN, TypeKind.LongType), + FRETURN(Classfile.FRETURN, 1, Kind.RETURN, TypeKind.FloatType), + DRETURN(Classfile.DRETURN, 1, Kind.RETURN, TypeKind.DoubleType), + ARETURN(Classfile.ARETURN, 1, Kind.RETURN, TypeKind.ReferenceType), + RETURN(Classfile.RETURN, 1, Kind.RETURN, TypeKind.VoidType), + GETSTATIC(Classfile.GETSTATIC, 3, Kind.FIELD_ACCESS), + PUTSTATIC(Classfile.PUTSTATIC, 3, Kind.FIELD_ACCESS), + GETFIELD(Classfile.GETFIELD, 3, Kind.FIELD_ACCESS), + PUTFIELD(Classfile.PUTFIELD, 3, Kind.FIELD_ACCESS), + INVOKEVIRTUAL(Classfile.INVOKEVIRTUAL, 3, Kind.INVOKE), + INVOKESPECIAL(Classfile.INVOKESPECIAL, 3, Kind.INVOKE), + INVOKESTATIC(Classfile.INVOKESTATIC, 3, Kind.INVOKE), + INVOKEINTERFACE(Classfile.INVOKEINTERFACE, 5, Kind.INVOKE), + INVOKEDYNAMIC(Classfile.INVOKEDYNAMIC, 5, Kind.INVOKE_DYNAMIC), + NEW(Classfile.NEW, 3, Kind.NEW_OBJECT), + NEWARRAY(Classfile.NEWARRAY, 2, Kind.NEW_PRIMITIVE_ARRAY), + ANEWARRAY(Classfile.ANEWARRAY, 3, Kind.NEW_REF_ARRAY), + ARRAYLENGTH(Classfile.ARRAYLENGTH, 1, Kind.OPERATOR, TypeKind.IntType), + ATHROW(Classfile.ATHROW, 1, Kind.THROW_EXCEPTION), + CHECKCAST(Classfile.CHECKCAST, 3, Kind.TYPE_CHECK), + INSTANCEOF(Classfile.INSTANCEOF, 3, Kind.TYPE_CHECK), + MONITORENTER(Classfile.MONITORENTER, 1, Kind.MONITOR), + MONITOREXIT(Classfile.MONITOREXIT, 1, Kind.MONITOR), + MULTIANEWARRAY(Classfile.MULTIANEWARRAY, 4, Kind.NEW_MULTI_ARRAY), + IFNULL(Classfile.IFNULL, 3, Kind.BRANCH, TypeKind.ReferenceType), + IFNONNULL(Classfile.IFNONNULL, 3, Kind.BRANCH, TypeKind.IntType), + GOTO_W(Classfile.GOTO_W, 5, Kind.BRANCH, TypeKind.VoidType), + JSR_W(Classfile.JSR_W, 5, Kind.UNSUPPORTED), + ILOAD_W((Classfile.WIDE << 8) | Classfile.ILOAD, 4, Kind.LOAD, TypeKind.IntType, -1), + LLOAD_W((Classfile.WIDE << 8) | Classfile.LLOAD, 4, Kind.LOAD, TypeKind.LongType, -1), + FLOAD_W((Classfile.WIDE << 8) | Classfile.FLOAD, 4, Kind.LOAD, TypeKind.FloatType, -1), + DLOAD_W((Classfile.WIDE << 8) | Classfile.DLOAD, 4, Kind.LOAD, TypeKind.DoubleType, -1), + ALOAD_W((Classfile.WIDE << 8) | Classfile.ALOAD, 4, Kind.LOAD, TypeKind.ReferenceType, -1), + ISTORE_W((Classfile.WIDE << 8) | Classfile.ISTORE, 4, Kind.STORE, TypeKind.IntType, -1), + LSTORE_W((Classfile.WIDE << 8) | Classfile.LSTORE, 4, Kind.STORE, TypeKind.LongType, -1), + FSTORE_W((Classfile.WIDE << 8) | Classfile.FSTORE, 4, Kind.STORE, TypeKind.FloatType, -1), + DSTORE_W((Classfile.WIDE << 8) | Classfile.DSTORE, 4, Kind.STORE, TypeKind.DoubleType, -1), + ASTORE_W((Classfile.WIDE << 8) | Classfile.ASTORE, 4, Kind.STORE, TypeKind.ReferenceType, -1), + RET_W((Classfile.WIDE << 8) | Classfile.RET, 4, Kind.UNSUPPORTED), + IINC_W((Classfile.WIDE << 8) | Classfile.IINC, 6, Kind.INCREMENT, TypeKind.IntType, -1); + + /** + * Kinds of opcodes. + */ + public static enum Kind { + LOAD, STORE, INCREMENT, BRANCH, LOOKUP_SWITCH, TABLE_SWITCH, RETURN, THROW_EXCEPTION, + FIELD_ACCESS, INVOKE, INVOKE_DYNAMIC, + NEW_OBJECT, NEW_PRIMITIVE_ARRAY, NEW_REF_ARRAY, NEW_MULTI_ARRAY, + TYPE_CHECK, ARRAY_LOAD, ARRAY_STORE, STACK, CONVERT, OPERATOR, CONSTANT, + MONITOR, NOP, UNSUPPORTED; + } + + private final int bytecode; + private final int sizeIfFixed; + private final Kind kind; + private final TypeKind primaryTypeKind; + private final TypeKind secondaryTypeKind; + private final int slot; + private final ConstantDesc constantValue; + + Opcode(int bytecode, int sizeIfFixed, Kind kind) { + this(bytecode, sizeIfFixed, kind, null, null, 0, null); + } + + Opcode(int bytecode, int sizeIfFixed, Kind kind, TypeKind typeKind) { + this(bytecode, sizeIfFixed, kind, typeKind, null, 0, null); + } + + Opcode(int bytecode, int sizeIfFixed, Kind kind, TypeKind typeKind, int slot) { + this(bytecode, sizeIfFixed, kind, typeKind, null, slot, null); + } + + Opcode(int bytecode, int sizeIfFixed, Kind kind, TypeKind typeKind, int slot, ConstantDesc constantValue) { + this(bytecode, sizeIfFixed, kind, typeKind, null, slot, constantValue); + } + + Opcode(int bytecode, int sizeIfFixed, Kind kind, TypeKind primaryTypeKind, TypeKind secondaryTypeKind) { + this(bytecode, sizeIfFixed, kind, primaryTypeKind, secondaryTypeKind, 0, null); + } + + Opcode(int bytecode, + int sizeIfFixed, + Kind kind, + TypeKind primaryTypeKind, + TypeKind secondaryTypeKind, + int slot, + ConstantDesc constantValue) { + this.bytecode = bytecode; + this.sizeIfFixed = sizeIfFixed; + this.kind = kind; + this.primaryTypeKind = primaryTypeKind; + this.secondaryTypeKind = secondaryTypeKind; + this.slot = slot; + this.constantValue = constantValue; + } + + public int bytecode() { return bytecode; } + + public boolean isWide() { return bytecode > 255; } + + public int sizeIfFixed() { return sizeIfFixed; } + + public Kind kind() { return kind; } + + public TypeKind primaryTypeKind() { + return primaryTypeKind; + } + + public TypeKind secondaryTypeKind() { + return secondaryTypeKind; + } + + public int slot() { + return slot; + } + + public ConstantDesc constantValue() { + return constantValue; + } + + public boolean isUnconditionalBranch() { + return switch (this) { + case GOTO, ATHROW, GOTO_W, LOOKUPSWITCH, TABLESWITCH -> true; + default -> kind() == Kind.RETURN; + }; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/PseudoInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/PseudoInstruction.java new file mode 100644 index 0000000000000..8711e80747b59 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/PseudoInstruction.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.instruction.CharacterRange; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.LabelTarget; +import jdk.internal.classfile.instruction.LineNumber; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; + +/** + * Models metadata about a {@link CodeAttribute}, such as entries in the + * exception table, line number table, local variable table, or the mapping + * between instructions and labels. Pseudo-instructions are delivered as part + * of the element stream of a {@link CodeModel}. Delivery of some + * pseudo-instructions can be disabled by modifying the value of classfile + * options (e.g., {@link Classfile.Option#processDebug(boolean)}). + */ +public sealed interface PseudoInstruction + extends CodeElement + permits CharacterRange, ExceptionCatch, LabelTarget, LineNumber, LocalVariable, LocalVariableType, AbstractPseudoInstruction { +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Signature.java b/src/java.base/share/classes/jdk/internal/classfile/Signature.java new file mode 100644 index 0000000000000..9e513ff622b87 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Signature.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import java.lang.constant.ClassDesc; +import jdk.internal.classfile.impl.SignaturesImpl; + +import java.util.List; +import static java.util.Objects.requireNonNull; +import java.util.Optional; +import jdk.internal.classfile.impl.Util; + +/** + * Models generic Java type signatures, as defined in {@jvms 4.7.9.1}. + */ +public sealed interface Signature { + + /** {@return the raw signature string} */ + String signatureString(); + + /** + * Parses generic Java type signature from raw string + * @param javaTypeSignature raw Java type signature string + * @return Java type signature + */ + public static Signature parseFrom(String javaTypeSignature) { + return new SignaturesImpl().parseSignature(requireNonNull(javaTypeSignature)); + } + + /** + * @param classDesc the symbolic description of the Java type + * @return Java type signature + */ + public static Signature of(ClassDesc classDesc) { + requireNonNull(classDesc); + if (classDesc.isArray()) + return ArrayTypeSig.of(of(classDesc.componentType())); + if (classDesc.isPrimitive()) + return BaseTypeSig.of(classDesc); + return ClassTypeSig.of(classDesc); + } + + /** + * Models the signature of a primitive type or void + */ + public sealed interface BaseTypeSig extends Signature + permits SignaturesImpl.BaseTypeSigImpl { + + /** {@return the single-letter descriptor for the base type} */ + char baseType(); + + /** + * {@return the signature of a primitive type or void} + * @param classDesc a symbolic descriptor for the base type, must correspond + * to a primitive type + */ + public static BaseTypeSig of(ClassDesc classDesc) { + requireNonNull(classDesc); + if (!classDesc.isPrimitive()) + throw new IllegalArgumentException("primitive class type required"); + return new SignaturesImpl.BaseTypeSigImpl(classDesc.descriptorString().charAt(0)); + } + + /** + * {@return the signature of a primitive type or void} + * @param baseType the single-letter descriptor for the base type + */ + public static BaseTypeSig of(char baseType) { + if ("VIJCSBFDZ".indexOf(baseType) < 0) + throw new IllegalArgumentException("invalid base type signature"); + return new SignaturesImpl.BaseTypeSigImpl(baseType); + } + } + + /** + * Models the signature of a reference type, which may be a class, interface, + * type variable, or array type. + */ + public sealed interface RefTypeSig + extends Signature + permits ArrayTypeSig, ClassTypeSig, TypeVarSig { + } + + /** + * Models the signature of a possibly-parameterized class or interface type. + */ + public sealed interface ClassTypeSig + extends RefTypeSig, ThrowableSig + permits SignaturesImpl.ClassTypeSigImpl { + + /** {@return the signature of the outer type, if any} */ + Optional outerType(); + + /** {@return the class name} */ + String className(); + + /** {@return the class name, as a symbolic descriptor} */ + default ClassDesc classDesc() { + return ClassDesc.ofInternalName(className()); + } + + /** {@return the type arguments of the class} */ + List typeArgs(); + + /** + * {@return a class type signature} + * @param className the name of the class + * @param typeArgs signatures of the type arguments + */ + public static ClassTypeSig of(ClassDesc className, TypeArg... typeArgs) { + return of(null, className, typeArgs); + } + + /** + * {@return a class type signature for an inner class} + * @param outerType signature of the outer type + * @param className the name of the class + * @param typeArgs signatures of the type arguments + */ + public static ClassTypeSig of(ClassTypeSig outerType, ClassDesc className, TypeArg... typeArgs) { + requireNonNull(className); + return of(outerType, Util.toInternalName(className), typeArgs); + } + + /** + * {@return a class type signature} + * @param className the name of the class + * @param typeArgs signatures of the type arguments + */ + public static ClassTypeSig of(String className, TypeArg... typeArgs) { + return of(null, className, typeArgs); + } + + /** + * {@return a class type signature for an inner class} + * @param outerType signature of the outer type + * @param className the name of the class + * @param typeArgs signatures of the type arguments + */ + public static ClassTypeSig of(ClassTypeSig outerType, String className, TypeArg... typeArgs) { + requireNonNull(className); + return new SignaturesImpl.ClassTypeSigImpl(Optional.ofNullable(outerType), className.replace(".", "/"), List.of(typeArgs)); + } + } + + /** + * Models the type argument. + */ + public sealed interface TypeArg + permits SignaturesImpl.TypeArgImpl { + + /** + * Indicator for whether a wildcard has default bound, no bound, + * an upper bound, or a lower bound + */ + public enum WildcardIndicator { + DEFAULT, UNBOUNDED, EXTENDS, SUPER; + } + + /** {@return the wildcard indicator} */ + WildcardIndicator wildcardIndicator(); + + /** {@return the signature of the type bound, if any} */ + Optional boundType(); + + /** + * {@return a bounded type arg} + * @param boundType the bound + */ + public static TypeArg of(RefTypeSig boundType) { + requireNonNull(boundType); + return of(WildcardIndicator.DEFAULT, Optional.of(boundType)); + } + + /** + * {@return an unbounded type arg} + */ + public static TypeArg unbounded() { + return of(WildcardIndicator.UNBOUNDED, Optional.empty()); + } + + /** + * {@return an upper-bounded type arg} + * @param boundType the upper bound + */ + public static TypeArg extendsOf(RefTypeSig boundType) { + requireNonNull(boundType); + return of(WildcardIndicator.EXTENDS, Optional.of(boundType)); + } + + /** + * {@return a lower-bounded type arg} + * @param boundType the lower bound + */ + public static TypeArg superOf(RefTypeSig boundType) { + requireNonNull(boundType); + return of(WildcardIndicator.SUPER, Optional.of(boundType)); + } + + /** + * {@return a bounded type arg} + * @param wildcard the wild card + * @param boundType optional bound type + */ + public static TypeArg of(WildcardIndicator wildcard, Optional boundType) { + return new SignaturesImpl.TypeArgImpl(wildcard, boundType); + } + } + + /** + * Models the signature of a type variable. + */ + public sealed interface TypeVarSig + extends RefTypeSig, ThrowableSig + permits SignaturesImpl.TypeVarSigImpl { + + /** {@return the name of the type variable} */ + String identifier(); + + /** + * {@return a signature for a type variable} + * @param identifier the name of the type variable + */ + public static TypeVarSig of(String identifier) { + return new SignaturesImpl.TypeVarSigImpl(requireNonNull(identifier)); + } + } + + /** + * Models the signature of an array type. + */ + public sealed interface ArrayTypeSig + extends RefTypeSig + permits SignaturesImpl.ArrayTypeSigImpl { + + /** {@return the signature of the component type} */ + Signature componentSignature(); + + /** + * {@return a signature for an array type} + * @param componentSignature the component type + */ + public static ArrayTypeSig of(Signature componentSignature) { + return of(1, requireNonNull(componentSignature)); + } + + /** + * {@return a signature for an array type} + * @param dims the dimension of the array + * @param componentSignature the component type + */ + public static ArrayTypeSig of(int dims, Signature componentSignature) { + requireNonNull(componentSignature); + if (dims < 1 || dims > 255) + throw new IllegalArgumentException("illegal array depth value"); + if (componentSignature instanceof SignaturesImpl.ArrayTypeSigImpl arr) + return new SignaturesImpl.ArrayTypeSigImpl(dims + arr.arrayDepth(), arr.elemType()); + return new SignaturesImpl.ArrayTypeSigImpl(dims, componentSignature); + } + } + + /** + * Models a signature for a type parameter of a generic class or method. + */ + public sealed interface TypeParam + permits SignaturesImpl.TypeParamImpl { + + /** {@return the name of the type parameter} */ + String identifier(); + + /** {@return the class bound of the type parameter} */ + Optional classBound(); + + /** {@return the interface bounds of the type parameter} */ + List interfaceBounds(); + + /** + * {@return a signature for a type parameter} + * @param identifier the name of the type parameter + * @param classBound the class bound of the type parameter + * @param interfaceBounds the interface bounds of the type parameter + */ + public static TypeParam of(String identifier, RefTypeSig classBound, RefTypeSig... interfaceBounds) { + return new SignaturesImpl.TypeParamImpl( + requireNonNull(identifier), + Optional.ofNullable(classBound), + List.of(interfaceBounds)); + } + + /** + * {@return a signature for a type parameter} + * @param identifier the name of the type parameter + * @param classBound the class bound of the type parameter + * @param interfaceBounds the interface bounds of the type parameter + */ + public static TypeParam of(String identifier, Optional classBound, RefTypeSig... interfaceBounds) { + return new SignaturesImpl.TypeParamImpl( + requireNonNull(identifier), + requireNonNull(classBound), + List.of(interfaceBounds)); + } + } + + /** + * Models a signature for a throwable type. + */ + public sealed interface ThrowableSig extends Signature { + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/Superclass.java b/src/java.base/share/classes/jdk/internal/classfile/Superclass.java new file mode 100644 index 0000000000000..f8ca2359b5cf2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/Superclass.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.SuperclassImpl; + +/** + * Models the superclass of a class. Delivered as a {@link + * jdk.internal.classfile.ClassElement} when traversing a {@link ClassModel}. + */ +public sealed interface Superclass + extends ClassElement + permits SuperclassImpl { + + /** {@return the superclass} */ + ClassEntry superclassEntry(); + + /** + * {@return a {@linkplain Superclass} element} + * @param superclassEntry the superclass + */ + static Superclass of(ClassEntry superclassEntry) { + return new SuperclassImpl(superclassEntry); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/TypeAnnotation.java b/src/java.base/share/classes/jdk/internal/classfile/TypeAnnotation.java new file mode 100644 index 0000000000000..be4aa1b217945 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/TypeAnnotation.java @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +import java.lang.constant.ClassDesc; +import java.util.List; + +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.TargetInfoImpl; +import jdk.internal.classfile.impl.UnboundAttribute; + +import static jdk.internal.classfile.Classfile.TAT_CAST; +import static jdk.internal.classfile.Classfile.TAT_CLASS_EXTENDS; +import static jdk.internal.classfile.Classfile.TAT_CLASS_TYPE_PARAMETER; +import static jdk.internal.classfile.Classfile.TAT_CLASS_TYPE_PARAMETER_BOUND; +import static jdk.internal.classfile.Classfile.TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT; +import static jdk.internal.classfile.Classfile.TAT_CONSTRUCTOR_REFERENCE; +import static jdk.internal.classfile.Classfile.TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT; +import static jdk.internal.classfile.Classfile.TAT_EXCEPTION_PARAMETER; +import static jdk.internal.classfile.Classfile.TAT_FIELD; +import static jdk.internal.classfile.Classfile.TAT_INSTANCEOF; +import static jdk.internal.classfile.Classfile.TAT_LOCAL_VARIABLE; +import static jdk.internal.classfile.Classfile.TAT_METHOD_FORMAL_PARAMETER; +import static jdk.internal.classfile.Classfile.TAT_METHOD_INVOCATION_TYPE_ARGUMENT; +import static jdk.internal.classfile.Classfile.TAT_METHOD_RECEIVER; +import static jdk.internal.classfile.Classfile.TAT_METHOD_REFERENCE; +import static jdk.internal.classfile.Classfile.TAT_METHOD_REFERENCE_TYPE_ARGUMENT; +import static jdk.internal.classfile.Classfile.TAT_METHOD_RETURN; +import static jdk.internal.classfile.Classfile.TAT_METHOD_TYPE_PARAMETER; +import static jdk.internal.classfile.Classfile.TAT_METHOD_TYPE_PARAMETER_BOUND; +import static jdk.internal.classfile.Classfile.TAT_NEW; +import static jdk.internal.classfile.Classfile.TAT_RESOURCE_VARIABLE; +import static jdk.internal.classfile.Classfile.TAT_THROWS; +import jdk.internal.classfile.impl.TemporaryConstantPool; + +/** + * Models an annotation on a type use. + * + * @see RuntimeVisibleTypeAnnotationsAttribute + * @see RuntimeInvisibleTypeAnnotationsAttribute + */ +public sealed interface TypeAnnotation + extends Annotation + permits UnboundAttribute.UnboundTypeAnnotation { + + /** + * The kind of target on which the annotation appears. + */ + public enum TargetType { + /** For annotations on a class type parameter declaration. */ + CLASS_TYPE_PARAMETER(TAT_CLASS_TYPE_PARAMETER, 1), + + /** For annotations on a method type parameter declaration. */ + METHOD_TYPE_PARAMETER(TAT_METHOD_TYPE_PARAMETER, 1), + + /** For annotations on the type of an "extends" or "implements" clause. */ + CLASS_EXTENDS(TAT_CLASS_EXTENDS, 2), + + /** For annotations on a bound of a type parameter of a class. */ + CLASS_TYPE_PARAMETER_BOUND(TAT_CLASS_TYPE_PARAMETER_BOUND, 2), + + /** For annotations on a bound of a type parameter of a method. */ + METHOD_TYPE_PARAMETER_BOUND(TAT_METHOD_TYPE_PARAMETER_BOUND, 2), + + /** For annotations on a field. */ + FIELD(TAT_FIELD, 0), + + /** For annotations on a method return type. */ + METHOD_RETURN(TAT_METHOD_RETURN, 0), + + /** For annotations on the method receiver. */ + METHOD_RECEIVER(TAT_METHOD_RECEIVER, 0), + + /** For annotations on a method parameter. */ + METHOD_FORMAL_PARAMETER(TAT_METHOD_FORMAL_PARAMETER, 1), + + /** For annotations on a throws clause in a method declaration. */ + THROWS(TAT_THROWS, 2), + + /** For annotations on a local variable. */ + LOCAL_VARIABLE(TAT_LOCAL_VARIABLE, -1), + + /** For annotations on a resource variable. */ + RESOURCE_VARIABLE(TAT_RESOURCE_VARIABLE, -1), + + /** For annotations on an exception parameter. */ + EXCEPTION_PARAMETER(TAT_EXCEPTION_PARAMETER, 2), + + /** For annotations on a type test. */ + INSTANCEOF(TAT_INSTANCEOF, 2), + + /** For annotations on an object creation expression. */ + NEW(TAT_NEW, 2), + + /** For annotations on a constructor reference receiver. */ + CONSTRUCTOR_REFERENCE(TAT_CONSTRUCTOR_REFERENCE, 2), + + /** For annotations on a method reference receiver. */ + METHOD_REFERENCE(TAT_METHOD_REFERENCE, 2), + + /** For annotations on a typecast. */ + CAST(TAT_CAST, 3), + + /** For annotations on a type argument of an object creation expression. */ + CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT(TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, 3), + + /** For annotations on a type argument of a method call. */ + METHOD_INVOCATION_TYPE_ARGUMENT(TAT_METHOD_INVOCATION_TYPE_ARGUMENT, 3), + + /** For annotations on a type argument of a constructor reference. */ + CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT(TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, 3), + + /** For annotations on a type argument of a method reference. */ + METHOD_REFERENCE_TYPE_ARGUMENT(TAT_METHOD_REFERENCE_TYPE_ARGUMENT, 3); + + private final int targetTypeValue; + private final int sizeIfFixed; + + private TargetType(int targetTypeValue, int sizeIfFixed) { + this.targetTypeValue = targetTypeValue; + this.sizeIfFixed = sizeIfFixed; + } + + public int targetTypeValue() { + return targetTypeValue; + } + + public int sizeIfFixed() { + return sizeIfFixed; + } + } + + /** + * {@return information describing precisely which type in a declaration or expression + * is annotated} + */ + TargetInfo targetInfo(); + + /** + * {@return which part of the type indicated by {@link #targetInfo()} is annotated} + */ + List targetPath(); + + /** + * {@return a type annotation} + * @param targetInfo which type in a declaration or expression is annotated + * @param targetPath which part of the type is annotated + * @param annotationClassUtf8Entry the annotation class + * @param annotationElements the annotation elements + */ + static TypeAnnotation of(TargetInfo targetInfo, List targetPath, + Utf8Entry annotationClassUtf8Entry, + List annotationElements) { + return new UnboundAttribute.UnboundTypeAnnotation(targetInfo, targetPath, + annotationClassUtf8Entry, annotationElements); + } + + /** + * {@return a type annotation} + * @param targetInfo which type in a declaration or expression is annotated + * @param targetPath which part of the type is annotated + * @param annotationClass the annotation class + * @param annotationElements the annotation elements + */ + static TypeAnnotation of(TargetInfo targetInfo, List targetPath, + ClassDesc annotationClass, + AnnotationElement... annotationElements) { + return of(targetInfo, targetPath, annotationClass, List.of(annotationElements)); + } + + /** + * {@return a type annotation} + * @param targetInfo which type in a declaration or expression is annotated + * @param targetPath which part of the type is annotated + * @param annotationClass the annotation class + * @param annotationElements the annotation elements + */ + static TypeAnnotation of(TargetInfo targetInfo, List targetPath, + ClassDesc annotationClass, + List annotationElements) { + return of(targetInfo, targetPath, + TemporaryConstantPool.INSTANCE.utf8Entry(annotationClass.descriptorString()), annotationElements); + } + + /** + * {@return a type annotation} + * @param targetInfo which type in a declaration or expression is annotated + * @param targetPath which part of the type is annotated + * @param annotationClassUtf8Entry the annotation class + * @param annotationElements the annotation elements + */ + static TypeAnnotation of(TargetInfo targetInfo, List targetPath, + Utf8Entry annotationClassUtf8Entry, + AnnotationElement... annotationElements) { + return of(targetInfo, targetPath, annotationClassUtf8Entry, List.of(annotationElements)); + } + + /** + * Specifies which type in a declaration or expression is being annotated. + */ + sealed interface TargetInfo { + + TargetType targetType(); + + default int size() { + return targetType().sizeIfFixed; + } + + static TypeParameterTarget ofTypeParameter(TargetType targetType, int typeParameterIndex) { + return new TargetInfoImpl.TypeParameterTargetImpl(targetType, typeParameterIndex); + } + + static TypeParameterTarget ofClassTypeParameter(int typeParameterIndex) { + return ofTypeParameter(TargetType.CLASS_TYPE_PARAMETER, typeParameterIndex); + } + + static TypeParameterTarget ofMethodTypeParameter(int typeParameterIndex) { + return ofTypeParameter(TargetType.METHOD_TYPE_PARAMETER, typeParameterIndex); + } + + static SupertypeTarget ofClassExtends(int supertypeIndex) { + return new TargetInfoImpl.SupertypeTargetImpl(supertypeIndex); + } + + static TypeParameterBoundTarget ofTypeParameterBound(TargetType targetType, int typeParameterIndex, int boundIndex) { + return new TargetInfoImpl.TypeParameterBoundTargetImpl(targetType, typeParameterIndex, boundIndex); + } + + static TypeParameterBoundTarget ofClassTypeParameterBound(int typeParameterIndex, int boundIndex) { + return ofTypeParameterBound(TargetType.CLASS_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + } + + static TypeParameterBoundTarget ofMethodTypeParameterBound(int typeParameterIndex, int boundIndex) { + return ofTypeParameterBound(TargetType.METHOD_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + } + + static EmptyTarget of(TargetType targetType) { + return new TargetInfoImpl.EmptyTargetImpl(targetType); + } + + static EmptyTarget ofField() { + return of(TargetType.FIELD); + } + + static EmptyTarget ofMethodReturn() { + return of(TargetType.METHOD_RETURN); + } + + static EmptyTarget ofMethodReceiver() { + return of(TargetType.METHOD_RECEIVER); + } + + static FormalParameterTarget ofMethodFormalParameter(int formalParameterIndex) { + return new TargetInfoImpl.FormalParameterTargetImpl(formalParameterIndex); + } + + static ThrowsTarget ofThrows(int throwsTargetIndex) { + return new TargetInfoImpl.ThrowsTargetImpl(throwsTargetIndex); + } + + static LocalVarTarget ofVariable(TargetType targetType, List table) { + return new TargetInfoImpl.LocalVarTargetImpl(targetType, table); + } + + static LocalVarTarget ofLocalVariable(List table) { + return ofVariable(TargetType.LOCAL_VARIABLE, table); + } + + static LocalVarTarget ofResourceVariable(List table) { + return ofVariable(TargetType.RESOURCE_VARIABLE, table); + } + + static CatchTarget ofExceptionParameter(int exceptionTableIndex) { + return new TargetInfoImpl.CatchTargetImpl(exceptionTableIndex); + } + + static OffsetTarget ofOffset(TargetType targetType, Label target) { + return new TargetInfoImpl.OffsetTargetImpl(targetType, target); + } + + static OffsetTarget ofInstanceofExpr(Label target) { + return ofOffset(TargetType.INSTANCEOF, target); + } + + static OffsetTarget ofNewExpr(Label target) { + return ofOffset(TargetType.NEW, target); + } + + static OffsetTarget ofConstructorReference(Label target) { + return ofOffset(TargetType.CONSTRUCTOR_REFERENCE, target); + } + + static OffsetTarget ofMethodReference(Label target) { + return ofOffset(TargetType.METHOD_REFERENCE, target); + } + + static TypeArgumentTarget ofTypeArgument(TargetType targetType, Label target, int typeArgumentIndex) { + return new TargetInfoImpl.TypeArgumentTargetImpl(targetType, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofCastExpr(Label target, int typeArgumentIndex) { + return ofTypeArgument(TargetType.CAST, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofConstructorInvocationTypeArgument(Label target, int typeArgumentIndex) { + return ofTypeArgument(TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofMethodInvocationTypeArgument(Label target, int typeArgumentIndex) { + return ofTypeArgument(TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofConstructorReferenceTypeArgument(Label target, int typeArgumentIndex) { + return ofTypeArgument(TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofMethodReferenceTypeArgument(Label target, int typeArgumentIndex) { + return ofTypeArgument(TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); + } + } + + /** + * Indicates that an annotation appears on the declaration of the i'th type + * parameter of a generic class, generic interface, generic method, or + * generic constructor. + */ + sealed interface TypeParameterTarget extends TargetInfo + permits TargetInfoImpl.TypeParameterTargetImpl { + + /** + * JVMS: The value of the type_parameter_index item specifies which type parameter declaration is annotated. + * A type_parameter_index value of 0 specifies the first type parameter declaration. + * + * @return the index into the type parameters + */ + int typeParameterIndex(); + } + + /** + * Indicates that an annotation appears on a type in the extends or implements + * clause of a class or interface declaration. + */ + sealed interface SupertypeTarget extends TargetInfo + permits TargetInfoImpl.SupertypeTargetImpl { + + /** + * JVMS: A supertype_index value of 65535 specifies that the annotation appears on the superclass in an extends + * clause of a class declaration. + * + * Any other supertype_index value is an index into the interfaces array of the enclosing ClassFile structure, + * and specifies that the annotation appears on that superinterface in either the implements clause of a class + * declaration or the extends clause of an interface declaration. + * + * @return the index into the interfaces array or 65535 to indicate it is the superclass + */ + int supertypeIndex(); + } + + /** + * Indicates that an annotation appears on the i'th bound of the j'th + * type parameter declaration of a generic class, interface, method, or + * constructor. + */ + sealed interface TypeParameterBoundTarget extends TargetInfo + permits TargetInfoImpl.TypeParameterBoundTargetImpl { + + /** + * Which type parameter declaration has an annotated bound. + * + * @return the zero-origin index into the type parameters + */ + int typeParameterIndex(); + + /** + * Which bound of the type parameter declaration is annotated. + * + * @return the zero-origin index into bounds on the type parameter + */ + int boundIndex(); + } + + /** + * Indicates that an annotation appears on either the type in a field + * declaration, the return type of a method, the type of a newly constructed + * object, or the receiver type of a method or constructor. + */ + sealed interface EmptyTarget extends TargetInfo + permits TargetInfoImpl.EmptyTargetImpl { + } + + /** + * Indicates that an annotation appears on the type in a formal parameter + * declaration of a method, constructor, or lambda expression. + */ + sealed interface FormalParameterTarget extends TargetInfo + permits TargetInfoImpl.FormalParameterTargetImpl { + + /** + * Which formal parameter declaration has an annotated type. + * + * @return the index into the formal parameter declarations, in the order + * declared in the source code + */ + int formalParameterIndex(); + } + + /** + * Indicates that an annotation appears on the i'th type in the throws + * clause of a method or constructor declaration. + */ + sealed interface ThrowsTarget extends TargetInfo + permits TargetInfoImpl.ThrowsTargetImpl { + + /** + * The index into the exception_index_table array of the + * Exceptions attribute of the method_info structure enclosing the + * RuntimeVisibleTypeAnnotations attribute. + * + * @return index into the list jdk.internal.classfile.attribute.ExceptionsAttribute.exceptions() + */ + int throwsTargetIndex(); + } + + /** + * Indicates that an annotation appears on the type in a local variable declaration, + * including a variable declared as a resource in a try-with-resources statement. + */ + sealed interface LocalVarTarget extends TargetInfo + permits TargetInfoImpl.LocalVarTargetImpl { + + /** + * @return the table of local variable location/indicies. + */ + List table(); + } + + /** + * Indicates a range of code array offsets within which a local variable + * has a value, and the index into the local variable array of the current + * frame at which that local variable can be found. + */ + sealed interface LocalVarTargetInfo + permits TargetInfoImpl.LocalVarTargetInfoImpl { + + /** + * The given local variable has a value at indices into the code array in the interval + * [start_pc, start_pc + length), that is, between start_pc inclusive and start_pc + length exclusive. + * + * @return the start of the bytecode section. + */ + Label startLabel(); + + + /** + * The given local variable has a value at indices into the code array in the interval + * [start_pc, start_pc + length), that is, between start_pc inclusive and start_pc + length exclusive. + * + * @return + */ + Label endLabel(); + + /** + * The given local variable must be at index in the local variable array of the current frame. + * + * If the local variable at index is of type double or long, it occupies both index and index + 1. + * + * @return index into the local variables + */ + int index(); + + static LocalVarTargetInfo of(Label startLabel, Label endLabel, int index) { + return new TargetInfoImpl.LocalVarTargetInfoImpl(startLabel, endLabel, index); + } + } + + /** + * Indicates that an annotation appears on the i'th type in an exception parameter + * declaration. + */ + sealed interface CatchTarget extends TargetInfo + permits TargetInfoImpl.CatchTargetImpl { + + /** + * The index into the exception_table array of the Code + * attribute enclosing the RuntimeVisibleTypeAnnotations attribute. + * + * @return the index into the exception table + */ + int exceptionTableIndex(); + } + + /** + * Indicates that an annotation appears on either the type in an instanceof expression + * or a new expression, or the type before the :: in a method reference expression. + */ + sealed interface OffsetTarget extends TargetInfo + permits TargetInfoImpl.OffsetTargetImpl { + + /** + * The code array offset of either the bytecode instruction + * corresponding to the instanceof expression, the new bytecode instruction corresponding to the new + * expression, or the bytecode instruction corresponding to the method reference expression. + * + * @return + */ + Label target(); + } + + /** + * Indicates that an annotation appears either on the i'th type in a cast + * expression, or on the i'th type argument in the explicit type argument list for any of the following: a new + * expression, an explicit constructor invocation statement, a method invocation expression, or a method reference + * expression. + */ + sealed interface TypeArgumentTarget extends TargetInfo + permits TargetInfoImpl.TypeArgumentTargetImpl { + + /** + * The code array offset of either the bytecode instruction + * corresponding to the cast expression, the new bytecode instruction corresponding to the new expression, the + * bytecode instruction corresponding to the explicit constructor invocation statement, the bytecode + * instruction corresponding to the method invocation expression, or the bytecode instruction corresponding to + * the method reference expression. + * + * @return + */ + Label target(); + + /** + * For a cast expression, the value of the type_argument_index item specifies which type in the cast + * operator is annotated. A type_argument_index value of 0 specifies the first (or only) type in the cast + * operator. + * + * The possibility of more than one type in a cast expression arises from a cast to an intersection type. + * + * For an explicit type argument list, the value of the type_argument_index item specifies which type argument + * is annotated. A type_argument_index value of 0 specifies the first type argument. + * + * @return the index into the type arguments + */ + int typeArgumentIndex(); + } + + /** + * JVMS: Wherever a type is used in a declaration or expression, the type_path structure identifies which part of + * the type is annotated. An annotation may appear on the type itself, but if the type is a reference type, then + * there are additional locations where an annotation may appear: + * + * If an array type T[] is used in a declaration or expression, then an annotation may appear on any component type + * of the array type, including the element type. + * + * If a nested type T1.T2 is used in a declaration or expression, then an annotation may appear on the name of the + * innermost member type and any enclosing type for which a type annotation is admissible {@jls 9.7.4}. + * + * If a parameterized type {@literal T or T or T} is used in a declaration or expression, then an + * annotation may appear on any type argument or on the bound of any wildcard type argument. + * + * JVMS: ... each entry in the path array represents an iterative, left-to-right step towards the precise location + * of the annotation in an array type, nested type, or parameterized type. (In an array type, the iteration visits + * the array type itself, then its component type, then the component type of that component type, and so on, + * until the element type is reached.) + */ + sealed interface TypePathComponent + permits UnboundAttribute.TypePathComponentImpl { + + public enum Kind { + ARRAY(0), + INNER_TYPE(1), + WILDCARD(2), + TYPE_ARGUMENT(3); + + private final int tag; + + private Kind(int tag) { + this.tag = tag; + } + + public int tag() { + return tag; + } + } + + TypePathComponent ARRAY = new UnboundAttribute.TypePathComponentImpl(Kind.ARRAY, 0); + TypePathComponent INNER_TYPE = new UnboundAttribute.TypePathComponentImpl(Kind.INNER_TYPE, 0); + TypePathComponent WILDCARD = new UnboundAttribute.TypePathComponentImpl(Kind.WILDCARD, 0); + + + /** + * The type path kind items from JVMS Table 4.7.20.2-A. + * + * @return the kind of path element + */ + Kind typePathKind(); + + /** + * JVMS: type_argument_index + * If the value of the type_path_kind item is 0, 1, or 2, then the value of the type_argument_index item is 0. + * + * If the value of the type_path_kind item is 3, then the value of the type_argument_index item specifies which + * type argument of a parameterized type is annotated, where 0 indicates the first type argument of a + * parameterized type. + * + * @return the index within the type component + */ + int typeArgumentIndex(); + + static TypePathComponent of(Kind typePathKind, int typeArgumentIndex) { + + return switch (typePathKind) { + case ARRAY -> ARRAY; + case INNER_TYPE -> INNER_TYPE; + case WILDCARD -> WILDCARD; + case TYPE_ARGUMENT -> new UnboundAttribute.TypePathComponentImpl(Kind.TYPE_ARGUMENT, typeArgumentIndex); + }; + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/TypeKind.java b/src/java.base/share/classes/jdk/internal/classfile/TypeKind.java new file mode 100644 index 0000000000000..245516b04f864 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/TypeKind.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile; + +/** + * Describes the types that can be part of a field or method descriptor. + */ +public enum TypeKind { + /** the primitive type byte */ + ByteType("byte", "B", 8), + /** the primitive type short */ + ShortType("short", "S", 9), + /** the primitive type int */ + IntType("int", "I", 10), + /** the primitive type float */ + FloatType("float", "F", 6), + /** the primitive type long */ + LongType("long", "J", 11), + /** the primitive type double */ + DoubleType("double", "D", 7), + /** a reference type */ + ReferenceType("reference type", "L", -1), + /** the primitive type char */ + CharType("char", "C", 5), + /** the primitive type boolean */ + BooleanType("boolean", "Z", 4), + /** void */ + VoidType("void", "V", -1); + + private static TypeKind[] newarraycodeToTypeTag; + + private final String name; + private final String descriptor; + private final int newarraycode; + + /** {@return the human-readable name corresponding to this type} */ + public String typeName() { return name; } + + /** {@return the field descriptor character corresponding to this type} */ + public String descriptor() { return descriptor; } + + /** {@return the code used by the {@code newarray} opcode corresponding to this type} */ + public int newarraycode() { + return newarraycode; + } + + /** + * {@return the number of local variable slots consumed by this type} + */ + public int slotSize() { + return switch (this) { + case VoidType -> 0; + case LongType, DoubleType -> 2; + default -> 1; + }; + } + + /** + * Erase this type kind to the type which will be used for xLOAD, xSTORE, + * and xRETURN bytecodes + * @return the erased type kind + */ + public TypeKind asLoadable() { + return switch (this) { + case BooleanType, ByteType, CharType, ShortType -> TypeKind.IntType; + default -> this; + }; + } + + TypeKind(String name, String descriptor, int newarraycode) { + this.name = name; + this.descriptor = descriptor; + this.newarraycode = newarraycode; + } + + /** + * {@return the type kind associated with the array type described by the + * array code used as an operand to {@code newarray}} + * @param newarraycode the operand of the {@code newarray} instruction + */ + public static TypeKind fromNewArrayCode(int newarraycode) { + if (newarraycodeToTypeTag == null) { + newarraycodeToTypeTag = new TypeKind[12]; + for (TypeKind tag : TypeKind.values()) { + if (tag.newarraycode > 0) newarraycodeToTypeTag[tag.newarraycode] = tag; + } + } + return newarraycodeToTypeTag[newarraycode]; + } + + /** + * {@return the type kind associated with the specified field descriptor} + * @param s the field descriptor + */ + public static TypeKind fromDescriptor(CharSequence s) { + return switch (s.charAt(0)) { + case '[', 'L' -> TypeKind.ReferenceType; + case 'B' -> TypeKind.ByteType; + case 'C' -> TypeKind.CharType; + case 'Z' -> TypeKind.BooleanType; + case 'S' -> TypeKind.ShortType; + case 'I' -> TypeKind.IntType; + case 'F' -> TypeKind.FloatType; + case 'J' -> TypeKind.LongType; + case 'D' -> TypeKind.DoubleType; + case 'V' -> TypeKind.VoidType; + default -> throw new IllegalStateException("Bad type: " + s); + }; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/WritableElement.java b/src/java.base/share/classes/jdk/internal/classfile/WritableElement.java new file mode 100644 index 0000000000000..8e75f996accbd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/WritableElement.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile; + +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.impl.DirectFieldBuilder; +import jdk.internal.classfile.impl.DirectMethodBuilder; + +/** + * A classfile element that can encode itself as a stream of bytes in the + * encoding expected by the classfile format. + * + * @param the type of the entity + */ +public sealed interface WritableElement extends ClassfileElement + permits Annotation, AnnotationElement, AnnotationValue, Attribute, + PoolEntry, BootstrapMethodEntry, FieldModel, MethodModel, + ConstantPoolBuilder, DirectFieldBuilder, DirectMethodBuilder { + /** + * Writes the element to the specified writer + * + * @param buf the writer + */ + void writeTo(BufWriter buf); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/AnnotationDefaultAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/AnnotationDefaultAttribute.java new file mode 100644 index 0000000000000..790b1370cdc5b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/AnnotationDefaultAttribute.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code AnnotationDefault} attribute {@jvms 4.7.22}, which can + * appear on methods of annotation types, and records the default value + * {@jls 9.6.2} for the element corresponding to this method. Delivered as a + * {@link MethodElement} when traversing the elements of a {@link MethodModel}. + */ +public sealed interface AnnotationDefaultAttribute + extends Attribute, MethodElement + permits BoundAttribute.BoundAnnotationDefaultAttr, + UnboundAttribute.UnboundAnnotationDefaultAttribute { + + /** + * {@return the default value of the annotation type element represented by + * this method} + */ + AnnotationValue defaultValue(); + + /** + * {@return an {@code AnnotationDefault} attribute} + * @param annotationDefault the default value of the annotation type element + */ + static AnnotationDefaultAttribute of(AnnotationValue annotationDefault) { + return new UnboundAttribute.UnboundAnnotationDefaultAttribute(annotationDefault); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/BootstrapMethodsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/BootstrapMethodsAttribute.java new file mode 100644 index 0000000000000..69db048af6c46 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/BootstrapMethodsAttribute.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code BootstrapMethods} attribute {@jvms 4.7.23}, which serves as + * an extension to the constant pool of a classfile. Elements of the bootstrap + * method table are accessed through {@link ConstantPool}. + */ +public sealed interface BootstrapMethodsAttribute + extends Attribute + permits BoundAttribute.BoundBootstrapMethodsAttribute, + UnboundAttribute.EmptyBootstrapAttribute { + + /** + * {@return the elements of the bootstrap method table} + */ + List bootstrapMethods(); + + /** + * {@return the size of the bootstrap methods table}. Calling this method + * does not necessarily inflate the entire table. + */ + int bootstrapMethodsSize(); + + // No factories; BMA is generated as part of constant pool +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/CharacterRangeInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/CharacterRangeInfo.java new file mode 100644 index 0000000000000..cf995ef6fea53 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/CharacterRangeInfo.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a single character range in the {@link CharacterRangeTableAttribute}. + */ +public sealed interface CharacterRangeInfo + permits UnboundAttribute.UnboundCharacterRangeInfo { + + /** + * {@return the start of the character range region (inclusive)} This is + * the index into the code array at which the code for this character range + * begins. + */ + int startPc(); + + /** + * {@return the end of the character range region (exclusive)} This is the + * index into the code array after which the code for this character range + * ends. + */ + int endPc(); + + /** + * {@return the encoded start of the character range region (inclusive)} + * The value is constructed from the line_number/column_number pair as given + * by {@code line_number << 10 + column_number}, where the source file is + * viewed as an array of (possibly multi-byte) characters. + */ + int characterRangeStart(); + + /** + * {@return the encoded end of the character range region (exclusive)}. + * The value is constructed from the line_number/column_number pair as given + * by {@code line_number << 10 + column_number}, where the source file is + * viewed as an array of (possibly multi-byte) characters. + */ + int characterRangeEnd(); + + /** + * The value of the flags item describes the kind of range. Multiple flags + * may be set within flags. + *

+ *

+ * All bits of the flags item not assigned above are reserved for future use. They should be set to zero in generated class files and should be ignored by Java virtual machine implementations. + * + * @return the flags + */ + int flags(); + + /** + * {@return a character range description} + * @param startPc the start of the bytecode range, inclusive + * @param endPc the end of the bytecode range, exclusive + * @param characterRangeStart the start of the character range, inclusive, + * encoded as {@code line_number << 10 + column_number} + * @param characterRangeEnd the end of the character range, exclusive, + * encoded as {@code line_number << 10 + column_number} + * @param flags the range flags + */ + static CharacterRangeInfo of(int startPc, + int endPc, + int characterRangeStart, + int characterRangeEnd, + int flags) { + return new UnboundAttribute.UnboundCharacterRangeInfo(startPc, endPc, + characterRangeStart, characterRangeEnd, + flags); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/CharacterRangeTableAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/CharacterRangeTableAttribute.java new file mode 100644 index 0000000000000..549a483078971 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/CharacterRangeTableAttribute.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * The CharacterRangeTable attribute is an optional variable-length attribute in + * the attributes table of a {@code Code} attribute. It may be used by debuggers + * to determine which part of the Java virtual machine code array corresponds to + * a given position in the source file or to determine what section of source + * code corresponds to a given index into the code array. The + * CharacterRangeTable attribute consists of an array of character range entries. + * Each character range entry within the table associates a range of indices in + * the code array with a range of character indices in the source file. If the + * source file is viewed as an array of characters, a character index is the + * corresponding index into this array. Note that character indices are not the + * same as byte indices as multi-byte characters may be present in the source + * file. Each character range entry includes a flag which indicates what kind of + * range is described: statement, assignment, method call, etc. Both code index + * ranges and character ranges may nest within other ranges, but they may not + * partially overlap. Thus, a given code index may correspond to several + * character range entries and in turn several character ranges, but there will + * be a smallest character range, and for each kind of range in which it is + * enclosed there will be a smallest character range. Similarly, a given + * character index may correspond to several character range entries and in turn + * several code index ranges, but there will be a smallest code index range, and + * for each kind of range in which it is enclosed there will be a smallest code + * index range. The character range entries may appear in any order. + */ +public sealed interface CharacterRangeTableAttribute + extends Attribute + permits BoundAttribute.BoundCharacterRangeTableAttribute, + UnboundAttribute.UnboundCharacterRangeTableAttribute { + + /** + * {@return the entries of the character range table} + */ + List characterRangeTable(); + + /** + * {@return a {@code CharacterRangeTable} attribute} + * @param ranges the descriptions of the character ranges + */ + static CharacterRangeTableAttribute of(List ranges) { + return new UnboundAttribute.UnboundCharacterRangeTableAttribute(ranges); + } +} + diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/CodeAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/CodeAttribute.java new file mode 100644 index 0000000000000..47f2627cd09ec --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/CodeAttribute.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.impl.BoundAttribute; + +/** + * Models the {@code Code} attribute {@jvms 4.7.3}, appears on non-native, + * non-abstract methods and contains the bytecode of the method body. Delivered + * as a {@link jdk.internal.classfile.MethodElement} when traversing the elements of a + * {@link jdk.internal.classfile.MethodModel}. + */ +public sealed interface CodeAttribute extends Attribute, CodeModel + permits BoundAttribute.BoundCodeAttribute { + + /** + * {@return The length of the code array in bytes} + */ + int codeLength(); + + /** + * {@return the bytes (bytecode) of the code array} + */ + byte[] codeArray(); + + /** + * {@return the position of the {@code Label} in the {@code codeArray} + * or -1 if the {@code Label} does not point to the {@code codeArray}} + * @param label a marker for a position within this {@code CodeAttribute} + */ + int labelToBci(Label label); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/CompilationIDAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/CompilationIDAttribute.java new file mode 100644 index 0000000000000..0e536f374fe66 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/CompilationIDAttribute.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code CompilationID} attribute (@@@ need reference), which can + * appear on classes and records the compilation time of the class. Delivered + * as a {@link jdk.internal.classfile.ClassElement} when traversing the elements of + * a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface CompilationIDAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundCompilationIDAttribute, + UnboundAttribute.UnboundCompilationIDAttribute { + + /** + * {@return the compilation ID} The compilation ID is the value of + * {@link System#currentTimeMillis()} when the classfile is generated. + */ + Utf8Entry compilationId(); + + /** + * {@return a {@code CompilationID} attribute} + * @param id the compilation ID + */ + static CompilationIDAttribute of(Utf8Entry id) { + return new UnboundAttribute.UnboundCompilationIDAttribute(id); + } + + /** + * {@return a {@code CompilationID} attribute} + * @param id the compilation ID + */ + static CompilationIDAttribute of(String id) { + return new UnboundAttribute.UnboundCompilationIDAttribute(TemporaryConstantPool.INSTANCE.utf8Entry(id)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ConstantValueAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ConstantValueAttribute.java new file mode 100644 index 0000000000000..3fcdb624496ea --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ConstantValueAttribute.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ConstantDesc; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.constantpool.ConstantValueEntry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code ConstantValue} attribute {@jvms 4.7.2}, which can appear on + * fields and indicates that the field's value is a constant. Delivered as a + * {@link jdk.internal.classfile.FieldElement} when traversing the elements of a + * {@link jdk.internal.classfile.FieldModel}. + */ +public sealed interface ConstantValueAttribute + extends Attribute, FieldElement + permits BoundAttribute.BoundConstantValueAttribute, + UnboundAttribute.UnboundConstantValueAttribute { + + /** + * {@return the constant value of the field} + */ + ConstantValueEntry constant(); + + /** + * {@return a {@code ConstantValue} attribute} + * @param value the constant value + */ + static ConstantValueAttribute of(ConstantValueEntry value) { + return new UnboundAttribute.UnboundConstantValueAttribute(value); + } + + /** + * {@return a {@code ConstantValue} attribute} + * @param value the constant value + */ + static ConstantValueAttribute of(ConstantDesc value) { + return of(switch(value) { + case Integer i -> TemporaryConstantPool.INSTANCE.intEntry(i); + case Float f -> TemporaryConstantPool.INSTANCE.floatEntry(f); + case Long l -> TemporaryConstantPool.INSTANCE.longEntry(l); + case Double d -> TemporaryConstantPool.INSTANCE.doubleEntry(d); + case String s -> TemporaryConstantPool.INSTANCE.stringEntry(s); + default -> throw new IllegalArgumentException("Invalid ConstantValueAtrtibute value: " + value); + }); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/DeprecatedAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/DeprecatedAttribute.java new file mode 100644 index 0000000000000..0c4272599e820 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/DeprecatedAttribute.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code Deprecated} attribute {@jvms 4.7.15}, which can appear on + * classes, methods, and fields. Delivered as a {@link ClassElement}, + * {@link MethodElement}, or {@link FieldElement} when traversing the elements + * of a corresponding model. + */ +public sealed interface DeprecatedAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement + permits BoundAttribute.BoundDeprecatedAttribute, + UnboundAttribute.UnboundDeprecatedAttribute { + + /** + * {@return a {@code Deprecated} attribute} + */ + static DeprecatedAttribute of() { + return new UnboundAttribute.UnboundDeprecatedAttribute(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/EnclosingMethodAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/EnclosingMethodAttribute.java new file mode 100644 index 0000000000000..fc4c33d1a356b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/EnclosingMethodAttribute.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.Optional; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code EnclosingMethod} attribute {@jvms 4.7.7}, which can appear + * on classes, and indicates that the class is a local or anonymous class. + * Delivered as a {@link ClassElement} when traversing the elements of a {@link + * jdk.internal.classfile.ClassModel}. + */ +public sealed interface EnclosingMethodAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundEnclosingMethodAttribute, + UnboundAttribute.UnboundEnclosingMethodAttribute { + + /** + * {@return the innermost class that encloses the declaration of the current + * class} + */ + ClassEntry enclosingClass(); + + /** + * {@return the name and type of the enclosing method, if the class is + * immediately enclosed by a method or constructor} + */ + Optional enclosingMethod(); + + /** + * {@return the name of the enclosing method, if the class is + * immediately enclosed by a method or constructor} + */ + default Optional enclosingMethodName() { + return enclosingMethod().map(NameAndTypeEntry::name); + } + + /** + * {@return the type of the enclosing method, if the class is + * immediately enclosed by a method or constructor} + */ + default Optional enclosingMethodType() { + return enclosingMethod().map(NameAndTypeEntry::type); + } + + /** + * {@return the type of the enclosing method, if the class is + * immediately enclosed by a method or constructor} + */ + default Optional enclosingMethodTypeSymbol() { + return enclosingMethodType().map(n -> MethodTypeDesc.ofDescriptor(n.stringValue())); + } + + /** + * {@return an {@code EnclosingMethod} attribute} + * @param className the class name + * @param method the name and type of the enclosing method + */ + static EnclosingMethodAttribute of(ClassEntry className, + Optional method) { + return new UnboundAttribute.UnboundEnclosingMethodAttribute(className, method.orElse(null)); + } + + /** + * {@return an {@code EnclosingMethod} attribute} + * @param className the class name + * @param methodName the name of the enclosing method + * @param methodType the type of the enclosing method + */ + static EnclosingMethodAttribute of(ClassDesc className, + Optional methodName, + Optional methodType) { + return new UnboundAttribute.UnboundEnclosingMethodAttribute( + TemporaryConstantPool.INSTANCE.classEntry(className), + methodName.isPresent() && methodType.isPresent() + ? TemporaryConstantPool.INSTANCE.nameAndTypeEntry(methodName.get(), methodType.get()) + : null); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ExceptionsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ExceptionsAttribute.java new file mode 100644 index 0000000000000..bcf9f41c5edfc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ExceptionsAttribute.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models the {@code Exceptions} attribute {@jvms 4.7.5}, which can appear on + * methods, and records the exceptions declared to be thrown by this method. + * Delivered as a {@link MethodElement} when traversing the elements of a + * {@link jdk.internal.classfile.MethodModel}. + */ +public sealed interface ExceptionsAttribute + extends Attribute, MethodElement + permits BoundAttribute.BoundExceptionsAttribute, + UnboundAttribute.UnboundExceptionsAttribute { + + /** + * {@return the exceptions declared to be thrown by this method} + */ + List exceptions(); + + /** + * {@return an {@code Exceptions} attribute} + * @param exceptions the checked exceptions that may be thrown from this method + */ + static ExceptionsAttribute of(List exceptions) { + return new UnboundAttribute.UnboundExceptionsAttribute(exceptions); + } + + /** + * {@return an {@code Exceptions} attribute} + * @param exceptions the checked exceptions that may be thrown from this method + */ + static ExceptionsAttribute of(ClassEntry... exceptions) { + return of(List.of(exceptions)); + } + + /** + * {@return an {@code Exceptions} attribute} + * @param exceptions the checked exceptions that may be thrown from this method + */ + static ExceptionsAttribute ofSymbols(List exceptions) { + return of(Util.entryList(exceptions)); + } + + /** + * {@return an {@code Exceptions} attribute} + * @param exceptions the checked exceptions that may be thrown from this method + */ + static ExceptionsAttribute ofSymbols(ClassDesc... exceptions) { + return ofSymbols(Arrays.asList(exceptions)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/InnerClassInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/InnerClassInfo.java new file mode 100644 index 0000000000000..0e2e464380e71 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/InnerClassInfo.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Optional; +import java.util.Set; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import java.lang.reflect.AccessFlag; + +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models a single inner class in the {@link InnerClassesAttribute}. + */ +public sealed interface InnerClassInfo + permits UnboundAttribute.UnboundInnerClassInfo { + + /** + * {@return the class described by this inner class description} + */ + ClassEntry innerClass(); + + /** + * {@return the class or interface of which this class is a member, if it is a + * member of a class or interface} + */ + Optional outerClass(); + + /** + * {@return the name of the class or interface of which this class is a + * member, if it is a member of a class or interface} + */ + Optional innerName(); + + /** + * {@return a bit mask of flags denoting access permissions and properties + * of the inner class} + */ + int flagsMask(); + + /** + * {@return a set of flag enums denoting access permissions and properties + * of the inner class} + */ + default Set flags() { + return AccessFlag.maskToAccessFlags(flagsMask(), AccessFlag.Location.INNER_CLASS); + } + + /** + * {@return whether a specific access flag is set} + * @param flag the access flag + */ + default boolean has(AccessFlag flag) { + return Util.has(AccessFlag.Location.INNER_CLASS, flagsMask(), flag); + } + + /** + * {@return an inner class description} + * @param innerClass the inner class being described + * @param outerClass the class containing the inner class, if any + * @param innerName the name of the inner class, if it is not anonymous + * @param flags the inner class access flags + */ + static InnerClassInfo of(ClassEntry innerClass, Optional outerClass, + Optional innerName, int flags) { + return new UnboundAttribute.UnboundInnerClassInfo(innerClass, outerClass, innerName, flags); + } + + /** + * {@return an inner class description} + * @param innerClass the inner class being described + * @param outerClass the class containing the inner class, if any + * @param innerName the name of the inner class, if it is not anonymous + * @param flags the inner class access flags + */ + static InnerClassInfo of(ClassDesc innerClass, Optional outerClass, Optional innerName, int flags) { + return new UnboundAttribute.UnboundInnerClassInfo(TemporaryConstantPool.INSTANCE.classEntry(innerClass), + outerClass.map(TemporaryConstantPool.INSTANCE::classEntry), + innerName.map(TemporaryConstantPool.INSTANCE::utf8Entry), + flags); + } + + /** + * {@return an inner class description} + * @param innerClass the inner class being described + * @param outerClass the class containing the inner class, if any + * @param innerName the name of the inner class, if it is not anonymous + * @param flags the inner class access flags + */ + static InnerClassInfo of(ClassDesc innerClass, Optional outerClass, Optional innerName, AccessFlag... flags) { + return of(innerClass, outerClass, innerName, Util.flagsToBits(AccessFlag.Location.INNER_CLASS, flags)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/InnerClassesAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/InnerClassesAttribute.java new file mode 100644 index 0000000000000..89c55ecc4569c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/InnerClassesAttribute.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code InnerClasses} attribute {@jvms 4.7.6}, which can + * appear on classes, and records which classes referenced by this classfile + * are inner classes. Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface InnerClassesAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundInnerClassesAttribute, + UnboundAttribute.UnboundInnerClassesAttribute { + + /** + * {@return the inner classes used by this class} + */ + List classes(); + + /** + * {@return an {@code InnerClasses} attribute} + * @param innerClasses descriptions of the inner classes + */ + static InnerClassesAttribute of(List innerClasses) { + return new UnboundAttribute.UnboundInnerClassesAttribute(innerClasses); + } + + /** + * {@return an {@code InnerClasses} attribute} + * @param innerClasses descriptions of the inner classes + */ + static InnerClassesAttribute of(InnerClassInfo... innerClasses) { + return new UnboundAttribute.UnboundInnerClassesAttribute(List.of(innerClasses)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/LineNumberInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/LineNumberInfo.java new file mode 100644 index 0000000000000..88c6b0afc1391 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/LineNumberInfo.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a single line number in the {@link LineNumberTableAttribute}. + */ +public sealed interface LineNumberInfo + permits UnboundAttribute.UnboundLineNumberInfo { + + /** + * {@return the index into the code array at which the code for this line + * begins} + */ + int startPc(); + + /** + * {@return the line number within the original source file} + */ + int lineNumber(); + + /** + * {@return a line number description} + * @param startPc the starting index of the code array for this line + * @param lineNumber the line number within the original source file + */ + public static LineNumberInfo of(int startPc, int lineNumber) { + return new UnboundAttribute.UnboundLineNumberInfo(startPc, lineNumber); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/LineNumberTableAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/LineNumberTableAttribute.java new file mode 100644 index 0000000000000..d66be5a61548c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/LineNumberTableAttribute.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code LineNumberTable} attribute {@jvms 4.7.12}, which can appear + * on a {@code Code} attribute, and records the mapping between indexes into + * the code table and line numbers in the source file. + * Delivered as a {@link jdk.internal.classfile.instruction.LineNumber} when traversing the + * elements of a {@link jdk.internal.classfile.CodeModel}, according to the setting of the + * {@link jdk.internal.classfile.Classfile.Option#processLineNumbers(boolean)} option. + */ +public sealed interface LineNumberTableAttribute + extends Attribute + permits BoundAttribute.BoundLineNumberTableAttribute, + UnboundAttribute.UnboundLineNumberTableAttribute { + + /** + * {@return the table mapping bytecode offsets to source line numbers} + */ + List lineNumbers(); + + /** + * {@return a {@code LineNumberTable} attribute} + * @param lines the line number descriptions + */ + static LineNumberTableAttribute of(List lines) { + return new UnboundAttribute.UnboundLineNumberTableAttribute(lines); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableInfo.java new file mode 100644 index 0000000000000..bcef293efa45c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableInfo.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundLocalVariable; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a single local variable in the {@link LocalVariableTableAttribute}. + */ +public sealed interface LocalVariableInfo + permits UnboundAttribute.UnboundLocalVariableInfo, BoundLocalVariable { + + /** + * {@return the index into the code array (inclusive) at which the scope of + * this variable begins} + */ + int startPc(); + + /** + * {@return the length of the region of the code array in which this + * variable is in scope.} + */ + int length(); + + /** + * {@return the name of the local variable} + */ + Utf8Entry name(); + + /** + * {@return the field descriptor of the local variable} + */ + Utf8Entry type(); + + /** + * {@return the field descriptor of the local variable} + */ + default ClassDesc typeSymbol() { + return ClassDesc.ofDescriptor(type().stringValue()); + } + + /** + * {@return the index into the local variable array of the current frame + * which holds this local variable} + */ + int slot(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTableAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTableAttribute.java new file mode 100644 index 0000000000000..76fae7e84769a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTableAttribute.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +import java.util.List; + +/** + * Models the {@code LocalVariableTable} attribute {@jvms 4.7.13}, which can appear + * on a {@code Code} attribute, and records debug information about local + * variables. + * Delivered as a {@link jdk.internal.classfile.instruction.LocalVariable} when traversing the + * elements of a {@link jdk.internal.classfile.CodeModel}, according to the setting of the + * {@link jdk.internal.classfile.Classfile.Option#processDebug(boolean)} option. + */ +public sealed interface LocalVariableTableAttribute + extends Attribute + permits BoundAttribute.BoundLocalVariableTableAttribute, UnboundAttribute.UnboundLocalVariableTableAttribute { + + /** + * {@return debug information for the local variables in this method} + */ + List localVariables(); + + /** + * {@return a {@code LocalVariableTable} attribute} + * @param locals the local variable descriptions + */ + static LocalVariableTableAttribute of(List locals) { + return new UnboundAttribute.UnboundLocalVariableTableAttribute(locals); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTypeInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTypeInfo.java new file mode 100644 index 0000000000000..6fcf0209b8e23 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTypeInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundLocalVariableType; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a single local variable in the {@link LocalVariableTypeTableAttribute}. + */ +public sealed interface LocalVariableTypeInfo + permits UnboundAttribute.UnboundLocalVariableTypeInfo, BoundLocalVariableType { + + /** + * {@return the index into the code array (inclusive) at which the scope of + * this variable begins} + */ + int startPc(); + + /** + * {@return the length of the region of the code array in which this + * variable is in scope.} + */ + int length(); + + /** + * {@return the name of the local variable} + */ + Utf8Entry name(); + + + /** + * {@return the field signature of the local variable} + */ + Utf8Entry signature(); + + /** + * {@return the index into the local variable array of the current frame + * which holds this local variable} + */ + int slot(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTypeTableAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTypeTableAttribute.java new file mode 100644 index 0000000000000..481d02829e50d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/LocalVariableTypeTableAttribute.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +import java.util.List; + +/** + * Models the {@code LocalVariableTypeTable} attribute {@jvms 4.7.14}, which can appear + * on a {@code Code} attribute, and records debug information about local + * variables. + * Delivered as a {@link jdk.internal.classfile.instruction.LocalVariable} when traversing the + * elements of a {@link jdk.internal.classfile.CodeModel}, according to the setting of the + * {@link jdk.internal.classfile.Classfile.Option#processLineNumbers(boolean)} option. + */ +public sealed interface LocalVariableTypeTableAttribute + extends Attribute + permits BoundAttribute.BoundLocalVariableTypeTableAttribute, UnboundAttribute.UnboundLocalVariableTypeTableAttribute { + + /** + * {@return debug information for the local variables in this method} + */ + List localVariableTypes(); + + /** + * {@return a {@code LocalVariableTypeTable} attribute} + * @param locals the local variable descriptions + */ + static LocalVariableTypeTableAttribute of(List locals) { + return new UnboundAttribute.UnboundLocalVariableTypeTableAttribute(locals); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/MethodParameterInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/MethodParameterInfo.java new file mode 100644 index 0000000000000..bc0eb2047a789 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/MethodParameterInfo.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.util.Optional; +import java.util.Set; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models a single method parameter in the {@link MethodParametersAttribute}. + */ +public sealed interface MethodParameterInfo + permits UnboundAttribute.UnboundMethodParameterInfo { + /** + * The name of the method parameter, if there is one. + * + * @return the parameter name, if it has one + */ + Optional name(); + + /** + * Parameter access flags for this parameter, as a bit mask. Valid + * parameter flags include {@link Classfile#ACC_FINAL}, + * {@link Classfile#ACC_SYNTHETIC}, and {@link Classfile#ACC_MANDATED}. + * + * @return the access flags, as a bit mask + */ + int flagsMask(); + + /** + * Parameter access flags for this parameter. + * + * @return the access flags, as a bit mask + */ + default Set flags() { + return AccessFlag.maskToAccessFlags(flagsMask(), AccessFlag.Location.METHOD_PARAMETER); + } + + /** + * {@return whether the method parameter has a specific flag set} + * @param flag the method parameter flag + */ + default boolean has(AccessFlag flag) { + return Util.has(AccessFlag.Location.METHOD_PARAMETER, flagsMask(), flag); + } + + /** + * {@return a method parameter description} + * @param name the method parameter name + * @param flags the method parameter access flags + */ + static MethodParameterInfo of(Optional name, int flags) { + return new UnboundAttribute.UnboundMethodParameterInfo(name, flags); + } + + /** + * {@return a method parameter description} + * @param name the method parameter name + * @param flags the method parameter access flags + */ + static MethodParameterInfo of(Optional name, AccessFlag... flags) { + return of(name.map(TemporaryConstantPool.INSTANCE::utf8Entry), Util.flagsToBits(AccessFlag.Location.METHOD_PARAMETER, flags)); + } + + /** + * {@return a method parameter description} + * @param name the method parameter name + * @param flags the method parameter access flags + */ + static MethodParameterInfo ofParameter(Optional name, int flags) { + return of(name.map(TemporaryConstantPool.INSTANCE::utf8Entry), flags); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/MethodParametersAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/MethodParametersAttribute.java new file mode 100644 index 0000000000000..6b00c655323eb --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/MethodParametersAttribute.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code MethodParameters} attribute {@jvms 4.7.24}, which can + * appear on methods, and records optional information about the method's + * parameters. Delivered as a {@link jdk.internal.classfile.MethodElement} when + * traversing the elements of a {@link jdk.internal.classfile.MethodModel}. + */ +public sealed interface MethodParametersAttribute + extends Attribute, MethodElement + permits BoundAttribute.BoundMethodParametersAttribute, + UnboundAttribute.UnboundMethodParametersAttribute { + + /** + * {@return information about the parameters of the method} The i'th entry + * in the list correponds to the i'th parameter in the method declaration. + */ + List parameters(); + + /** + * {@return a {@code MethodParameters} attribute} + * @param parameters the method parameter descriptions + */ + static MethodParametersAttribute of(List parameters) { + return new UnboundAttribute.UnboundMethodParametersAttribute(parameters); + } + + /** + * {@return a {@code MethodParameters} attribute} + * @param parameters the method parameter descriptions + */ + static MethodParametersAttribute of(MethodParameterInfo... parameters) { + return of(List.of(parameters)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleAttribute.java new file mode 100644 index 0000000000000..6511134b12586 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleAttribute.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Collection; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import jdk.internal.classfile.impl.ModuleAttributeBuilderImpl; +import jdk.internal.classfile.impl.Util; + +/** + * Models the {@code Module} attribute {@jvms 4.7.25}, which can + * appear on classes that represent module descriptors. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ + +public sealed interface ModuleAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundModuleAttribute, UnboundAttribute.UnboundModuleAttribute { + + /** + * {@return the name of the module} + */ + ModuleEntry moduleName(); + + /** + * {@return the the module flags of the module, as a bit mask} + */ + int moduleFlagsMask(); + + /** + * {@return the the module flags of the module, as a set of enum constants} + */ + default Set moduleFlags() { + return AccessFlag.maskToAccessFlags(moduleFlagsMask(), AccessFlag.Location.MODULE); + } + + default boolean has(AccessFlag flag) { + return Util.has(AccessFlag.Location.MODULE, moduleFlagsMask(), flag); + } + + /** + * {@return version of the module, if present} + */ + Optional moduleVersion(); + + /** + * {@return the modules required by this module} + */ + List requires(); + + /** + * {@return the packages exported by this module} + */ + List exports(); + + /** + * {@return the packages opened by this module} + */ + List opens(); + + /** + * {@return the services used by this module} Services may be discovered via + * {@link java.util.ServiceLoader}. + */ + List uses(); + + /** + * {@return the service implementations provided by this module} + */ + List provides(); + + /** + * {@return a {@code Module} attribute} + * + * @param moduleName the module name + * @param moduleFlags the module flags + * @param moduleVersion the module version + * @param requires the required packages + * @param exports the exported packages + * @param opens the opened packages + * @param uses the consumed services + * @param provides the provided services + */ + static ModuleAttribute of(ModuleEntry moduleName, int moduleFlags, + Utf8Entry moduleVersion, + Collection requires, + Collection exports, + Collection opens, + Collection uses, + Collection provides) { + return new UnboundAttribute.UnboundModuleAttribute(moduleName, moduleFlags, moduleVersion, requires, exports, opens, uses, provides); + } + + /** + * {@return a {@code Module} attribute} + * + * @param moduleName the module name + * @param attrHandler a handler that receives a {@link ModuleAttributeBuilder} + */ + static ModuleAttribute of(ModuleDesc moduleName, + Consumer attrHandler) { + var mb = new ModuleAttributeBuilderImpl(moduleName); + attrHandler.accept(mb); + return mb.build(); + } + + public sealed interface ModuleAttributeBuilder + permits ModuleAttributeBuilderImpl { + + ModuleAttributeBuilder moduleName(ModuleDesc moduleName); + ModuleAttributeBuilder moduleFlags(int flagsMask); + default ModuleAttributeBuilder moduleFlags(AccessFlag... moduleFlags) { + return moduleFlags(Util.flagsToBits(AccessFlag.Location.MODULE, moduleFlags)); + } + ModuleAttributeBuilder moduleVersion(String version); + + ModuleAttributeBuilder requires(ModuleDesc module, int requiresFlagsMask, String version); + default ModuleAttributeBuilder requires(ModuleDesc module, Collection requiresFlags, String version) { + return requires(module, Util.flagsToBits(AccessFlag.Location.MODULE_REQUIRES, requiresFlags), version); + } + ModuleAttributeBuilder requires(ModuleRequireInfo requires); + + ModuleAttributeBuilder exports(PackageDesc pkge, int exportsFlagsMask, ModuleDesc... exportsToModules); + default ModuleAttributeBuilder exports(PackageDesc pkge, Collection exportsFlags, ModuleDesc... exportsToModules) { + return exports(pkge, Util.flagsToBits(AccessFlag.Location.MODULE_EXPORTS, exportsFlags), exportsToModules); + } + ModuleAttributeBuilder exports(ModuleExportInfo exports); + + ModuleAttributeBuilder opens(PackageDesc pkge, int opensFlagsMask, ModuleDesc... opensToModules); + default ModuleAttributeBuilder opens(PackageDesc pkge, Collection opensFlags, ModuleDesc... opensToModules) { + return opens(pkge, Util.flagsToBits(AccessFlag.Location.MODULE_OPENS, opensFlags), opensToModules); + } + ModuleAttributeBuilder opens(ModuleOpenInfo opens); + + ModuleAttributeBuilder uses(ClassDesc service); + ModuleAttributeBuilder uses(ClassEntry uses); + + ModuleAttributeBuilder provides(ClassDesc service, ClassDesc... implClasses); + ModuleAttributeBuilder provides(ModuleProvideInfo provides); + + ModuleAttribute build(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleExportInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleExportInfo.java new file mode 100644 index 0000000000000..b85f0cdac34ea --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleExportInfo.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import java.lang.reflect.AccessFlag; + +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models a single "exports" declaration in the {@link jdk.internal.classfile.attribute.ModuleAttribute}. + */ +public sealed interface ModuleExportInfo + permits UnboundAttribute.UnboundModuleExportInfo { + + /** + * {@return the exported package} + */ + PackageEntry exportedPackage(); + + /** + * {@return the flags associated with this export declaration, as a bit mask} + * Valid flags include {@link Classfile#ACC_SYNTHETIC} and + * {@link Classfile#ACC_MANDATED}. + */ + int exportsFlagsMask(); + + /** + * {@return the flags associated with this export declaration, as a set of + * flag values} + */ + default Set exportsFlags() { + return AccessFlag.maskToAccessFlags(exportsFlagsMask(), AccessFlag.Location.MODULE_EXPORTS); + } + + /** + * {@return the list of modules to which this package is exported, if it is a + * qualified export} + */ + List exportsTo(); + + /** + * {@return whether the module has the specified access flag set} + * @param flag the access flag + */ + default boolean has(AccessFlag flag) { + return Util.has(AccessFlag.Location.MODULE_EXPORTS, exportsFlagsMask(), flag); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags, as a bitmask + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageEntry exports, int exportFlags, + List exportsTo) { + return new UnboundAttribute.UnboundModuleExportInfo(exports, exportFlags, exportsTo); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageEntry exports, Collection exportFlags, + List exportsTo) { + return of(exports, Util.flagsToBits(AccessFlag.Location.MODULE_EXPORTS, exportFlags), exportsTo); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags, as a bitmask + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageEntry exports, + int exportFlags, + ModuleEntry... exportsTo) { + return of(exports, exportFlags, List.of(exportsTo)); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageEntry exports, + Collection exportFlags, + ModuleEntry... exportsTo) { + return of(exports, Util.flagsToBits(AccessFlag.Location.MODULE_EXPORTS, exportFlags), exportsTo); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags, as a bitmask + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageDesc exports, int exportFlags, + List exportsTo) { + return of(TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(exports.packageInternalName())), + exportFlags, + Util.moduleEntryList(exportsTo)); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageDesc exports, Collection exportFlags, + List exportsTo) { + return of(exports, Util.flagsToBits(AccessFlag.Location.MODULE_EXPORTS, exportFlags), exportsTo); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags, as a bitmask + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageDesc exports, + int exportFlags, + ModuleDesc... exportsTo) { + return of(exports, exportFlags, List.of(exportsTo)); + } + + /** + * {@return a module export description} + * @param exports the exported package + * @param exportFlags the export flags + * @param exportsTo the modules to which this package is exported + */ + static ModuleExportInfo of(PackageDesc exports, + Collection exportFlags, + ModuleDesc... exportsTo) { + return of(exports, Util.flagsToBits(AccessFlag.Location.MODULE_EXPORTS, exportFlags), exportsTo); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleHashInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleHashInfo.java new file mode 100644 index 0000000000000..a2e73c065e120 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleHashInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models hash information for a single module in the {@link jdk.internal.classfile.attribute.ModuleHashesAttribute}. + */ +public sealed interface ModuleHashInfo + permits UnboundAttribute.UnboundModuleHashInfo { + + /** + * {@return the name of the related module} + */ + ModuleEntry moduleName(); + + /** + * {@return the hash of the related module} + */ + byte[] hash(); + + /** + * {@return a module hash description} + * @param moduleName the module name + * @param hash the hash value + */ + static ModuleHashInfo of(ModuleEntry moduleName, byte[] hash) { + return new UnboundAttribute.UnboundModuleHashInfo(moduleName, hash); + } + + /** + * {@return a module hash description} + * @param moduleDesc the module name + * @param hash the hash value + */ + static ModuleHashInfo of(ModuleDesc moduleDesc, byte[] hash) { + return new UnboundAttribute.UnboundModuleHashInfo(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(moduleDesc.moduleName())), hash); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleHashesAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleHashesAttribute.java new file mode 100644 index 0000000000000..5447ea0e04305 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleHashesAttribute.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; + +import java.util.List; + +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code ModuleHashes} attribute, which can + * appear on classes that represent module descriptors. This is a JDK-specific + * attribute, which captures the hashes of a set of co-delivered modules. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + * + *

The specification of the {@code ModuleHashes} attribute is: + *

 {@code
+ *
+ * ModuleHashes_attribute {
+ *   // index to CONSTANT_utf8_info structure in constant pool representing
+ *   // the string "ModuleHashes"
+ *   u2 attribute_name_index;
+ *   u4 attribute_length;
+ *
+ *   // index to CONSTANT_utf8_info structure with algorithm name
+ *   u2 algorithm_index;
+ *
+ *   // the number of entries in the hashes table
+ *   u2 hashes_count;
+ *   {   u2 module_name_index (index to CONSTANT_Module_info structure)
+ *       u2 hash_length;
+ *       u1 hash[hash_length];
+ *   } hashes[hashes_count];
+ *
+ * }
+ * } 
+ */ +public sealed interface ModuleHashesAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundModuleHashesAttribute, UnboundAttribute.UnboundModuleHashesAttribute { + + /** + * {@return the algorithm name used to compute the hash} + */ + Utf8Entry algorithm(); + + /** + * {@return the hash information about related modules} + */ + List hashes(); + + /** + * {@return a {@code ModuleHashes} attribute} + * @param algorithm the hashing algorithm + * @param hashes the hash descriptions + */ + static ModuleHashesAttribute of(String algorithm, + List hashes) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(algorithm), hashes); + } + + /** + * {@return a {@code ModuleHashes} attribute} + * @param algorithm the hashing algorithm + * @param hashes the hash descriptions + */ + static ModuleHashesAttribute of(String algorithm, + ModuleHashInfo... hashes) { + return of(algorithm, List.of(hashes)); + } + + /** + * {@return a {@code ModuleHashes} attribute} + * @param algorithm the hashing algorithm + * @param hashes the hash descriptions + */ + static ModuleHashesAttribute of(Utf8Entry algorithm, + List hashes) { + return new UnboundAttribute.UnboundModuleHashesAttribute(algorithm, hashes); + } + + /** + * {@return a {@code ModuleHashes} attribute} + * @param algorithm the hashing algorithm + * @param hashes the hash descriptions + */ + static ModuleHashesAttribute of(Utf8Entry algorithm, + ModuleHashInfo... hashes) { + return of(algorithm, List.of(hashes)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleMainClassAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleMainClassAttribute.java new file mode 100644 index 0000000000000..a21e0f4888be7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleMainClassAttribute.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code ModuleMainClass} attribute {@jvms 4.7.27}, which can + * appear on classes that represent module descriptors. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface ModuleMainClassAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundModuleMainClassAttribute, UnboundAttribute.UnboundModuleMainClassAttribute { + + /** + * {@return main class for this module} + */ + ClassEntry mainClass(); + + /** + * {@return a {@code ModuleMainClass} attribute} + * @param mainClass the main class + */ + static ModuleMainClassAttribute of(ClassEntry mainClass) { + return new UnboundAttribute.UnboundModuleMainClassAttribute(mainClass); + } + + /** + * {@return a {@code ModuleMainClass} attribute} + * @param mainClass the main class + */ + static ModuleMainClassAttribute of(ClassDesc mainClass) { + return new UnboundAttribute.UnboundModuleMainClassAttribute(TemporaryConstantPool.INSTANCE.classEntry(mainClass)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleOpenInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleOpenInfo.java new file mode 100644 index 0000000000000..df14646f95c6e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleOpenInfo.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import java.lang.reflect.AccessFlag; + +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models a single "opens" declaration in the {@link jdk.internal.classfile.attribute.ModuleAttribute}. + */ +public sealed interface ModuleOpenInfo + permits UnboundAttribute.UnboundModuleOpenInfo { + + /** + * {@return the package being opened} + */ + PackageEntry openedPackage(); + + /** + * {@return the flags associated with this open declaration, as a bit mask} + * Valid flags include {@link jdk.internal.classfile.Classfile#ACC_SYNTHETIC} and + * {@link jdk.internal.classfile.Classfile#ACC_MANDATED} + */ + int opensFlagsMask(); + + default Set opensFlags() { + return AccessFlag.maskToAccessFlags(opensFlagsMask(), AccessFlag.Location.MODULE_OPENS); + } + + /** + * {@return whether the specified access flag is set} + * @param flag the access flag + */ + default boolean has(AccessFlag flag) { + return Util.has(AccessFlag.Location.MODULE_OPENS, opensFlagsMask(), flag); + } + + /** + * The list of modules to which this package is opened, if it is a + * qualified open. + * + * @return the modules to which this package is opened + */ + List opensTo(); + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageEntry opens, int opensFlags, + List opensTo) { + return new UnboundAttribute.UnboundModuleOpenInfo(opens, opensFlags, opensTo); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageEntry opens, Collection opensFlags, + List opensTo) { + return of(opens, Util.flagsToBits(AccessFlag.Location.MODULE_OPENS, opensFlags), opensTo); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageEntry opens, + int opensFlags, + ModuleEntry... opensTo) { + return of(opens, opensFlags, List.of(opensTo)); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageEntry opens, + Collection opensFlags, + ModuleEntry... opensTo) { + return of(opens, Util.flagsToBits(AccessFlag.Location.MODULE_OPENS, opensFlags), opensTo); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageDesc opens, int opensFlags, + List opensTo) { + return of(TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(opens.packageInternalName())), + opensFlags, + Util.moduleEntryList(opensTo)); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageDesc opens, Collection opensFlags, + List opensTo) { + return of(opens, Util.flagsToBits(AccessFlag.Location.MODULE_OPENS, opensFlags), opensTo); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageDesc opens, + int opensFlags, + ModuleDesc... opensTo) { + return of(opens, opensFlags, List.of(opensTo)); + } + + /** + * {@return a module open description} + * @param opens the package to open + * @param opensFlags the open flags + * @param opensTo the packages to which this package is opened, if it is a qualified open + */ + static ModuleOpenInfo of(PackageDesc opens, + Collection opensFlags, + ModuleDesc... opensTo) { + return of(opens, Util.flagsToBits(AccessFlag.Location.MODULE_OPENS, opensFlags), opensTo); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModulePackagesAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModulePackagesAttribute.java new file mode 100644 index 0000000000000..464e2a6a532fc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModulePackagesAttribute.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.impl.BoundAttribute; + +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code ModulePackages} attribute {@jvms 4.7.26}, which can + * appear on classes that represent module descriptors. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface ModulePackagesAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundModulePackagesAttribute, + UnboundAttribute.UnboundModulePackagesAttribute { + + /** + * {@return the packages that are opened or exported by this module} + */ + List packages(); + + /** + * {@return a {@code ModulePackages} attribute} + * @param packages the packages + */ + static ModulePackagesAttribute of(List packages) { + return new UnboundAttribute.UnboundModulePackagesAttribute(packages); + } + + /** + * {@return a {@code ModulePackages} attribute} + * @param packages the packages + */ + static ModulePackagesAttribute of(PackageEntry... packages) { + return of(List.of(packages)); + } + + /** + * {@return a {@code ModulePackages} attribute} + * @param packages the packages + */ + static ModulePackagesAttribute ofNames(List packages) { + var p = new PackageEntry[packages.size()]; + for (int i = 0; i < packages.size(); i++) { + p[i] = TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(packages.get(i).packageInternalName())); + } + return of(p); + } + + /** + * {@return a {@code ModulePackages} attribute} + * @param packages the packages + */ + static ModulePackagesAttribute ofNames(PackageDesc... packages) { + // List view, since ref to packages is temporary + return ofNames(Arrays.asList(packages)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleProvideInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleProvideInfo.java new file mode 100644 index 0000000000000..22906dfddabaa --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleProvideInfo.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models a single "provides" declaration in the {@link jdk.internal.classfile.attribute.ModuleAttribute}. + */ +public sealed interface ModuleProvideInfo + permits UnboundAttribute.UnboundModuleProvideInfo { + + /** + * {@return the service interface representing the provided service} + */ + ClassEntry provides(); + + /** + * {@return the classes providing the service implementation} + */ + List providesWith(); + + /** + * {@return a service provision description} + * @param provides the service class interface + * @param providesWith the service class implementations + */ + static ModuleProvideInfo of(ClassEntry provides, + List providesWith) { + return new UnboundAttribute.UnboundModuleProvideInfo(provides, providesWith); + } + + /** + * {@return a service provision description} + * @param provides the service class interface + * @param providesWith the service class implementations + */ + static ModuleProvideInfo of(ClassEntry provides, + ClassEntry... providesWith) { + return of(provides, List.of(providesWith)); + } + + /** + * {@return a service provision description} + * @param provides the service class interface + * @param providesWith the service class implementations + */ + static ModuleProvideInfo of(ClassDesc provides, + List providesWith) { + return of(TemporaryConstantPool.INSTANCE.classEntry(provides), Util.entryList(providesWith)); + } + + /** + * {@return a service provision description} + * @param provides the service class interface + * @param providesWith the service class implementations + */ + static ModuleProvideInfo of(ClassDesc provides, + ClassDesc... providesWith) { + // List view, since ref to providesWith is temporary + return of(provides, Arrays.asList(providesWith)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleRequireInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleRequireInfo.java new file mode 100644 index 0000000000000..e5b720a2df912 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleRequireInfo.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models a single "requires" declaration in the {@link jdk.internal.classfile.attribute.ModuleAttribute}. + */ +public sealed interface ModuleRequireInfo + permits UnboundAttribute.UnboundModuleRequiresInfo { + + /** + * {@return The module on which the current module depends} + */ + ModuleEntry requires(); + + /** + * {@return the flags associated with this require declaration, as a bit mask} + * Valid flags include {@link jdk.internal.classfile.Classfile#ACC_TRANSITIVE}, + * {@link jdk.internal.classfile.Classfile#ACC_STATIC_PHASE}, + * {@link jdk.internal.classfile.Classfile#ACC_SYNTHETIC} and + * {@link jdk.internal.classfile.Classfile#ACC_MANDATED} + */ + int requiresFlagsMask(); + + default Set requiresFlags() { + return AccessFlag.maskToAccessFlags(requiresFlagsMask(), AccessFlag.Location.MODULE_REQUIRES); + } + + /** + * {@return the required version of the required module, if present} + */ + Optional requiresVersion(); + + /** + * {@return whether the specific access flag is set} + * @param flag the access flag + */ + default boolean has(AccessFlag flag) { + return Util.has(AccessFlag.Location.MODULE_REQUIRES, requiresFlagsMask(), flag); + } + + /** + * {@return a module requirement description} + * @param requires the required module + * @param requiresFlags the require-specific flags + * @param requiresVersion the required version + */ + static ModuleRequireInfo of(ModuleEntry requires, int requiresFlags, Utf8Entry requiresVersion) { + return new UnboundAttribute.UnboundModuleRequiresInfo(requires, requiresFlags, Optional.ofNullable(requiresVersion)); + } + + /** + * {@return a module requirement description} + * @param requires the required module + * @param requiresFlags the require-specific flags + * @param requiresVersion the required version + */ + static ModuleRequireInfo of(ModuleEntry requires, Collection requiresFlags, Utf8Entry requiresVersion) { + return of(requires, Util.flagsToBits(AccessFlag.Location.MODULE_REQUIRES, requiresFlags), requiresVersion); + } + + /** + * {@return a module requirement description} + * @param requires the required module + * @param requiresFlags the require-specific flags + * @param requiresVersion the required version + */ + static ModuleRequireInfo of(ModuleDesc requires, int requiresFlags, String requiresVersion) { + return new UnboundAttribute.UnboundModuleRequiresInfo(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(requires.moduleName())), requiresFlags, Optional.ofNullable(requiresVersion).map(s -> TemporaryConstantPool.INSTANCE.utf8Entry(s))); + } + + /** + * {@return a module requirement description} + * @param requires the required module + * @param requiresFlags the require-specific flags + * @param requiresVersion the required version + */ + static ModuleRequireInfo of(ModuleDesc requires, Collection requiresFlags, String requiresVersion) { + return of(requires, Util.flagsToBits(AccessFlag.Location.MODULE_REQUIRES, requiresFlags), requiresVersion); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleResolutionAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleResolutionAttribute.java new file mode 100644 index 0000000000000..46317d56df903 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleResolutionAttribute.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code ModuleResolution} attribute, which can + * appear on classes that represent module descriptors. This is a JDK-specific + * * attribute, which captures resolution metadata for modules. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + * + *

The specification of the {@code ModuleResolution} attribute is: + *

 {@code
+ *  ModuleResolution_attribute {
+ *    u2 attribute_name_index;    // "ModuleResolution"
+ *    u4 attribute_length;        // 2
+ *    u2 resolution_flags;
+ *
+ *  The value of the resolution_flags item is a mask of flags used to denote
+ *  properties of module resolution. The flags are as follows:
+ *
+ *   // Optional
+ *   0x0001 (DO_NOT_RESOLVE_BY_DEFAULT)
+ *
+ *   // At most one of:
+ *   0x0002 (WARN_DEPRECATED)
+ *   0x0004 (WARN_DEPRECATED_FOR_REMOVAL)
+ *   0x0008 (WARN_INCUBATING)
+ *  }
+ * } 
+ */ +public sealed interface ModuleResolutionAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundModuleResolutionAttribute, UnboundAttribute.UnboundModuleResolutionAttribute { + + /** + * The value of the resolution_flags item is a mask of flags used to denote + * properties of module resolution. The flags are as follows: + * + * // Optional + * 0x0001 (DO_NOT_RESOLVE_BY_DEFAULT) + * + * // At most one of: + * 0x0002 (WARN_DEPRECATED) + * 0x0004 (WARN_DEPRECATED_FOR_REMOVAL) + * 0x0008 (WARN_INCUBATING) + */ + int resolutionFlags(); + + /** + * {@return a {@code ModuleResolution} attribute} + * @param resolutionFlags the resolution falgs + */ + static ModuleResolutionAttribute of(int resolutionFlags) { + return new UnboundAttribute.UnboundModuleResolutionAttribute(resolutionFlags); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleTargetAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleTargetAttribute.java new file mode 100644 index 0000000000000..36beab9e4852a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/ModuleTargetAttribute.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code ModuleTarget} attribute, which can + * appear on classes that represent module descriptors. This is a JDK-specific + * attribute, which captures constraints on the target platform. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + * + *

The specification of the {@code ModuleTarget} attribute is: + *

 {@code
+ * TargetPlatform_attribute {
+ *   // index to CONSTANT_utf8_info structure in constant pool representing
+ *   // the string "ModuleTarget"
+ *   u2 attribute_name_index;
+ *   u4 attribute_length;
+ *
+ *   // index to CONSTANT_utf8_info structure with the target platform
+ *   u2 target_platform_index;
+ * }
+ * } 
+ */ +public sealed interface ModuleTargetAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundModuleTargetAttribute, UnboundAttribute.UnboundModuleTargetAttribute { + + /** + * {@return the target platform} + */ + Utf8Entry targetPlatform(); + + /** + * {@return a {@code ModuleTarget} attribute} + * @param targetPlatform the target platform + */ + static ModuleTargetAttribute of(String targetPlatform) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(targetPlatform)); + } + + /** + * {@return a {@code ModuleTarget} attribute} + * @param targetPlatform the target platform + */ + static ModuleTargetAttribute of(Utf8Entry targetPlatform) { + return new UnboundAttribute.UnboundModuleTargetAttribute(targetPlatform); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/NestHostAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/NestHostAttribute.java new file mode 100644 index 0000000000000..e2a311a76f3ff --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/NestHostAttribute.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code NestHost} attribute {@jvms 4.7.28}, which can + * appear on classes to indicate that this class is a member of a nest. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface NestHostAttribute extends Attribute, ClassElement + permits BoundAttribute.BoundNestHostAttribute, + UnboundAttribute.UnboundNestHostAttribute { + + /** + * {@return the host class of the nest to which this class belongs} + */ + ClassEntry nestHost(); + + /** + * {@return a {@code NestHost} attribute} + * @param nestHost the host class of the nest + */ + static NestHostAttribute of(ClassEntry nestHost) { + return new UnboundAttribute.UnboundNestHostAttribute(nestHost); + } + + /** + * {@return a {@code NestHost} attribute} + * @param nestHost the host class of the nest + */ + static NestHostAttribute of(ClassDesc nestHost) { + return of(TemporaryConstantPool.INSTANCE.classEntry(nestHost)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/NestMembersAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/NestMembersAttribute.java new file mode 100644 index 0000000000000..00c4a59515846 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/NestMembersAttribute.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models the {@code NestMembers} attribute {@jvms 4.7.29}, which can + * appear on classes to indicate that this class is the host of a nest. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface NestMembersAttribute extends Attribute, ClassElement + permits BoundAttribute.BoundNestMembersAttribute, UnboundAttribute.UnboundNestMembersAttribute { + + /** + * {@return the classes belonging to the nest hosted by this class} + */ + List nestMembers(); + + /** + * {@return a {@code NestMembers} attribute} + * @param nestMembers the member classes of the nest + */ + static NestMembersAttribute of(List nestMembers) { + return new UnboundAttribute.UnboundNestMembersAttribute(nestMembers); + } + + /** + * {@return a {@code NestMembers} attribute} + * @param nestMembers the member classes of the nest + */ + static NestMembersAttribute of(ClassEntry... nestMembers) { + return of(List.of(nestMembers)); + } + + /** + * {@return a {@code NestMembers} attribute} + * @param nestMembers the member classes of the nest + */ + static NestMembersAttribute ofSymbols(List nestMembers) { + return of(Util.entryList(nestMembers)); + } + + /** + * {@return a {@code NestMembers} attribute} + * @param nestMembers the member classes of the nest + */ + static NestMembersAttribute ofSymbols(ClassDesc... nestMembers) { + // List view, since ref to nestMembers is temporary + return ofSymbols(Arrays.asList(nestMembers)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/PermittedSubclassesAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/PermittedSubclassesAttribute.java new file mode 100644 index 0000000000000..affad318bf5b5 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/PermittedSubclassesAttribute.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * Models the {@code PermittedSubclasses} attribute {@jvms 4.7.31}, which can + * appear on classes to indicate which classes may extend this class. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface PermittedSubclassesAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundPermittedSubclassesAttribute, UnboundAttribute.UnboundPermittedSubclassesAttribute { + + /** + * {@return the list of permitted subclasses} + */ + List permittedSubclasses(); + + /** + * {@return a {@code PermittedSubclasses} attribute} + * @param permittedSubclasses the permitted subclasses + */ + static PermittedSubclassesAttribute of(List permittedSubclasses) { + return new UnboundAttribute.UnboundPermittedSubclassesAttribute(permittedSubclasses); + } + + /** + * {@return a {@code PermittedSubclasses} attribute} + * @param permittedSubclasses the permitted subclasses + */ + static PermittedSubclassesAttribute of(ClassEntry... permittedSubclasses) { + return of(List.of(permittedSubclasses)); + } + + /** + * {@return a {@code PermittedSubclasses} attribute} + * @param permittedSubclasses the permitted subclasses + */ + static PermittedSubclassesAttribute ofSymbols(List permittedSubclasses) { + return of(Util.entryList(permittedSubclasses)); + } + + /** + * {@return a {@code PermittedSubclasses} attribute} + * @param permittedSubclasses the permitted subclasses + */ + static PermittedSubclassesAttribute ofSymbols(ClassDesc... permittedSubclasses) { + // List view, since ref to nestMembers is temporary + return ofSymbols(Arrays.asList(permittedSubclasses)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RecordAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RecordAttribute.java new file mode 100644 index 0000000000000..bede1ac97acc3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RecordAttribute.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code Record} attribute {@jvms 4.7.30}, which can + * appear on classes to indicate that this class is a record class. + * Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing the elements of a {@link jdk.internal.classfile.ClassModel}. + */ +public sealed interface RecordAttribute extends Attribute, ClassElement + permits BoundAttribute.BoundRecordAttribute, UnboundAttribute.UnboundRecordAttribute { + + /** + * {@return the components of this record class} + */ + List components(); + + /** + * {@return a {@code Record} attribute} + * @param components the record components + */ + static RecordAttribute of(List components) { + return new UnboundAttribute.UnboundRecordAttribute(components); + } + + /** + * {@return a {@code Record} attribute} + * @param components the record components + */ + static RecordAttribute of(RecordComponentInfo... components) { + return of(List.of(components)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RecordComponentInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RecordComponentInfo.java new file mode 100644 index 0000000000000..9793703dd1acf --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RecordComponentInfo.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.AttributedElement; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundRecordComponentInfo; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models a single record component in the {@link jdk.internal.classfile.attribute.RecordAttribute}. + */ +public sealed interface RecordComponentInfo + extends AttributedElement + permits BoundRecordComponentInfo, UnboundAttribute.UnboundRecordComponentInfo { + /** + * {@return the name of this component} + */ + Utf8Entry name(); + + /** + * {@return the field descriptor of this component} + */ + Utf8Entry descriptor(); + + /** + * {@return the field descriptor of this component, as a {@linkplain ClassDesc}} + */ + default ClassDesc descriptorSymbol() { + return ClassDesc.ofDescriptor(descriptor().stringValue()); + } + + /** + * {@return a record component description} + * @param name the component name + * @param descriptor the component field descriptor + * @param attributes the component attributes + */ + static RecordComponentInfo of(Utf8Entry name, + Utf8Entry descriptor, + List> attributes) { + return new UnboundAttribute.UnboundRecordComponentInfo(name, descriptor, attributes); + } + + /** + * {@return a record component description} + * @param name the component name + * @param descriptor the component field descriptor + * @param attributes the component attributes + */ + static RecordComponentInfo of(Utf8Entry name, + Utf8Entry descriptor, + Attribute... attributes) { + return of(name, descriptor, List.of(attributes)); + } + + /** + * {@return a record component description} + * @param name the component name + * @param descriptor the component field descriptor + * @param attributes the component attributes + */ + static RecordComponentInfo of(String name, + ClassDesc descriptor, + List> attributes) { + return new UnboundAttribute.UnboundRecordComponentInfo(TemporaryConstantPool.INSTANCE.utf8Entry(name), + TemporaryConstantPool.INSTANCE.utf8Entry(descriptor.descriptorString()), + attributes); + } + + /** + * {@return a record component description} + * @param name the component name + * @param descriptor the component field descriptor + * @param attributes the component attributes + */ + static RecordComponentInfo of(String name, + ClassDesc descriptor, + Attribute... attributes) { + return of(name, descriptor, List.of(attributes)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.java new file mode 100644 index 0000000000000..6ffbfdc5d455f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +import java.util.List; + +/** + * Models the {@code RuntimeInvisibleAnnotations} attribute {@jvms 4.7.17}, which + * can appear on classes, methods, and fields. Delivered as a + * {@link jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.FieldElement}, or + * {@link jdk.internal.classfile.MethodElement} when traversing the corresponding model type. + */ +public sealed interface RuntimeInvisibleAnnotationsAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement + permits BoundAttribute.BoundRuntimeInvisibleAnnotationsAttribute, + UnboundAttribute.UnboundRuntimeInvisibleAnnotationsAttribute { + + /** + * {@return the non-runtime-visible annotations on this class, field, or method} + */ + List annotations(); + + /** + * {@return a {@code RuntimeInvisibleAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeInvisibleAnnotationsAttribute of(List annotations) { + return new UnboundAttribute.UnboundRuntimeInvisibleAnnotationsAttribute(annotations); + } + + /** + * {@return a {@code RuntimeInvisibleAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeInvisibleAnnotationsAttribute of(Annotation... annotations) { + return of(List.of(annotations)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleParameterAnnotationsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleParameterAnnotationsAttribute.java new file mode 100644 index 0000000000000..5140251611b7b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleParameterAnnotationsAttribute.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code RuntimeInvisibleParameterAnnotations} attribute + * {@jvms 4.7.19}, which can appear on methods. Delivered as a {@link + * jdk.internal.classfile.MethodElement} when traversing a {@link MethodModel}. + */ +public sealed interface RuntimeInvisibleParameterAnnotationsAttribute + extends Attribute, MethodElement + permits BoundAttribute.BoundRuntimeInvisibleParameterAnnotationsAttribute, + UnboundAttribute.UnboundRuntimeInvisibleParameterAnnotationsAttribute { + + /** + * {@return the list of annotations corresponding to each method parameter} + * The element at the i'th index corresponds to the annotations on the i'th + * parameter. + */ + List> parameterAnnotations(); + + /** + * {@return a {@code RuntimeInvisibleParameterAnnotations} attribute} + * @param parameterAnnotations a list of parameter annotations for each parameter + */ + static RuntimeInvisibleParameterAnnotationsAttribute of(List> parameterAnnotations) { + return new UnboundAttribute.UnboundRuntimeInvisibleParameterAnnotationsAttribute(parameterAnnotations); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.java new file mode 100644 index 0000000000000..f86c7ca1b3904 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.TypeAnnotation; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code RuntimeInvisibleTypeAnnotations} attribute {@jvms 4.7.21}, which + * can appear on classes, methods, fields, and code attributes. Delivered as a + * {@link jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.FieldElement}, + * {@link jdk.internal.classfile.MethodElement}, or {@link CodeElement} when traversing + * the corresponding model type. + */ +public sealed interface RuntimeInvisibleTypeAnnotationsAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement, CodeElement + permits BoundAttribute.BoundRuntimeInvisibleTypeAnnotationsAttribute, + UnboundAttribute.UnboundRuntimeInvisibleTypeAnnotationsAttribute { + + /** + * {@return the non-runtime-visible type annotations on parts of this class, field, or method} + */ + List annotations(); + + /** + * {@return a {@code RuntimeInvisibleTypeAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeInvisibleTypeAnnotationsAttribute of(List annotations) { + return new UnboundAttribute.UnboundRuntimeInvisibleTypeAnnotationsAttribute(annotations); + } + + /** + * {@return a {@code RuntimeInvisibleTypeAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeInvisibleTypeAnnotationsAttribute of(TypeAnnotation... annotations) { + return of(List.of(annotations)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleAnnotationsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleAnnotationsAttribute.java new file mode 100644 index 0000000000000..98f92546aa6ba --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleAnnotationsAttribute.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +import java.util.List; + +/** + * Models the {@code RuntimeVisibleAnnotations} attribute {@jvms 4.7.16}, which + * can appear on classes, methods, and fields. Delivered as a + * {@link jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.FieldElement}, or + * {@link jdk.internal.classfile.MethodElement} when traversing the corresponding model type. + */ +public sealed interface RuntimeVisibleAnnotationsAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement + permits BoundAttribute.BoundRuntimeVisibleAnnotationsAttribute, + UnboundAttribute.UnboundRuntimeVisibleAnnotationsAttribute { + + /** + * {@return the runtime-visible annotations on this class, field, or method} + */ + List annotations(); + + /** + * {@return a {@code RuntimeVisibleAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeVisibleAnnotationsAttribute of(List annotations) { + return new UnboundAttribute.UnboundRuntimeVisibleAnnotationsAttribute(annotations); + } + + /** + * {@return a {@code RuntimeVisibleAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeVisibleAnnotationsAttribute of(Annotation... annotations) { + return of(List.of(annotations)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleParameterAnnotationsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleParameterAnnotationsAttribute.java new file mode 100644 index 0000000000000..91d9234c3a1da --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleParameterAnnotationsAttribute.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code RuntimeVisibleParameterAnnotations} attribute {@jvms 4.7.18}, which + * can appear on methods. Delivered as a {@link jdk.internal.classfile.MethodElement} + * when traversing a {@link MethodModel}. + */ +public sealed interface RuntimeVisibleParameterAnnotationsAttribute + extends Attribute, MethodElement + permits BoundAttribute.BoundRuntimeVisibleParameterAnnotationsAttribute, + UnboundAttribute.UnboundRuntimeVisibleParameterAnnotationsAttribute { + + /** + * {@return the list of annotations corresponding to each method parameter} + * The element at the i'th index corresponds to the annotations on the i'th + * parameter. + */ + List> parameterAnnotations(); + + /** + * {@return a {@code RuntimeVisibleParameterAnnotations} attribute} + * @param parameterAnnotations a list of parameter annotations for each parameter + */ + static RuntimeVisibleParameterAnnotationsAttribute of(List> parameterAnnotations) { + return new UnboundAttribute.UnboundRuntimeVisibleParameterAnnotationsAttribute(parameterAnnotations); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.java new file mode 100644 index 0000000000000..79ca6e435fc2c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.TypeAnnotation; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code RuntimeVisibleTypeAnnotations} attribute {@jvms 4.7.20}, which + * can appear on classes, methods, fields, and code attributes. Delivered as a + * {@link jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.FieldElement}, + * {@link jdk.internal.classfile.MethodElement}, or {@link CodeElement} when traversing + * the corresponding model type. + */ +public sealed interface RuntimeVisibleTypeAnnotationsAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement, CodeElement + permits BoundAttribute.BoundRuntimeVisibleTypeAnnotationsAttribute, + UnboundAttribute.UnboundRuntimeVisibleTypeAnnotationsAttribute { + + /** + * {@return the runtime-visible type annotations on parts of this class, field, or method} + */ + List annotations(); + + /** + * {@return a {@code RuntimeVisibleTypeAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeVisibleTypeAnnotationsAttribute of(List annotations) { + return new UnboundAttribute.UnboundRuntimeVisibleTypeAnnotationsAttribute(annotations); + } + + /** + * {@return a {@code RuntimeVisibleTypeAnnotations} attribute} + * @param annotations the annotations + */ + static RuntimeVisibleTypeAnnotationsAttribute of(TypeAnnotation... annotations) { + return of(List.of(annotations)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/SignatureAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/SignatureAttribute.java new file mode 100644 index 0000000000000..010b2d66dd627 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/SignatureAttribute.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassSignature; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.MethodSignature; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code Signature} attribute {@jvms 4.7.9}, which + * can appear on classes, methods, or fields. Delivered as a + * {@link jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.FieldElement}, or + * {@link jdk.internal.classfile.MethodElement} when traversing + * the corresponding model type. + */ +public sealed interface SignatureAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement + permits BoundAttribute.BoundSignatureAttribute, UnboundAttribute.UnboundSignatureAttribute { + + /** + * {@return the signature for the class, method, or field} + */ + Utf8Entry signature(); + + /** + * Parse the siganture as a class signature. + * @return the class signature + */ + default ClassSignature asClassSignature() { + return ClassSignature.parseFrom(signature().stringValue()); + } + + /** + * Parse the signature as a method signature. + * @return the method signature + */ + default MethodSignature asMethodSignature() { + return MethodSignature.parseFrom(signature().stringValue()); + } + + /** + * Parse the siganture as a type signature. + * @return the type signature + */ + default Signature asTypeSignature() { + return Signature.parseFrom(signature().stringValue()); + } + + /** + * {@return a {@code Signature} attribute for a class} + * @param classSignature the signature + */ + static SignatureAttribute of(ClassSignature classSignature) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(classSignature.signatureString())); + } + + /** + * {@return a {@code Signature} attribute for a method} + * @param methodSignature the signature + */ + static SignatureAttribute of(MethodSignature methodSignature) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(methodSignature.signatureString())); + } + + /** + * {@return a {@code Signature} attribute} + * @param signature the signature + */ + static SignatureAttribute of(Signature signature) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(signature.signatureString())); + } + + /** + * {@return a {@code Signature} attribute} + * @param signature the signature + */ + static SignatureAttribute of(Utf8Entry signature) { + return new UnboundAttribute.UnboundSignatureAttribute(signature); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceDebugExtensionAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceDebugExtensionAttribute.java new file mode 100644 index 0000000000000..55c32a159a056 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceDebugExtensionAttribute.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * SourceDebugExtensionAttribute. + */ +public sealed interface SourceDebugExtensionAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundSourceDebugExtensionAttribute, UnboundAttribute.UnboundSourceDebugExtensionAttribute { + + /** + * {@return the debug extension payload} + */ + byte[] contents(); + + /** + * {@return a {@code SourceDebugExtension} attribute} + * @param contents the extension contents + */ + static SourceDebugExtensionAttribute of(byte[] contents) { + return new UnboundAttribute.UnboundSourceDebugExtensionAttribute(contents); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceFileAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceFileAttribute.java new file mode 100644 index 0000000000000..7f9a73a84823d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceFileAttribute.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code SourceFile} attribute {@jvms 4.7.10}, which + * can appear on classes. Delivered as a {@link jdk.internal.classfile.ClassElement} + * when traversing a {@link ClassModel}. + */ +public sealed interface SourceFileAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundSourceFileAttribute, UnboundAttribute.UnboundSourceFileAttribute { + + /** + * {@return the name of the source file from which this class was compiled} + */ + Utf8Entry sourceFile(); + + static SourceFileAttribute of(String sourceFile) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(sourceFile)); + } + + static SourceFileAttribute of(Utf8Entry sourceFile) { + return new UnboundAttribute.UnboundSourceFileAttribute(sourceFile); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceIDAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceIDAttribute.java new file mode 100644 index 0000000000000..deb9bee83d4c3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/SourceIDAttribute.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code SourceFile} attribute (@@@ reference needed), which can + * appear on classes. Delivered as a {@link jdk.internal.classfile.ClassElement} when + * traversing a {@link ClassModel}. + */ +public sealed interface SourceIDAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundSourceIDAttribute, UnboundAttribute.UnboundSourceIDAttribute { + + /** + * {@return the source id} The source id is the last modified time of the + * source file (as reported by the filesystem, in milliseconds) when the + * classfile is compiled. + */ + Utf8Entry sourceId(); + + /** + * {@return a {@code SourceID} attribute} + * @param sourceId the source id + */ + static SourceIDAttribute of(Utf8Entry sourceId) { + return new UnboundAttribute.UnboundSourceIDAttribute(sourceId); + } + + /** + * {@return a {@code SourceID} attribute} + * @param sourceId the source id + */ + static SourceIDAttribute of(String sourceId) { + return of(TemporaryConstantPool.INSTANCE.utf8Entry(sourceId)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/StackMapFrameInfo.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/StackMapFrameInfo.java new file mode 100644 index 0000000000000..82219dcfee87a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/StackMapFrameInfo.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.List; + +import jdk.internal.classfile.Label; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.impl.StackMapDecoder; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import static jdk.internal.classfile.Classfile.*; + +/** + * Models stack map frame of {@code StackMapTable} attribute {@jvms 4.7.4}. + */ +public sealed interface StackMapFrameInfo + permits StackMapDecoder.StackMapFrameImpl { + + int frameType(); + Label target(); + List locals(); + List stack(); + + public static StackMapFrameInfo of(Label target, + List locals, + List stack) { + + return new StackMapDecoder.StackMapFrameImpl(255, target, locals, stack); + } + + /** + * The type of a stack value. + */ + sealed interface VerificationTypeInfo { + int tag(); + } + + /** + * A simple stack value. + */ + public enum SimpleVerificationTypeInfo implements VerificationTypeInfo { + ITEM_TOP(VT_TOP), + ITEM_INTEGER(VT_INTEGER), + ITEM_FLOAT(VT_FLOAT), + ITEM_DOUBLE(VT_DOUBLE), + ITEM_LONG(VT_LONG), + ITEM_NULL(VT_NULL), + ITEM_UNINITIALIZED_THIS(VT_UNINITIALIZED_THIS); + + + private final int tag; + + SimpleVerificationTypeInfo(int tag) { + this.tag = tag; + } + + @Override + public int tag() { + return tag; + } + } + + /** + * A stack value for an object type. + */ + sealed interface ObjectVerificationTypeInfo extends VerificationTypeInfo + permits StackMapDecoder.ObjectVerificationTypeInfoImpl { + + public static ObjectVerificationTypeInfo of(ClassEntry className) { + return new StackMapDecoder.ObjectVerificationTypeInfoImpl(className); + } + + public static ObjectVerificationTypeInfo of(ClassDesc classDesc) { + return of(TemporaryConstantPool.INSTANCE.classEntry(classDesc)); + } + + /** + * {@return the class of the value} + */ + ClassEntry className(); + + default ClassDesc classSymbol() { + return className().asSymbol(); + } + } + + /** + * An uninitialized stack value. + */ + sealed interface UninitializedVerificationTypeInfo extends VerificationTypeInfo + permits StackMapDecoder.UninitializedVerificationTypeInfoImpl { + Label newTarget(); + + public static UninitializedVerificationTypeInfo of(Label newTarget) { + return new StackMapDecoder.UninitializedVerificationTypeInfoImpl(newTarget); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/StackMapTableAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/StackMapTableAttribute.java new file mode 100644 index 0000000000000..fa9f3e2841c3b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/StackMapTableAttribute.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, 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 License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 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 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. + */ + +package jdk.internal.classfile.attribute; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code StackMapTable} attribute {@jvms 4.7.4}, which can appear + * on a {@code Code} attribute. + */ +public sealed interface StackMapTableAttribute + extends Attribute, CodeElement + permits BoundAttribute.BoundStackMapTableAttribute, UnboundAttribute.UnboundStackMapTableAttribute { + + /** + * {@return the stack map frames} + */ + List entries(); + + public static StackMapTableAttribute of(List entries) { + return new UnboundAttribute.UnboundStackMapTableAttribute(entries); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/SyntheticAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/SyntheticAttribute.java new file mode 100644 index 0000000000000..7f84ed496a0b1 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/SyntheticAttribute.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; + +/** + * Models the {@code Synthetic} attribute {@jvms 4.7.8}, which can appear on + * classes, methods, and fields. Delivered as a {@link ClassElement}, + * {@link MethodElement}, or {@link FieldElement} when traversing the elements + * of a corresponding model. + */ +public sealed interface SyntheticAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement + permits BoundAttribute.BoundSyntheticAttribute, UnboundAttribute.UnboundSyntheticAttribute { + + /** + * {@return a {@code Synthetic} attribute} + */ + static SyntheticAttribute of() { + return new UnboundAttribute.UnboundSyntheticAttribute(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/attribute/UnknownAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/attribute/UnknownAttribute.java new file mode 100644 index 0000000000000..f8161a613da30 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/attribute/UnknownAttribute.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.attribute; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.impl.BoundAttribute; + +/** + * Models an unknown attribute on a class, method, or field. + */ +public sealed interface UnknownAttribute + extends Attribute, + ClassElement, MethodElement, FieldElement + permits BoundAttribute.BoundUnknownAttribute { + + /** + * {@return the uninterpreted contents of the attribute payload} + */ + byte[] contents(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/ClassPrinter.java b/src/java.base/share/classes/jdk/internal/classfile/components/ClassPrinter.java new file mode 100644 index 0000000000000..316e541ab0c46 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/ClassPrinter.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.components; + +import java.lang.constant.ConstantDesc; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Stream; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CompoundElement; + +import jdk.internal.classfile.impl.ClassPrinterImpl; + +/** + * A printer of classfiles and its elements. + *

+ * Any {@link ClassModel}, {@link FieldModel}, {@link MethodModel}, or {@link CodeModel} + * can be printed to a human-readable structured text in JSON, XML, or YAML format. + * Or it can be exported into a tree of traversable and printable nodes, + * more exactly into a tree of {@link MapNode}, {@link ListNode}, and {@link LeafNode} instances. + *

+ * Level of details to print or to export is driven by {@link Verbosity} option. + *

+ * The most frequent use case is to simply print a class: + * {@snippet lang="java" class="PackageSnippets" region="printClass"} + *

+ * {@link ClassPrinter} allows to traverse tree of simple printable nodes to hook custom printer: + * {@snippet lang="java" class="PackageSnippets" region="customPrint"} + *

+ * Another use case for {@link ClassPrinter} is to simplify writing of automated tests: + * {@snippet lang="java" class="PackageSnippets" region="printNodesInTest"} + */ +public final class ClassPrinter { + + /** + * Level of detail to print or export. + */ + public enum Verbosity { + + /** + * Only top level class info, class members and attribute names are printed. + */ + MEMBERS_ONLY, + + /** + * Top level class info, class members, and critical attributes are printed. + *

+ * Critical attributes are: + *

    + *
  • ConstantValue + *
  • Code + *
  • StackMapTable + *
  • BootstrapMethods + *
  • NestHost + *
  • NestMembers + *
  • PermittedSubclasses + *
+ * @jvms 4.7 Attributes + */ + CRITICAL_ATTRIBUTES, + + /** + * All class content is printed, including constant pool. + */ + TRACE_ALL } + + /** + * Named, traversable, and printable node parent. + */ + public sealed interface Node { + + /** + * Printable name of the node. + * @return name of the node + */ + ConstantDesc name(); + + /** + * Walks through the underlying tree. + * @return ordered stream of nodes + */ + Stream walk(); + + /** + * Prints the node and its sub-tree into JSON format. + * @param out consumer of the printed fragments + */ + default void toJson(Consumer out) { + ClassPrinterImpl.toJson(this, out); + } + + /** + * Prints the node and its sub-tree into XML format. + * @param out consumer of the printed fragments + */ + default void toXml(Consumer out) { + ClassPrinterImpl.toXml(this, out); + } + + /** + * Prints the node and its sub-tree into YAML format. + * @param out consumer of the printed fragments + */ + default void toYaml(Consumer out) { + ClassPrinterImpl.toYaml(this, out); + } + } + + /** + * A leaf node holding single printable value. + */ + public sealed interface LeafNode extends Node + permits ClassPrinterImpl.LeafNodeImpl { + + /** + * Printable node value + * @return node value + */ + ConstantDesc value(); + } + + /** + * A tree node holding {@link List} of nested nodes. + */ + public sealed interface ListNode extends Node, List + permits ClassPrinterImpl.ListNodeImpl { + } + + /** + * A tree node holding {@link Map} of nested nodes. + *

+ * Each {@link Map.Entry#getKey()} == {@link Map.Entry#getValue()}.{@link #name()}. + */ + public sealed interface MapNode extends Node, Map + permits ClassPrinterImpl.MapNodeImpl { + } + + /** + * Exports provided model into a tree of printable nodes. + * @param model a {@link ClassModel}, {@link FieldModel}, {@link MethodModel}, or {@link CodeModel} to export + * @param verbosity level of details to export + * @return root node of the exported tree + */ + public static MapNode toTree(CompoundElement model, Verbosity verbosity) { + return ClassPrinterImpl.modelToTree(model, verbosity); + } + + /** + * Prints provided model as structured text in JSON format. + * @param model a {@link ClassModel}, {@link FieldModel}, {@link MethodModel}, or {@link CodeModel} to print + * @param verbosity level of details to print + * @param out consumer of the print fragments + */ + public static void toJson(CompoundElement model, Verbosity verbosity, Consumer out) { + toTree(model, verbosity).toJson(out); + } + + /** + * Prints provided model as structured text in XML format. + * @param model a {@link ClassModel}, {@link FieldModel}, {@link MethodModel}, or {@link CodeModel} to print + * @param verbosity level of details to print + * @param out consumer of the print fragments + */ + public static void toXml(CompoundElement model, Verbosity verbosity, Consumer out) { + toTree(model, verbosity).toXml(out); + } + + /** + * Prints provided model as structured text in YAML format. + * @param model a {@link ClassModel}, {@link FieldModel}, {@link MethodModel}, or {@link CodeModel} to print + * @param verbosity level of details to print + * @param out consumer of the print fragments + */ + public static void toYaml(CompoundElement model, Verbosity verbosity, Consumer out) { + toTree(model, verbosity).toYaml(out); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/ClassRemapper.java b/src/java.base/share/classes/jdk/internal/classfile/components/ClassRemapper.java new file mode 100644 index 0000000000000..f5b7a1071ef2d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/ClassRemapper.java @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.components; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.AnnotationElement; +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassSignature; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.FieldBuilder; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.FieldTransform; +import jdk.internal.classfile.Interfaces; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.InvokeDynamicInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; +import jdk.internal.classfile.instruction.NewMultiArrayInstruction; +import jdk.internal.classfile.instruction.NewObjectInstruction; +import jdk.internal.classfile.instruction.NewReferenceArrayInstruction; +import jdk.internal.classfile.instruction.TypeCheckInstruction; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.MethodSignature; +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.Superclass; +import jdk.internal.classfile.TypeAnnotation; +import jdk.internal.classfile.attribute.AnnotationDefaultAttribute; +import jdk.internal.classfile.attribute.EnclosingMethodAttribute; +import jdk.internal.classfile.attribute.ExceptionsAttribute; +import jdk.internal.classfile.attribute.InnerClassInfo; +import jdk.internal.classfile.attribute.InnerClassesAttribute; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleProvideInfo; +import jdk.internal.classfile.attribute.NestHostAttribute; +import jdk.internal.classfile.attribute.NestMembersAttribute; +import jdk.internal.classfile.attribute.PermittedSubclassesAttribute; +import jdk.internal.classfile.attribute.RecordAttribute; +import jdk.internal.classfile.attribute.RecordComponentInfo; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; +import jdk.internal.classfile.impl.Util; +import jdk.internal.classfile.instruction.ConstantInstruction.LoadConstantInstruction; + +/** + * {@code ClassRemapper} is a {@link ClassTransform}, {@link FieldTransform}, + * {@link MethodTransform} and {@link CodeTransform} + * deeply re-mapping all class references in any form, according to given map or + * map function. + *

+ * The re-mapping is applied to superclass, interfaces, all kinds of descriptors + * and signatures, all attributes referencing classes in any form (including all + * types of annotations), and to all instructions referencing to classes. + *

+ * Primitive types and arrays are never subjects of mapping and are not allowed + * targets of mapping. + *

+ * Arrays of reference types are always decomposed, mapped as the base reference + * types and composed back to arrays. + */ +public sealed interface ClassRemapper extends ClassTransform { + + /** + * Creates new instance of {@code ClassRemapper} instructed with a class map. + * Map may contain only re-mapping entries, identity mapping is applied by default. + * @param classMap class map + * @return new instance of {@code ClassRemapper} + */ + static ClassRemapper of(Map classMap) { + return of(desc -> classMap.getOrDefault(desc, desc)); + } + + /** + * Creates new instance of {@code ClassRemapper} instructed with a map function. + * Map function must return valid {@link java.lang.constant.ClassDesc} of an interface + * or a class, even for identity mappings. + * @param mapFunction class map function + * @return new instance of {@code ClassRemapper} + */ + static ClassRemapper of(Function mapFunction) { + return new ClassRemapperImpl(mapFunction); + } + + /** + * Access method to internal class mapping function. + * @param desc source class + * @return class target class + */ + ClassDesc map(ClassDesc desc); + + /** + * Returns this {@code ClassRemapper} as {@link FieldTransform} instance + * @return this {@code ClassRemapper} as {@link FieldTransform} instance + */ + FieldTransform asFieldTransform(); + + /** + * Returns this {@code ClassRemapper} as {@link MethodTransform} instance + * @return this {@code ClassRemapper} as {@link MethodTransform} instance + */ + MethodTransform asMethodTransform(); + + /** + * Returns this {@code ClassRemapper} as {@link CodeTransform} instance + * @return this {@code ClassRemapper} as {@link CodeTransform} instance + */ + CodeTransform asCodeTransform(); + + /** + * Remaps the whole ClassModel into a new class file, including the class name. + * @param clm class model to re-map + * @return re-mapped class file bytes + */ + default byte[] remapClass(ClassModel clm) { + return Classfile.build(map(clm.thisClass().asSymbol()), + clb -> clm.forEachElement(resolve(clb).consumer())); + } + + record ClassRemapperImpl(Function mapFunction) implements ClassRemapper { + + @Override + public void accept(ClassBuilder clb, ClassElement cle) { + switch (cle) { + case FieldModel fm -> + clb.withField(fm.fieldName().stringValue(), map( + fm.fieldTypeSymbol()), fb -> + fm.forEachElement(asFieldTransform().resolve(fb).consumer())); + case MethodModel mm -> + clb.withMethod(mm.methodName().stringValue(), mapMethodDesc( + mm.methodTypeSymbol()), mm.flags().flagsMask(), mb -> + mm.forEachElement(asMethodTransform().resolve(mb).consumer())); + case Superclass sc -> + clb.withSuperclass(map(sc.superclassEntry().asSymbol())); + case Interfaces ins -> + clb.withInterfaceSymbols(Util.mappedList(ins.interfaces(), in -> + map(in.asSymbol()))); + case SignatureAttribute sa -> + clb.with(SignatureAttribute.of(mapClassSignature(sa.asClassSignature()))); + case InnerClassesAttribute ica -> + clb.with(InnerClassesAttribute.of(ica.classes().stream().map(ici -> + InnerClassInfo.of(map(ici.innerClass().asSymbol()), + ici.outerClass().map(oc -> map(oc.asSymbol())), + ici.innerName().map(Utf8Entry::stringValue), + ici.flagsMask())).toList())); + case EnclosingMethodAttribute ema -> + clb.with(EnclosingMethodAttribute.of(map(ema.enclosingClass().asSymbol()), + ema.enclosingMethodName().map(Utf8Entry::stringValue), + ema.enclosingMethodTypeSymbol().map(this::mapMethodDesc))); + case RecordAttribute ra -> + clb.with(RecordAttribute.of(ra.components().stream() + .map(this::mapRecordComponent).toList())); + case ModuleAttribute ma -> + clb.with(ModuleAttribute.of(ma.moduleName(), ma.moduleFlagsMask(), + ma.moduleVersion().orElse(null), + ma.requires(), ma.exports(), ma.opens(), + ma.uses().stream().map(ce -> + clb.constantPool().classEntry(map(ce.asSymbol()))).toList(), + ma.provides().stream().map(mp -> + ModuleProvideInfo.of(map(mp.provides().asSymbol()), + mp.providesWith().stream().map(pw -> + map(pw.asSymbol())).toList())).toList())); + case NestHostAttribute nha -> + clb.with(NestHostAttribute.of(map(nha.nestHost().asSymbol()))); + case NestMembersAttribute nma -> + clb.with(NestMembersAttribute.ofSymbols(nma.nestMembers().stream() + .map(nm -> map(nm.asSymbol())).toList())); + case PermittedSubclassesAttribute psa -> + clb.with(PermittedSubclassesAttribute.ofSymbols( + psa.permittedSubclasses().stream().map(ps -> + map(ps.asSymbol())).toList())); + case RuntimeVisibleAnnotationsAttribute aa -> + clb.with(RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + clb.with(RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + clb.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + clb.with(cle); + } + } + + @Override + public FieldTransform asFieldTransform() { + return (FieldBuilder fb, FieldElement fe) -> { + switch (fe) { + case SignatureAttribute sa -> + fb.with(SignatureAttribute.of( + mapSignature(sa.asTypeSignature()))); + case RuntimeVisibleAnnotationsAttribute aa -> + fb.with(RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + fb.with(RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + fb.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + fb.with(fe); + } + }; + } + + @Override + public MethodTransform asMethodTransform() { + return (MethodBuilder mb, MethodElement me) -> { + switch (me) { + case AnnotationDefaultAttribute ada -> + mb.with(AnnotationDefaultAttribute.of( + mapAnnotationValue(ada.defaultValue()))); + case CodeModel com -> + mb.transformCode(com, asCodeTransform()); + case ExceptionsAttribute ea -> + mb.with(ExceptionsAttribute.ofSymbols( + ea.exceptions().stream().map(ce -> + map(ce.asSymbol())).toList())); + case SignatureAttribute sa -> + mb.with(SignatureAttribute.of( + mapMethodSignature(sa.asMethodSignature()))); + case RuntimeVisibleAnnotationsAttribute aa -> + mb.with(RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + mb.with(RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeVisibleParameterAnnotationsAttribute paa -> + mb.with(RuntimeVisibleParameterAnnotationsAttribute.of( + paa.parameterAnnotations().stream() + .map(this::mapAnnotations).toList())); + case RuntimeInvisibleParameterAnnotationsAttribute paa -> + mb.with(RuntimeInvisibleParameterAnnotationsAttribute.of( + paa.parameterAnnotations().stream() + .map(this::mapAnnotations).toList())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + mb.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + mb.with(me); + } + }; + } + + @Override + public CodeTransform asCodeTransform() { + return (CodeBuilder cob, CodeElement coe) -> { + switch (coe) { + case FieldInstruction fai -> + cob.fieldInstruction(fai.opcode(), map(fai.owner().asSymbol()), + fai.name().stringValue(), map(fai.typeSymbol())); + case InvokeInstruction ii -> + cob.invokeInstruction(ii.opcode(), map(ii.owner().asSymbol()), + ii.name().stringValue(), mapMethodDesc(ii.typeSymbol()), + ii.isInterface()); + case InvokeDynamicInstruction idi -> + cob.invokeDynamicInstruction(DynamicCallSiteDesc.of( + idi.bootstrapMethod(), idi.name().stringValue(), + mapMethodDesc(idi.typeSymbol()))); + case NewObjectInstruction c -> + cob.newObjectInstruction(map(c.className().asSymbol())); + case NewReferenceArrayInstruction c -> + cob.anewarray(map(c.componentType().asSymbol())); + case NewMultiArrayInstruction c -> + cob.multianewarray(map(c.arrayType().asSymbol()), c.dimensions()); + case TypeCheckInstruction c -> + cob.typeCheckInstruction(c.opcode(), map(c.type().asSymbol())); + case ExceptionCatch c -> + cob.exceptionCatch(c.tryStart(), c.tryEnd(), c.handler(),c.catchType() + .map(d -> TemporaryConstantPool.INSTANCE.classEntry(map(d.asSymbol())))); + case LocalVariable c -> + cob.localVariable(c.slot(), c.name().stringValue(), map(c.typeSymbol()), + c.startScope(), c.endScope()); + case LocalVariableType c -> + cob.localVariableType(c.slot(), c.name().stringValue(), + mapSignature(c.signatureSymbol()), c.startScope(), c.endScope()); + case LoadConstantInstruction ldc -> + cob.constantInstruction(ldc.opcode(), + mapConstantValue(ldc.constantValue())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + cob.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + cob.with(coe); + } + }; + } + + @Override + public ClassDesc map(ClassDesc desc) { + if (desc == null) return null; + if (desc.isArray()) return map(desc.componentType()).arrayType(); + if (desc.isPrimitive()) return desc; + return mapFunction.apply(desc); + } + + MethodTypeDesc mapMethodDesc(MethodTypeDesc desc) { + return MethodTypeDesc.of(map(desc.returnType()), + desc.parameterList().stream().map(this::map).toArray(ClassDesc[]::new)); + } + + ClassSignature mapClassSignature(ClassSignature signature) { + return ClassSignature.of(mapTypeParams(signature.typeParameters()), + mapSignature(signature.superclassSignature()), + signature.superinterfaceSignatures().stream() + .map(this::mapSignature).toArray(Signature.RefTypeSig[]::new)); + } + + MethodSignature mapMethodSignature(MethodSignature signature) { + return MethodSignature.of(mapTypeParams(signature.typeParameters()), + signature.throwableSignatures().stream().map(this::mapSignature).toList(), + mapSignature(signature.result()), + signature.arguments().stream() + .map(this::mapSignature).toArray(Signature[]::new)); + } + + RecordComponentInfo mapRecordComponent(RecordComponentInfo component) { + return RecordComponentInfo.of(component.name().stringValue(), + map(component.descriptorSymbol()), + component.attributes().stream().map(atr -> + switch (atr) { + case SignatureAttribute sa -> + SignatureAttribute.of( + mapSignature(sa.asTypeSignature())); + case RuntimeVisibleAnnotationsAttribute aa -> + RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations())); + case RuntimeInvisibleAnnotationsAttribute aa -> + RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations())); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations())); + default -> atr; + }).toList()); + } + + DirectMethodHandleDesc mapDirectMethodHandle(DirectMethodHandleDesc dmhd) { + return switch (dmhd.kind()) { + case GETTER, SETTER, STATIC_GETTER, STATIC_SETTER -> + MethodHandleDesc.ofField(dmhd.kind(), map(dmhd.owner()), + dmhd.methodName(), + map(ClassDesc.ofDescriptor(dmhd.lookupDescriptor()))); + default -> + MethodHandleDesc.ofMethod(dmhd.kind(), map(dmhd.owner()), + dmhd.methodName(), + mapMethodDesc(MethodTypeDesc.ofDescriptor(dmhd.lookupDescriptor()))); + }; + } + + ConstantDesc mapConstantValue(ConstantDesc value) { + return switch (value) { + case ClassDesc cd -> + map(cd); + case DynamicConstantDesc dcd -> + mapDynamicConstant(dcd); + case DirectMethodHandleDesc dmhd -> + mapDirectMethodHandle(dmhd); + case MethodTypeDesc mtd -> + mapMethodDesc(mtd); + default -> value; + }; + } + + DynamicConstantDesc mapDynamicConstant(DynamicConstantDesc dcd) { + return DynamicConstantDesc.ofNamed(mapDirectMethodHandle(dcd.bootstrapMethod()), + dcd.constantName(), + map(dcd.constantType()), + dcd.bootstrapArgsList().stream().map(this::mapConstantValue).toArray(ConstantDesc[]::new)); + } + + @SuppressWarnings("unchecked") + S mapSignature(S signature) { + return (S) switch (signature) { + case Signature.ArrayTypeSig ats -> + Signature.ArrayTypeSig.of(mapSignature(ats.componentSignature())); + case Signature.ClassTypeSig cts -> + Signature.ClassTypeSig.of( + cts.outerType().map(this::mapSignature).orElse(null), + map(cts.classDesc()), + cts.typeArgs().stream() + .map(ta -> Signature.TypeArg.of( + ta.wildcardIndicator(), + ta.boundType().map(this::mapSignature))) + .toArray(Signature.TypeArg[]::new)); + default -> signature; + }; + } + + List mapAnnotations(List annotations) { + return annotations.stream().map(this::mapAnnotation).toList(); + } + + Annotation mapAnnotation(Annotation a) { + return Annotation.of(map(a.classSymbol()), a.elements().stream().map(el -> + AnnotationElement.of(el.name(), mapAnnotationValue(el.value()))).toList()); + } + + AnnotationValue mapAnnotationValue(AnnotationValue val) { + return switch (val) { + case AnnotationValue.OfAnnotation oa -> + AnnotationValue.ofAnnotation(mapAnnotation(oa.annotation())); + case AnnotationValue.OfArray oa -> + AnnotationValue.ofArray(oa.values().stream().map(this::mapAnnotationValue).toList()); + case AnnotationValue.OfConstant oc -> oc; + case AnnotationValue.OfClass oc -> + AnnotationValue.ofClass(map(oc.classSymbol())); + case AnnotationValue.OfEnum oe -> + AnnotationValue.ofEnum(map(oe.classSymbol()), oe.constantName().stringValue()); + }; + } + + List mapTypeAnnotations(List typeAnnotations) { + return typeAnnotations.stream().map(a -> TypeAnnotation.of(a.targetInfo(), + a.targetPath(), map(a.classSymbol()), + a.elements().stream().map(el -> AnnotationElement.of(el.name(), + mapAnnotationValue(el.value()))).toList())).toList(); + } + + List mapTypeParams(List typeParams) { + return typeParams.stream().map(tp -> Signature.TypeParam.of(tp.identifier(), + tp.classBound().map(this::mapSignature), + tp.interfaceBounds().stream() + .map(this::mapSignature).toArray(Signature.RefTypeSig[]::new))).toList(); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/CodeLocalsShifter.java b/src/java.base/share/classes/jdk/internal/classfile/components/CodeLocalsShifter.java new file mode 100644 index 0000000000000..b246258c8a45e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/CodeLocalsShifter.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.components; + +import java.lang.constant.MethodTypeDesc; +import java.util.Arrays; + +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.AccessFlags; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.instruction.IncrementInstruction; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.StoreInstruction; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.LocalVariableType; + +/** + * {@link CodeLocalsShifter} is a {@link CodeTransform} shifting locals to + * newly allocated positions to avoid conflicts during code injection. + * Locals pointing to the receiver or to method arguments slots are never shifted. + * All locals pointing beyond the method arguments are re-indexed in order of appearance. + */ +public sealed interface CodeLocalsShifter extends CodeTransform { + + /** + * Creates a new instance of {@link CodeLocalsShifter} + * with fixed local slots calculated from provided method information + * @param methodFlags flags of the method to construct {@link CodeLocalsShifter} for + * @param methodDescriptor descriptor of the method to construct {@link CodeLocalsShifter} for + * @return new instance of {@link CodeLocalsShifter} + */ + static CodeLocalsShifter of(AccessFlags methodFlags, MethodTypeDesc methodDescriptor) { + int fixed = methodFlags.has(AccessFlag.STATIC) ? 0 : 1; + for (var param : methodDescriptor.parameterList()) + fixed += TypeKind.fromDescriptor(param.descriptorString()).slotSize(); + return new CodeLocalsShifterImpl(fixed); + } + + final static class CodeLocalsShifterImpl implements CodeLocalsShifter { + + private int[] locals = new int[0]; + private final int fixed; + + private CodeLocalsShifterImpl(int fixed) { + this.fixed = fixed; + } + + @Override + public void accept(CodeBuilder cob, CodeElement coe) { + switch (coe) { + case LoadInstruction li -> + cob.loadInstruction( + li.typeKind(), + shift(cob, li.slot(), li.typeKind())); + case StoreInstruction si -> + cob.storeInstruction( + si.typeKind(), + shift(cob, si.slot(), si.typeKind())); + case IncrementInstruction ii -> + cob.incrementInstruction( + shift(cob, ii.slot(), TypeKind.IntType), + ii.constant()); + case LocalVariable lv -> + cob.localVariable( + shift(cob, lv.slot(), TypeKind.fromDescriptor(lv.type().stringValue())), + lv.name(), + lv.type(), + lv.startScope(), + lv.endScope()); + case LocalVariableType lvt -> + cob.localVariableType( + shift(cob, lvt.slot(), + (lvt.signatureSymbol() instanceof Signature.BaseTypeSig bsig) + ? TypeKind.fromDescriptor(bsig.signatureString()) + : TypeKind.ReferenceType), + lvt.name(), + lvt.signature(), + lvt.startScope(), + lvt.endScope()); + default -> cob.with(coe); + } + } + + private int shift(CodeBuilder cob, int slot, TypeKind tk) { + if (tk == TypeKind.VoidType) throw new IllegalArgumentException("Illegal local void type"); + if (slot >= fixed) { + int key = 2*slot - fixed + tk.slotSize() - 1; + if (key >= locals.length) locals = Arrays.copyOf(locals, key + 20); + slot = locals[key] - 1; + if (slot < 0) { + slot = cob.allocateLocal(tk); + locals[key] = slot + 1; + if (tk.slotSize() == 2) locals[key - 1] = slot + 1; + } + } + return slot; + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/CodeRelabeler.java b/src/java.base/share/classes/jdk/internal/classfile/components/CodeRelabeler.java new file mode 100644 index 0000000000000..660dc1550fe23 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/CodeRelabeler.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.components; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.BiFunction; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.LookupSwitchInstruction; +import jdk.internal.classfile.instruction.SwitchCase; +import jdk.internal.classfile.instruction.TableSwitchInstruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.instruction.CharacterRange; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.LabelTarget; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; + +/** + * A code relabeler is a {@link CodeTransform} replacing all occurrences + * of {@link jdk.internal.classfile.Label} in the transformed code with new instances. + * All {@link jdk.internal.classfile.instruction.LabelTarget} instructions are adjusted accordingly. + * Relabeled code graph is identical to the original. + *

+ * Primary purpose of CodeRelabeler is for repeated injections of the same code blocks. + * Repeated injection of the same code block must be relabeled, so each instance of + * {@link jdk.internal.classfile.Label} is bound in the target bytecode exactly once. + */ +public sealed interface CodeRelabeler extends CodeTransform { + + /** + * Creates new instance of CodeRelabeler + * @return new instance of CodeRelabeler + */ + static CodeRelabeler of() { + return of(new IdentityHashMap<>()); + } + + /** + * Creates new instance of CodeRelabeler storing the label mapping into the provided map + * @param map label map actively used for relabeling + * @return new instance of CodeRelabeler + */ + static CodeRelabeler of(Map map) { + return of((l, cob) -> map.computeIfAbsent(l, ll -> cob.newLabel())); + } + + /** + * Creates new instance of CodeRelabeler using provided {@link java.util.function.BiFunction} + * to re-label the code. + * @param mapFunction + * @return + */ + static CodeRelabeler of(BiFunction mapFunction) { + return new CodeRelabelerImpl(mapFunction); + } + + /** + * Access method to internal re-labeling function. + * @param label source label + * @param codeBuilder builder to create new labels + * @return target label + */ + Label relabel(Label label, CodeBuilder codeBuilder); + + record CodeRelabelerImpl(BiFunction mapFunction) implements CodeRelabeler { + + @Override + public Label relabel(Label label, CodeBuilder cob) { + return mapFunction.apply(label, cob); + } + + @Override + public void accept(CodeBuilder cob, CodeElement coe) { + switch (coe) { + case BranchInstruction bi -> + cob.branchInstruction( + bi.opcode(), + relabel(bi.target(), cob)); + case LookupSwitchInstruction lsi -> + cob.lookupSwitchInstruction( + relabel(lsi.defaultTarget(), cob), + lsi.cases().stream().map(c -> + SwitchCase.of( + c.caseValue(), + relabel(c.target(), cob))).toList()); + case TableSwitchInstruction tsi -> + cob.tableSwitchInstruction( + tsi.lowValue(), + tsi.highValue(), + relabel(tsi.defaultTarget(), cob), + tsi.cases().stream().map(c -> + SwitchCase.of( + c.caseValue(), + relabel(c.target(), cob))).toList()); + case LabelTarget lt -> + cob.labelBinding( + relabel(lt.label(), cob)); + case ExceptionCatch ec -> + cob.exceptionCatch( + relabel(ec.tryStart(), cob), + relabel(ec.tryEnd(), cob), + relabel(ec.handler(), cob), + ec.catchType()); + case LocalVariable lv -> + cob.localVariable( + lv.slot(), + lv.name().stringValue(), + lv.typeSymbol(), + relabel(lv.startScope(), cob), + relabel(lv.endScope(), cob)); + case LocalVariableType lvt -> + cob.localVariableType( + lvt.slot(), + lvt.name().stringValue(), + lvt.signatureSymbol(), + relabel(lvt.startScope(), cob), + relabel(lvt.endScope(), cob)); + case CharacterRange chr -> + cob.characterRange( + relabel(chr.startScope(), cob), + relabel(chr.endScope(), cob), + chr.characterRangeStart(), + chr.characterRangeEnd(), + chr.flags()); + default -> + cob.with(coe); + } + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/CodeStackTracker.java b/src/java.base/share/classes/jdk/internal/classfile/components/CodeStackTracker.java new file mode 100644 index 0000000000000..646826f32526e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/CodeStackTracker.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.components; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Consumer; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.*; + +/** + * {@link CodeStackTracker} is a {@link CodeTransform} tracking stack content + * and calculating max stack size. + *

+ * Sample use: + *

+ * {@snippet lang=java : + * var stackTracker = CodeStackTracker.of(); + * codeBuilder.transforming(stackTracker, trackedBuilder -> { + * trackedBuilder.aload(0); + * trackedBuilder.lconst_0(); + * trackedBuilder.ifThen(...); + * ... + * var stack = stackTracker.stack().get(); + * int maxStack = stackTracker.maxStackSize().get(); + * }); + * } + */ +public sealed interface CodeStackTracker extends CodeTransform { + + /** + * Creates new instance of {@link CodeStackTracker} initialized with provided stack items + * @param initialStack initial stack content + * @return new instance of {@link CodeStackTracker} + */ + static CodeStackTracker of(TypeKind... initialStack) { + return new CodeStackTrackerImpl(initialStack); + } + + /** + * Returns {@linkplain Collection} of {@linkplain TypeKind} representing current stack. + * Returns an empty {@linkplain Optional} when the Stack content is unknown + * (right after {@code xRETURN, ATHROW, GOTO, GOTO_W, LOOKUPSWITCH, TABLESWITCH} instructions). + * + * Temporary unknown stack content can be recovered by binding of a {@linkplain Label} used as + * target of a branch instruction from existing code with known stack (forward branch target), + * or by binding of a {@linkplain Label} defining an exception handler (exception handler code start). + * + * @return actual stack content, or an empty {@linkplain Optional} if unknown + */ + Optional> stack(); + + /** + * Returns tracked max stack size. + * Returns an empty {@linkplain Optional} when max stack size tracking has been lost. + * + * Max stack size tracking is permanently lost when a stack instruction appears + * and the actual stack content is unknown. + * + * @return tracked max stack size, or an empty {@linkplain Optional} if tracking has been lost + */ + Optional maxStackSize(); + + final static class CodeStackTrackerImpl implements CodeStackTracker { + + private static record Item(TypeKind type, Item next) { + } + + private final class Stack extends AbstractCollection { + + private Item top; + private int count, realSize; + + Stack(Item top, int count, int realSize) { + this.top = top; + this.count = count; + this.realSize = realSize; + } + + @Override + public Iterator iterator() { + return new Iterator() { + Item i = top; + + @Override + public boolean hasNext() { + return i != null; + } + + @Override + public TypeKind next() { + if (i == null) { + throw new NoSuchElementException(); + } + var t = i.type; + i = i.next; + return t; + } + }; + } + + @Override + public int size() { + return count; + } + + private void push(TypeKind type) { + top = new Item(type, top); + realSize += type.slotSize(); + count++; + if (maxSize != null && realSize > maxSize) maxSize = realSize; + } + + private TypeKind pop() { + var t = top.type; + realSize -= t.slotSize(); + count--; + top = top.next; + return t; + } + } + + private Stack stack = new Stack(null, 0, 0); + private Integer maxSize = 0; + + CodeStackTrackerImpl(TypeKind... initialStack) { + for (int i = initialStack.length - 1; i >= 0; i--) + push(initialStack[i]); + } + + @Override + public Optional> stack() { + return Optional.ofNullable(fork()); + } + + @Override + public Optional maxStackSize() { + return Optional.ofNullable(maxSize); + } + + private final Map map = new HashMap<>(); + + private void push(TypeKind type) { + if (stack != null) { + if (type != TypeKind.VoidType) stack.push(type); + } else { + maxSize = null; + } + } + + private void pop(int i) { + if (stack != null) { + while (i-- > 0) stack.pop(); + } else { + maxSize = null; + } + } + + private Stack fork() { + return stack == null ? null : new Stack(stack.top, stack.count, stack.realSize); + } + + private void withStack(Consumer c) { + if (stack != null) c.accept(stack); + else maxSize = null; + } + + @Override + public void accept(CodeBuilder cb, CodeElement el) { + cb.with(el); + switch (el) { + case ArrayLoadInstruction i -> { + pop(2);push(i.typeKind()); + } + case ArrayStoreInstruction i -> + pop(3); + case BranchInstruction i -> { + if (i.opcode() == Opcode.GOTO || i.opcode() == Opcode.GOTO_W) { + map.put(i.target(), stack); + stack = null; + } else { + pop(1); + map.put(i.target(), fork()); + } + } + case ConstantInstruction i -> + push(i.typeKind()); + case ConvertInstruction i -> { + pop(1);push(i.toType()); + } + case FieldInstruction i -> { + switch (i.opcode()) { + case GETSTATIC -> + push(TypeKind.fromDescriptor(i.type().stringValue())); + case GETFIELD -> { + pop(1);push(TypeKind.fromDescriptor(i.type().stringValue())); + } + case PUTSTATIC -> + pop(1); + case PUTFIELD -> + pop(2); + } + } + case InvokeDynamicInstruction i -> { + var type = i.typeSymbol(); + pop(type.parameterCount()); + push(TypeKind.fromDescriptor(type.returnType().descriptorString())); + } + case InvokeInstruction i -> { + var type = i.typeSymbol(); + pop(type.parameterCount()); + if (i.opcode() != Opcode.INVOKESTATIC) pop(1); + push(TypeKind.fromDescriptor(type.returnType().descriptorString())); + } + case LoadInstruction i -> + push(i.typeKind()); + case StoreInstruction i -> + pop(1); + case LookupSwitchInstruction i -> { + map.put(i.defaultTarget(), stack); + for (var c : i.cases()) map.put(c.target(), fork()); + stack = null; + } + case MonitorInstruction i -> + pop(1); + case NewMultiArrayInstruction i -> { + pop(i.dimensions());push(TypeKind.ReferenceType); + } + case NewObjectInstruction i -> + push(TypeKind.ReferenceType); + case NewPrimitiveArrayInstruction i -> { + pop(1);push(TypeKind.ReferenceType); + } + case NewReferenceArrayInstruction i -> { + pop(1);push(TypeKind.ReferenceType); + } + case NopInstruction i -> {} + case OperatorInstruction i -> { + switch (i.opcode()) { + case ARRAYLENGTH, INEG, LNEG, FNEG, DNEG -> pop(1); + default -> pop(2); + } + push(i.typeKind()); + } + case ReturnInstruction i -> + stack = null; + case StackInstruction i -> { + switch (i.opcode()) { + case POP -> pop(1); + case POP2 -> withStack(s -> { + if (s.pop().slotSize() == 1) s.pop(); + }); + case DUP -> withStack(s -> { + var v = s.pop();s.push(v);s.push(v); + }); + case DUP2 -> withStack(s -> { + var v1 = s.pop(); + if (v1.slotSize() == 1) { + var v2 = s.pop(); + s.push(v2);s.push(v1); + s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v1); + } + }); + case DUP_X1 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + s.push(v1);s.push(v2);s.push(v1); + }); + case DUP_X2 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + if (v2.slotSize() == 1) { + var v3 = s.pop(); + s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v2);s.push(v1); + } + }); + case DUP2_X1 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + if (v1.slotSize() == 1) { + var v3 = s.pop(); + s.push(v2);s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v2);s.push(v1); + } + }); + case DUP2_X2 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + if (v1.slotSize() == 1) { + var v3 = s.pop(); + if (v3.slotSize() == 1) { + var v4 = s.pop(); + s.push(v2);s.push(v1);s.push(v4);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v2);s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } + } else { + if (v2.slotSize() == 1) { + var v3 = s.pop(); + s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v2);s.push(v1); + } + } + }); + case SWAP -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + s.push(v1);s.push(v2); + }); + } + } + case TableSwitchInstruction i -> { + map.put(i.defaultTarget(), stack); + for (var c : i.cases()) map.put(c.target(), fork()); + stack = null; + } + case ThrowInstruction i -> + stack = null; + case TypeCheckInstruction i -> { + switch (i.opcode()) { + case CHECKCAST -> { + pop(1);push(TypeKind.ReferenceType); + } + case INSTANCEOF -> { + pop(1);push(TypeKind.IntType); + } + } + } + case ExceptionCatch i -> + map.put(i.handler(), new Stack(new Item(TypeKind.ReferenceType, null), 1, 1)); + case LabelTarget i -> + stack = map.getOrDefault(i.label(), stack); + default -> {} + } + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/package-info.java b/src/java.base/share/classes/jdk/internal/classfile/components/package-info.java new file mode 100644 index 0000000000000..2b260db479e95 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/package-info.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + *

Specific components, transformations, and tools built on top of the + * Classfile API

+ * + * The {@code jdk.internal.classfile.components} package contains specific + * transformation components and utility classes helping to compose very complex + * tasks with minimal effort. + * + *

{@link ClassPrinter}

+ *

+ * {@link ClassPrinter} is a helper class providing seamless export of a {@link + * jdk.internal.classfile.ClassModel}, {@link jdk.internal.classfile.FieldModel}, + * {@link jdk.internal.classfile.MethodModel}, or {@link + * jdk.internal.classfile.CodeModel} into human-readable structured text in + * JSON, XML, or YAML format, or into a tree of traversable and printable nodes. + *

+ * Primary purpose of {@link ClassPrinter} is to provide human-readable class + * info for debugging, exception handling and logging purposes. The printed + * class also conforms to a standard format to support automated offline + * processing. + *

+ * The most frequent use case is to simply print a class: + * {@snippet lang="java" class="PackageSnippets" region="printClass"} + *

+ * {@link ClassPrinter} allows to traverse tree of simple printable nodes to + * hook custom printer: + * {@snippet lang="java" class="PackageSnippets" region="customPrint"} + *

+ * Another use case for {@link ClassPrinter} is to simplify writing of automated + * tests: + * {@snippet lang="java" class="PackageSnippets" region="printNodesInTest"} + * + *

{@link ClassRemapper}

+ * ClassRemapper is a {@link jdk.internal.classfile.ClassTransform}, {@link + * jdk.internal.classfile.FieldTransform}, {@link + * jdk.internal.classfile.MethodTransform} and {@link + * jdk.internal.classfile.CodeTransform} deeply re-mapping all class references + * in any form, according to given map or map function. + *

+ * The re-mapping is applied to superclass, interfaces, all kinds of descriptors + * and signatures, all attributes referencing classes in any form (including all + * types of annotations), and to all instructions referencing to classes. + *

+ * Primitive types and arrays are never subjects of mapping and are not allowed + * targets of mapping. + *

+ * Arrays of reference types are always decomposed, mapped as the base reference + * types and composed back to arrays. + *

+ * Single class remappigng example: + * {@snippet lang="java" class="PackageSnippets" region="singleClassRemap"} + *

+ * Remapping of all classes under specific package: + * {@snippet lang="java" class="PackageSnippets" region="allPackageRemap"} + * + *

{@link CodeLocalsShifter}

+ * {@link CodeLocalsShifter} is a {@link jdk.internal.classfile.CodeTransform} + * shifting locals to newly allocated positions to avoid conflicts during code + * injection. Locals pointing to the receiver or to method arguments slots are + * never shifted. All locals pointing beyond the method arguments are re-indexed + * in order of appearance. + *

+ * Sample of code transformation shifting all locals in all methods: + * {@snippet lang="java" class="PackageSnippets" region="codeLocalsShifting"} + * + *

{@link CodeRelabeler}

+ * {@link CodeRelabeler} is a {@link jdk.internal.classfile.CodeTransform} + * replacing all occurences of {@link jdk.internal.classfile.Label} in the + * transformed code with new instances. + * All {@link jdk.internal.classfile.instruction.LabelTarget} instructions are + * adjusted accordingly. + * Relabeled code graph is identical to the original. + *

+ * Primary purpose of {@link CodeRelabeler} is for repeated injections of the + * same code blocks. + * Repeated injection of the same code block must be relabeled, so each instance + * of {@link jdk.internal.classfile.Label} is bound in the target bytecode + * exactly once. + *

+ * Sample transformation relabeling all methods: + * {@snippet lang="java" class="PackageSnippets" region="codeRelabeling"} + * + *

Class Instrumentation Sample

+ * Following snippet is sample composition of {@link ClassRemapper}, {@link + * CodeLocalsShifter} and {@link CodeRelabeler} into fully functional class + * instrumenting transformation: + * {@snippet lang="java" class="PackageSnippets" region="classInstrumentation"} + */ +package jdk.internal.classfile.components; diff --git a/src/java.base/share/classes/jdk/internal/classfile/components/snippet-files/PackageSnippets.java b/src/java.base/share/classes/jdk/internal/classfile/components/snippet-files/PackageSnippets.java new file mode 100644 index 0000000000000..a31ccacd7e27c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/components/snippet-files/PackageSnippets.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; + +import java.lang.reflect.AccessFlag; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.InvokeInstruction; + +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.components.ClassPrinter; +import jdk.internal.classfile.components.ClassRemapper; +import jdk.internal.classfile.components.CodeLocalsShifter; +import jdk.internal.classfile.components.CodeRelabeler; +import jdk.internal.classfile.instruction.ReturnInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; + +class PackageSnippets { + + void printClass(ClassModel classModel) { + // @start region="printClass" + ClassPrinter.toJson(classModel, ClassPrinter.Verbosity.TRACE_ALL, System.out::print); + // @end + } + + // @start region="customPrint" + void customPrint(ClassModel classModel) { + print(ClassPrinter.toTree(classModel, ClassPrinter.Verbosity.TRACE_ALL)); + } + + void print(ClassPrinter.Node node) { + switch (node) { + case ClassPrinter.MapNode mn -> { + // print map header + mn.values().forEach(this::print); + } + case ClassPrinter.ListNode ln -> { + // print list header + ln.forEach(this::print); + } + case ClassPrinter.LeafNode n -> { + // print leaf node + } + } + } + // @end + + // @start region="printNodesInTest" + @Test + void printNodesInTest(ClassModel classModel) { + var classNode = ClassPrinter.toTree(classModel, ClassPrinter.Verbosity.TRACE_ALL); + assertContains(classNode, "method name", "myFooMethod"); + assertContains(classNode, "field name", "myBarField"); + assertContains(classNode, "inner class", "MyInnerFooClass"); + } + + void assertContains(ClassPrinter.Node node, ConstantDesc key, ConstantDesc value) { + if (!node.walk().anyMatch(n -> n instanceof ClassPrinter.LeafNode ln + && ln.name().equals(key) + && ln.value().equals(value))) { + node.toYaml(System.out::print); + throw new AssertionError("expected %s: %s".formatted(key, value)); + } + } + // @end + @interface Test{} + + void singleClassRemap(ClassModel... allMyClasses) { + // @start region="singleClassRemap" + var classRemapper = ClassRemapper.of( + Map.of(ClassDesc.of("Foo"), ClassDesc.of("Bar"))); + + for (var classModel : allMyClasses) { + byte[] newBytes = classRemapper.remapClass(classModel); + + } + // @end + } + + void allPackageRemap(ClassModel... allMyClasses) { + // @start region="allPackageRemap" + var classRemapper = ClassRemapper.of(cd -> + ClassDesc.ofDescriptor(cd.descriptorString().replace("Lcom/oldpackage/", "Lcom/newpackage/"))); + + for (var classModel : allMyClasses) { + byte[] newBytes = classRemapper.remapClass(classModel); + + } + // @end + } + + void codeLocalsShifting(ClassModel classModel) { + // @start region="codeLocalsShifting" + byte[] newBytes = classModel.transform((classBuilder, classElement) -> { + if (classElement instanceof MethodModel method) + classBuilder.transformMethod(method, + MethodTransform.transformingCode( + CodeLocalsShifter.of(method.flags(), method.methodTypeSymbol()))); + else + classBuilder.accept(classElement); + }); + // @end + } + + void codeRelabeling(ClassModel classModel) { + // @start region="codeRelabeling" + byte[] newBytes = classModel.transform( + ClassTransform.transformingMethodBodies( + CodeTransform.ofStateful(CodeRelabeler::of))); + // @end + } + + // @start region="classInstrumentation" + byte[] classInstrumentation(ClassModel target, ClassModel instrumentor, Predicate instrumentedMethodsFilter) { + var instrumentorCodeMap = instrumentor.methods().stream() + .filter(instrumentedMethodsFilter) + .collect(Collectors.toMap(mm -> mm.methodName().stringValue() + mm.methodType().stringValue(), mm -> mm.code().orElseThrow())); + var targetFieldNames = target.fields().stream().map(f -> f.fieldName().stringValue()).collect(Collectors.toSet()); + var targetMethods = target.methods().stream().map(m -> m.methodName().stringValue() + m.methodType().stringValue()).collect(Collectors.toSet()); + var instrumentorClassRemapper = ClassRemapper.of(Map.of(instrumentor.thisClass().asSymbol(), target.thisClass().asSymbol())); + return target.transform( + ClassTransform.transformingMethods( + instrumentedMethodsFilter, + (mb, me) -> { + if (me instanceof CodeModel targetCodeModel) { + var mm = targetCodeModel.parent().get(); + //instrumented methods code is taken from instrumentor + mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()), + //all references to the instrumentor class are remapped to target class + instrumentorClassRemapper.asCodeTransform() + .andThen((codeBuilder, instrumentorCodeElement) -> { + //all invocations of target methods from instrumentor are inlined + if (instrumentorCodeElement instanceof InvokeInstruction inv + && target.thisClass().asInternalName().equals(inv.owner().asInternalName()) + && mm.methodName().stringValue().equals(inv.name().stringValue()) + && mm.methodType().stringValue().equals(inv.type().stringValue())) { + + //store stacked method parameters into locals + var storeStack = new ArrayDeque(); + int slot = 0; + if (!mm.flags().has(AccessFlag.STATIC)) + storeStack.push(StoreInstruction.of(TypeKind.ReferenceType, slot++)); + for (var pt : mm.methodTypeSymbol().parameterList()) { + var tk = TypeKind.fromDescriptor(pt.descriptorString()); + storeStack.push(StoreInstruction.of(tk, slot)); + slot += tk.slotSize(); + } + storeStack.forEach(codeBuilder::with); + + //inlined target locals must be shifted based on the actual instrumentor locals + codeBuilder.block(inlinedBlockBuilder -> inlinedBlockBuilder + .transform(targetCodeModel, CodeLocalsShifter.of(mm.flags(), mm.methodTypeSymbol()) + .andThen(CodeRelabeler.of()) + .andThen((innerBuilder, shiftedTargetCode) -> { + //returns must be replaced with jump to the end of the inlined method + if (shiftedTargetCode instanceof ReturnInstruction) + innerBuilder.goto_(inlinedBlockBuilder.breakLabel()); + else + innerBuilder.with(shiftedTargetCode); + }))); + } else + codeBuilder.with(instrumentorCodeElement); + })); + } else + mb.with(me); + }) + .andThen(ClassTransform.endHandler(clb -> + //remaining instrumentor fields and methods are injected at the end + clb.transform(instrumentor, + ClassTransform.dropping(cle -> + !(cle instanceof FieldModel fm + && !targetFieldNames.contains(fm.fieldName().stringValue())) + && !(cle instanceof MethodModel mm + && !"".equals(mm.methodName().stringValue()) + && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue()))) + //and instrumentor class references remapped to target class + .andThen(instrumentorClassRemapper))))); + } + // @end +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/AnnotationConstantValueEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/AnnotationConstantValueEntry.java new file mode 100644 index 0000000000000..4c3097892f3cc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/AnnotationConstantValueEntry.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ConstantDesc; + +/** + * A constant pool entry that may be used as an annotation constant, + * which includes the four kinds of primitive constants, and UTF8 constants. + */ +public sealed interface AnnotationConstantValueEntry extends PoolEntry + permits DoubleEntry, FloatEntry, IntegerEntry, LongEntry, Utf8Entry { + + /** + * {@return the constant value} The constant value will be an {@link Integer}, + * {@link Long}, {@link Float}, {@link Double}, or {@link String}. + */ + ConstantDesc constantValue(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/ClassEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ClassEntry.java new file mode 100644 index 0000000000000..64169051ec680 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ClassEntry.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Class_info} constant in the constant pool of a + * classfile. + */ +public sealed interface ClassEntry + extends LoadableConstantEntry + permits AbstractPoolEntry.ClassEntryImpl { + + @Override + default ConstantDesc constantValue() { + return asSymbol(); + } + + /** + * {@return the UTF8 constant pool entry for the class name} + */ + Utf8Entry name(); + + /** + * {@return the class name, as an internal binary name} + */ + String asInternalName(); + + /** + * {@return the class name, as a symbolic descriptor} + */ + ClassDesc asSymbol(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantDynamicEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantDynamicEntry.java new file mode 100644 index 0000000000000..39a6aeba5ee67 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantDynamicEntry.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.TypeKind; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicConstantDesc; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Dynamic_info} constant in the constant pool of a + * classfile. + */ +public sealed interface ConstantDynamicEntry + extends DynamicConstantPoolEntry, LoadableConstantEntry + permits AbstractPoolEntry.ConstantDynamicEntryImpl { + + @Override + default ConstantDesc constantValue() { + return asSymbol(); + } + + /** + * {@return the symbolic descriptor for the {@code invokedynamic} constant} + */ + default DynamicConstantDesc asSymbol() { + return DynamicConstantDesc.ofNamed(bootstrap().bootstrapMethod().asSymbol(), + name().stringValue(), + ClassDesc.ofDescriptor(type().stringValue()), + bootstrap().arguments().stream() + .map(LoadableConstantEntry::constantValue) + .toArray(ConstantDesc[]::new)); + } + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return TypeKind.fromDescriptor(type().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPool.java new file mode 100644 index 0000000000000..703b42a4cadc7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPool.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022, 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 License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 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 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. + */ + +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.ClassReader; + +/** + * Provides read access to the constant pool and bootstrap method table of a + * classfile. + */ +public sealed interface ConstantPool + permits ClassReader, ConstantPoolBuilder { + + /** + * {@return the entry at the specified index} + * + * @param index the index within the pool of the desired entry + */ + PoolEntry entryByIndex(int index); + + /** + * {@return the number of entries in the constant pool} + */ + int entryCount(); + + /** + * {@return the {@link BootstrapMethodEntry} at the specified index within + * the bootstrap method table} + * + * @param index the index within the bootstrap method table of the desired + * entry + */ + BootstrapMethodEntry bootstrapMethodEntry(int index); + + /** + * {@return the number of entries in the bootstrap method table} + */ + int bootstrapMethodCount(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPoolBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPoolBuilder.java new file mode 100644 index 0000000000000..e95ad65d0b02e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantPoolBuilder.java @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.Collection; +import java.util.List; + +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.impl.ClassReaderImpl; +import jdk.internal.classfile.impl.Options; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import jdk.internal.classfile.WritableElement; +import jdk.internal.classfile.impl.SplitConstantPool; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.Util; + +/** + * Builder for the constant pool of a classfile. Provides read and write access + * to the constant pool that is being built. Writing is append-only and idempotent + * (entry-bearing methods will return an existing entry if there is one). + * + * A {@linkplain ConstantPoolBuilder} is associated with a {@link ClassBuilder}. + * The {@linkplain ConstantPoolBuilder} also provides access to some of the + * state of the {@linkplain ClassBuilder}, such as classfile processing options. + */ +public sealed interface ConstantPoolBuilder + extends ConstantPool, WritableElement + permits SplitConstantPool, TemporaryConstantPool { + + /** + * {@return a new constant pool builder} The new constant pool builder + * will inherit the classfile processing options of the specified class. + * If the processing options include {@link Classfile.Option#constantPoolSharing(boolean)}, + * (the default) the new constant pool builder will be also be pre-populated with the + * contents of the constant pool associated with the class reader. + * + * @param classModel the class to copy from + */ + static ConstantPoolBuilder of(ClassModel classModel) { + ClassReaderImpl reader = (ClassReaderImpl) classModel.constantPool(); + return reader.options().cpSharing + ? new SplitConstantPool(reader) + : new SplitConstantPool(reader.options()); + } + + /** + * {@return a new constant pool builder} The new constant pool builder + * will be empty and have the specified classfile processing options. + * + * @param options the processing options + */ + static ConstantPoolBuilder of(Collection options) { + return new SplitConstantPool(new Options(options)); + } + + /** + * {@return whether the provided constant pool is index-compatible with this + * one} This may be because they are the same constant pool, or because this + * constant pool was copied from the other. + * + * @param constantPool the other constant pool + */ + boolean canWriteDirect(ConstantPool constantPool); + + /** + * Writes associated bootstrap method entries to the specified writer + * + * @param buf the writer + * @return false when no bootstrap method entry has been written + */ + boolean writeBootstrapMethods(BufWriter buf); + + /** + * {@return A {@link Utf8Entry} describing the provided {@linkplain String}} + * If a UTF8 entry in the pool already describes this string, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param s the string + */ + Utf8Entry utf8Entry(String s); + + /** + * {@return A {@link Utf8Entry} describing the field descriptor of the provided + * {@linkplain ClassDesc}} + * If a UTF8 entry in the pool already describes this field descriptor, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param desc the symbolic descriptor for the class + */ + default Utf8Entry utf8Entry(ClassDesc desc) { + return utf8Entry(desc.descriptorString()); + } + + /** + * {@return A {@link Utf8Entry} describing the method descriptor of the provided + * {@linkplain MethodTypeDesc}} + * If a UTF8 entry in the pool already describes this field descriptor, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param desc the symbolic descriptor for the method type + */ + default Utf8Entry utf8Entry(MethodTypeDesc desc) { + return utf8Entry(desc.descriptorString()); + } + + /** + * {@return A {@link ClassEntry} describing the class whose internal name + * is encoded in the provided {@linkplain Utf8Entry}} + * If a Class entry in the pool already describes this class, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param ne the constant pool entry describing the internal name of the class + */ + ClassEntry classEntry(Utf8Entry ne); + + /** + * {@return A {@link ClassEntry} describing the class described by + * provided {@linkplain ClassDesc}} + * If a Class entry in the pool already describes this class, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param classDesc the symbolic descriptor for the class + */ + default ClassEntry classEntry(ClassDesc classDesc) { + return classEntry(utf8Entry(classDesc.isArray() ? classDesc.descriptorString() : Util.toInternalName(classDesc))); + } + + /** + * {@return A {@link PackageEntry} describing the class whose internal name + * is encoded in the provided {@linkplain Utf8Entry}} + * If a Package entry in the pool already describes this class, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param nameEntry the constant pool entry describing the internal name of + * the package + */ + PackageEntry packageEntry(Utf8Entry nameEntry); + + /** + * {@return A {@link PackageEntry} describing the class described by + * provided {@linkplain PackageDesc}} + * If a Package entry in the pool already describes this class, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param packageDesc the symbolic descriptor for the class + */ + default PackageEntry packageEntry(PackageDesc packageDesc) { + return packageEntry(utf8Entry(packageDesc.packageInternalName())); + } + + /** + * {@return A {@link ModuleEntry} describing the module whose name + * is encoded in the provided {@linkplain Utf8Entry}} + * If a module entry in the pool already describes this class, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param moduleName the constant pool entry describing the module name + */ + ModuleEntry moduleEntry(Utf8Entry moduleName); + + /** + * {@return A {@link ModuleEntry} describing the module described by + * provided {@linkplain ModuleDesc}} + * If a module entry in the pool already describes this class, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param moduleDesc the symbolic descriptor for the class + */ + default ModuleEntry moduleEntry(ModuleDesc moduleDesc) { + return moduleEntry(utf8Entry(moduleDesc.moduleName())); + } + + /** + * {@return A {@link NameAndTypeEntry} describing the provided name and type} + * If a NameAndType entry in the pool already describes this name and type, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param nameEntry the member name + * @param typeEntry the member field or method descriptor + */ + NameAndTypeEntry nameAndTypeEntry(Utf8Entry nameEntry, Utf8Entry typeEntry); + + /** + * {@return A {@link NameAndTypeEntry} describing the provided name and type} + * If a NameAndType entry in the pool already describes this name and type, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param name the member name + * @param type the symbolic descriptor for a field type + */ + default NameAndTypeEntry nameAndTypeEntry(String name, ClassDesc type) { + return nameAndTypeEntry(utf8Entry(name), utf8Entry(type.descriptorString())); + } + + /** + * {@return A {@link NameAndTypeEntry} describing the provided name and type} + * If a NameAndType entry in the pool already describes this name and type, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param name the member name + * @param type the symbolic descriptor for a method type + */ + default NameAndTypeEntry nameAndTypeEntry(String name, MethodTypeDesc type) { + return nameAndTypeEntry(utf8Entry(name), utf8Entry(type.descriptorString())); + } + + /** + * {@return A {@link FieldRefEntry} describing a field of a class} + * If a FieldRef entry in the pool already describes this field, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param owner the class the field is a member of + * @param nameAndType the name and type of the field + */ + FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType); + + /** + * {@return A {@link FieldRefEntry} describing a field of a class} + * If a FieldRef entry in the pool already describes this field, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param owner the class the field is a member of + * @param name the name of the field + * @param type the type of the field + */ + default FieldRefEntry fieldRefEntry(ClassDesc owner, String name, ClassDesc type) { + return fieldRefEntry(classEntry(owner), nameAndTypeEntry(name, type)); + } + + /** + * {@return A {@link MethodRefEntry} describing a method of a class} + * If a MethodRefEntry entry in the pool already describes this method, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param owner the class the method is a member of + * @param nameAndType the name and type of the method + */ + MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType); + + /** + * {@return A {@link MethodRefEntry} describing a method of a class} + * If a MethodRefEntry entry in the pool already describes this method, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param owner the class the method is a member of + * @param name the name of the method + * @param type the type of the method + */ + default MethodRefEntry methodRefEntry(ClassDesc owner, String name, MethodTypeDesc type) { + return methodRefEntry(classEntry(owner), nameAndTypeEntry(name, type)); + } + + /** + * {@return A {@link InterfaceMethodRefEntry} describing a method of a class} + * If a InterfaceMethodRefEntry entry in the pool already describes this method, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param owner the class the method is a member of + * @param nameAndType the name and type of the method + */ + InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType); + + /** + * {@return A {@link InterfaceMethodRefEntry} describing a method of a class} + * If a InterfaceMethodRefEntry entry in the pool already describes this method, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param owner the class the method is a member of + * @param name the name of the method + * @param type the type of the method + */ + default InterfaceMethodRefEntry interfaceMethodRefEntry(ClassDesc owner, String name, MethodTypeDesc type) { + return interfaceMethodRefEntry(classEntry(owner), nameAndTypeEntry(name, type)); + } + + /** + * {@return A {@link MethodTypeEntry} describing a method type} + * If a MethodType entry in the pool already describes this method type, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param descriptor the symbolic descriptor of the method type + */ + MethodTypeEntry methodTypeEntry(MethodTypeDesc descriptor); + + /** + * {@return A {@link MethodTypeEntry} describing a method type} + * If a MethodType entry in the pool already describes this method type, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param descriptor the constant pool entry for the method type descriptor + */ + MethodTypeEntry methodTypeEntry(Utf8Entry descriptor); + + /** + * {@return A {@link MethodHandleEntry} describing a direct method handle} + * If a MethodHandle entry in the pool already describes this method handle, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param descriptor the symbolic descriptor of the method handle + */ + default MethodHandleEntry methodHandleEntry(DirectMethodHandleDesc descriptor) { + var owner = classEntry(descriptor.owner()); + var nat = nameAndTypeEntry(utf8Entry(descriptor.methodName()), utf8Entry(descriptor.lookupDescriptor())); + return methodHandleEntry(descriptor.refKind(), switch (descriptor.kind()) { + case GETTER, SETTER, STATIC_GETTER, STATIC_SETTER -> fieldRefEntry(owner, nat); + case INTERFACE_STATIC, INTERFACE_VIRTUAL, INTERFACE_SPECIAL -> interfaceMethodRefEntry(owner, nat); + case STATIC, VIRTUAL, SPECIAL, CONSTRUCTOR -> methodRefEntry(owner, nat); + }); + } + + /** + * {@return A {@link MethodHandleEntry} describing a field accessor or method} + * If a MethodHandle entry in the pool already describes this method handle, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param refKind the reference kind of the method handle {@jvms 4.4.8} + * @param reference the constant pool entry describing the field or method + */ + MethodHandleEntry methodHandleEntry(int refKind, MemberRefEntry reference); + + /** + * {@return An {@link InvokeDynamicEntry} describing a dynamic call site} + * If an InvokeDynamic entry in the pool already describes this site, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param dcsd the symbolic descriptor of the method handle + */ + default InvokeDynamicEntry invokeDynamicEntry(DynamicCallSiteDesc dcsd) { + return invokeDynamicEntry(bsmEntry((DirectMethodHandleDesc)dcsd.bootstrapMethod(), List.of(dcsd.bootstrapArgs())), nameAndTypeEntry(dcsd.invocationName(), dcsd.invocationType())); + } + + /** + * {@return An {@link InvokeDynamicEntry} describing a dynamic call site} + * If an InvokeDynamic entry in the pool already describes this site, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param bootstrapMethodEntry the entry in the bootstrap method table + * @param nameAndType the invocation name and type + */ + InvokeDynamicEntry invokeDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, + NameAndTypeEntry nameAndType); + + /** + * {@return A {@link ConstantDynamicEntry} describing a dynamic constant} + * If a ConstantDynamic entry in the pool already describes this site, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param dcd the symbolic descriptor of the constant + */ + default ConstantDynamicEntry constantDynamicEntry(DynamicConstantDesc dcd) { + return constantDynamicEntry(bsmEntry(dcd.bootstrapMethod(), List.of(dcd.bootstrapArgs())), nameAndTypeEntry(dcd.constantName(), dcd.constantType())); + } + + /** + * {@return A {@link ConstantDynamicEntry} describing a dynamic constant} + * If a ConstantDynamic entry in the pool already describes this site, + * it is returned; otherwise, a new entry is added and the new entry is + * returned. + * + * @param bootstrapMethodEntry the entry in the bootstrap method table + * @param nameAndType the invocation name and type + */ + ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, NameAndTypeEntry nameAndType); + + /** + * {@return An {@link IntegerEntry} describing the provided value} + * If an integer entry in the pool already describes this value, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param value the value + */ + IntegerEntry intEntry(int value); + + /** + * {@return A {@link FloatEntry} describing the provided value} + * If a float entry in the pool already describes this value, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param value the value + */ + FloatEntry floatEntry(float value); + + /** + * {@return A {@link LongEntry} describing the provided value} + * If a long entry in the pool already describes this value, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param value the value + */ + LongEntry longEntry(long value); + + /** + * {@return A {@link DoubleEntry} describing the provided value} + * If a double entry in the pool already describes this value, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param value the value + */ + DoubleEntry doubleEntry(double value); + + /** + * {@return A {@link StringEntry} referencing the provided UTF8 entry} + * If a String entry in the pool already describes this value, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param utf8 the UTF8 entry describing the string + */ + StringEntry stringEntry(Utf8Entry utf8); + + /** + * {@return A {@link StringEntry} describing the provided value} + * If a string entry in the pool already describes this value, it is returned; + * otherwise, a new entry is added and the new entry is returned. + * + * @param value the value + */ + default StringEntry stringEntry(String value) { + return stringEntry(utf8Entry(value)); + } + + /** + * {@return A {@link ConstantValueEntry} descripbing the provided + * Integer, Long, Float, Double, or String constant} + * + * @param c the constant + */ + default ConstantValueEntry constantValueEntry(ConstantDesc c) { + if (c instanceof Integer i) return intEntry(i); + if (c instanceof String s) return stringEntry(s); + if (c instanceof Long l) return longEntry(l); + if (c instanceof Float f) return floatEntry(f); + if (c instanceof Double d) return doubleEntry(d); + throw new IllegalArgumentException("Illegal type: " + (c == null ? null : c.getClass())); + } + + /** + * {@return A {@link LoadableConstantEntry} describing the provided + * constant} The constant should be an Integer, String, Long, Float, + * Double, ClassDesc (for a Class constant), MethodTypeDesc (for a MethodType + * constant), DirectMethodHandleDesc (for a MethodHandle constant), or + * a DynamicConstantDesc (for a dynamic constant.) + * + * @param c the constant + */ + default LoadableConstantEntry loadableConstantEntry(ConstantDesc c) { + if (c instanceof Integer i) return intEntry(i); + if (c instanceof String s) return stringEntry(s); + if (c instanceof Long l) return longEntry(l); + if (c instanceof Float f) return floatEntry(f); + if (c instanceof Double d) return doubleEntry(d); + if (c instanceof ClassDesc cd) return classEntry(cd); + if (c instanceof MethodTypeDesc mtd) return methodTypeEntry(mtd); + if (c instanceof DirectMethodHandleDesc dmhd) return methodHandleEntry(dmhd); + if (c instanceof DynamicConstantDesc dcd) return constantDynamicEntry(dcd); + throw new IllegalArgumentException("Illegal type: " + (c == null ? null : c.getClass())); + } + + /** + * {@return An {@link AnnotationConstantValueEntry} describing the provided + * constant} The constant should be an Integer, String, Long, Float, + * Double, ClassDesc (for a Class constant), or MethodTypeDesc (for a MethodType + * constant.) + * + * @param c the constant + */ + default AnnotationConstantValueEntry annotationConstantValueEntry(ConstantDesc c) { + if (c instanceof Integer i) return intEntry(i); + if (c instanceof String s) return utf8Entry(s); + if (c instanceof Long l) return longEntry(l); + if (c instanceof Float f) return floatEntry(f); + if (c instanceof Double d) return doubleEntry(d); + if (c instanceof ClassDesc cd) return utf8Entry(cd); + if (c instanceof MethodTypeDesc mtd) return utf8Entry(mtd); + throw new IllegalArgumentException("Illegal type: " + (c == null ? null : c.getClass())); + } + + /** + * {@return a {@link BootstrapMethodEntry} describing the provided + * bootstrap method and static arguments} + * + * @param methodReference the bootstrap method + * @param arguments the bootstrap arguments + */ + default BootstrapMethodEntry bsmEntry(DirectMethodHandleDesc methodReference, + List arguments) { + return bsmEntry(methodHandleEntry(methodReference), + arguments.stream().map(this::loadableConstantEntry).toList()); + } + + /** + * {@return a {@link BootstrapMethodEntry} describing the provided + * bootstrap method and static arguments} + * + * @param methodReference the bootstrap method + * @param arguments the bootstrap arguments + */ + BootstrapMethodEntry bsmEntry(MethodHandleEntry methodReference, + List arguments); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantValueEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantValueEntry.java new file mode 100644 index 0000000000000..7816db6da4284 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ConstantValueEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ConstantDesc; + +/** + * Models a constant pool entry that can be used as the constant in a + * {@code ConstantValue} attribute; this includes the four primitive constant + * types and {@linkplain String} constants. + */ +public sealed interface ConstantValueEntry extends LoadableConstantEntry + permits DoubleEntry, FloatEntry, IntegerEntry, LongEntry, StringEntry { + + /** + * {@return the constant value} The constant value will be an {@link Integer}, + * {@link Long}, {@link Float}, {@link Double}, or {@link String}. + */ + @Override + ConstantDesc constantValue(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/DoubleEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/DoubleEntry.java new file mode 100644 index 0000000000000..042411365f1f7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/DoubleEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Double_info} constant in the constant pool of a + * classfile. + */ +public sealed interface DoubleEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits AbstractPoolEntry.DoubleEntryImpl { + + /** + * {@return the double value} + */ + double doubleValue(); + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return TypeKind.DoubleType; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/DynamicConstantPoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/DynamicConstantPoolEntry.java new file mode 100644 index 0000000000000..cd4ce92da0c07 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/DynamicConstantPoolEntry.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.BootstrapMethodEntry; + +/** + * Models a dynamic constant pool entry, which is either {@link ConstantDynamicEntry} + * or {@link InvokeDynamicEntry}. + */ +public sealed interface DynamicConstantPoolEntry extends PoolEntry + permits ConstantDynamicEntry, InvokeDynamicEntry { + + /** + * {@return the entry in the bootstrap method table for this constant} + */ + BootstrapMethodEntry bootstrap(); + + /** + * {@return the invocation name and type} + */ + NameAndTypeEntry nameAndType(); + + /** + * {@return the invocation name} + */ + default Utf8Entry name() { + return nameAndType().name(); + } + + /** + * {@return the invocation type} + */ + default Utf8Entry type() { + return nameAndType().type(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/FieldRefEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/FieldRefEntry.java new file mode 100644 index 0000000000000..d8e6bfdd95921 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/FieldRefEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Fieldref_info} constant in the constant pool of a + * classfile. + */ +public sealed interface FieldRefEntry extends MemberRefEntry + permits AbstractPoolEntry.FieldRefEntryImpl { + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/FloatEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/FloatEntry.java new file mode 100644 index 0000000000000..8ac8869cde2d8 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/FloatEntry.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Float_info} constant in the constant pool of a + * classfile. + */ +public sealed interface FloatEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits AbstractPoolEntry.FloatEntryImpl { + + /** + * {@return the float value} + */ + + float floatValue(); + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return TypeKind.FloatType; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/IntegerEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/IntegerEntry.java new file mode 100644 index 0000000000000..3e9856c734541 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/IntegerEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Integer_info} constant in the constant pool of a + * classfile. + */ +public sealed interface IntegerEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits AbstractPoolEntry.IntegerEntryImpl { + + /** + * {@return the integer value} + */ + int intValue(); + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return TypeKind.IntType; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/InterfaceMethodRefEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/InterfaceMethodRefEntry.java new file mode 100644 index 0000000000000..f636958d96518 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/InterfaceMethodRefEntry.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_InterfaceMethodRef_info} constant in the constant pool of a + * classfile. + */ +public sealed interface InterfaceMethodRefEntry + extends MemberRefEntry + permits AbstractPoolEntry.InterfaceMethodRefEntryImpl { + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/InvokeDynamicEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/InvokeDynamicEntry.java new file mode 100644 index 0000000000000..54a9d53b5405b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/InvokeDynamicEntry.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a constant pool entry for a dynamic call site. + */ +public sealed interface InvokeDynamicEntry + extends DynamicConstantPoolEntry + permits AbstractPoolEntry.InvokeDynamicEntryImpl { + + /** + * {@return a symbolic descriptor for the dynamic call site} + */ + default DynamicCallSiteDesc asSymbol() { + return DynamicCallSiteDesc.of(bootstrap().bootstrapMethod().asSymbol(), + name().stringValue(), + MethodTypeDesc.ofDescriptor(type().stringValue()), + bootstrap().arguments().stream() + .map(LoadableConstantEntry::constantValue) + .toArray(ConstantDesc[]::new)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/LoadableConstantEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/LoadableConstantEntry.java new file mode 100644 index 0000000000000..061e0271df853 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/LoadableConstantEntry.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ConstantDesc; +import jdk.internal.classfile.TypeKind; + +/** + * Marker interface for constant pool entries suitable for loading via the + * {@code LDC} instructions. + */ +public sealed interface LoadableConstantEntry extends PoolEntry + permits ClassEntry, ConstantDynamicEntry, ConstantValueEntry, MethodHandleEntry, MethodTypeEntry { + + /** + * {@return the constant described by this entry} + */ + ConstantDesc constantValue(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return TypeKind.ReferenceType; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/LongEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/LongEntry.java new file mode 100644 index 0000000000000..16ed5b16248dd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/LongEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_Long_info} constant in the constant pool of a + * classfile. + */ +public sealed interface LongEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits AbstractPoolEntry.LongEntryImpl { + + /** + * {@return the long value} + */ + long longValue(); + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return TypeKind.LongType; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/MemberRefEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MemberRefEntry.java new file mode 100644 index 0000000000000..86561cb44d1eb --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MemberRefEntry.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a member reference constant in the constant pool of a classfile, + * which includes references to fields, methods, and interface methods. + */ +public sealed interface MemberRefEntry extends PoolEntry + permits FieldRefEntry, InterfaceMethodRefEntry, MethodRefEntry, AbstractPoolEntry.AbstractMemberRefEntry { + /** + * {@return the class in which this member ref lives} + */ + ClassEntry owner(); + + /** + * {@return the name and type of the member} + */ + NameAndTypeEntry nameAndType(); + + /** + * {@return the name of the member} + */ + default Utf8Entry name() { + return nameAndType().name(); + } + + /** + * {@return the type of the member} + */ + default Utf8Entry type() { + return nameAndType().type(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodHandleEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodHandleEntry.java new file mode 100644 index 0000000000000..cde94e6d47328 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodHandleEntry.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_MethodHandle_info} constant in the constant pool of a + * classfile. + */ +public sealed interface MethodHandleEntry + extends LoadableConstantEntry + permits AbstractPoolEntry.MethodHandleEntryImpl { + + @Override + default ConstantDesc constantValue() { + return asSymbol(); + } + + /** + * {@return the reference kind of this method handle {@jvms 4.4.8}} + * @see java.lang.invoke.MethodHandleInfo + */ + int kind(); + + /** + * {@return the constant pool entry describing the method} + */ + MemberRefEntry reference(); + + /** + * {@return a symbolic descriptor for this method handle} + */ + DirectMethodHandleDesc asSymbol(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodRefEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodRefEntry.java new file mode 100644 index 0000000000000..0119140279850 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodRefEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_MethodRef_info} constant in the constant pool of a + * classfile. + */ +public sealed interface MethodRefEntry extends MemberRefEntry + permits AbstractPoolEntry.MethodRefEntryImpl { + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodTypeEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodTypeEntry.java new file mode 100644 index 0000000000000..8e1a256fa743a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/MethodTypeEntry.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_MethodType_info} constant in the constant pool of a + * classfile. + */ +public sealed interface MethodTypeEntry + extends LoadableConstantEntry + permits AbstractPoolEntry.MethodTypeEntryImpl { + + @Override + default ConstantDesc constantValue() { + return asSymbol(); + } + + /** + * {@return the constant pool entry describing the method type} + */ + Utf8Entry descriptor(); + + /** + * {@return a symbolic descriptor for the method type} + */ + default MethodTypeDesc asSymbol() { + return MethodTypeDesc.ofDescriptor(descriptor().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/ModuleEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ModuleEntry.java new file mode 100644 index 0000000000000..890ad9eb9078f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/ModuleEntry.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; + +/** + * Models a {@code CONSTANT_Module_info} constant in the constant pool of a + * classfile. + */ +public sealed interface ModuleEntry extends PoolEntry + permits AbstractPoolEntry.ModuleEntryImpl { + /** + * {@return the name of the module} + */ + Utf8Entry name(); + + /** + * {@return a symbolic descriptor for the module} + */ + ModuleDesc asSymbol(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/NameAndTypeEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/NameAndTypeEntry.java new file mode 100644 index 0000000000000..b6743c620649d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/NameAndTypeEntry.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_NameAndType_info} constant in the constant pool of a + * classfile. + */ +public sealed interface NameAndTypeEntry extends PoolEntry + permits AbstractPoolEntry.NameAndTypeEntryImpl { + + /** + * {@return the field or method name} + */ + Utf8Entry name(); + + /** + * {@return the field or method descriptor} + */ + Utf8Entry type(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/PackageEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/PackageEntry.java new file mode 100644 index 0000000000000..14354e11db7c2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/PackageEntry.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; +import jdk.internal.classfile.java.lang.constant.PackageDesc; + +/** + * Models a {@code CONSTANT_Package_info} constant in the constant pool of a + * classfile. + */ +public sealed interface PackageEntry extends PoolEntry + permits AbstractPoolEntry.PackageEntryImpl { + /** + * {@return the package name} + */ + Utf8Entry name(); + + /** + * {@return a symbolic descriptor for the package name} + */ + PackageDesc asSymbol(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/PoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/PoolEntry.java new file mode 100644 index 0000000000000..0ca960f64b2a3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/PoolEntry.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.WritableElement; + +/** + * Models an entry in the constant pool of a classfile. + */ +public sealed interface PoolEntry extends WritableElement + permits AnnotationConstantValueEntry, DynamicConstantPoolEntry, + LoadableConstantEntry, MemberRefEntry, ModuleEntry, NameAndTypeEntry, + PackageEntry { + + /** + * {@return the constant pool this entry is from} + */ + ConstantPool constantPool(); + + /** + * {@return the constant pool tag that describes the type of this entry} + */ + byte tag(); + + /** + * {@return the index within the constant pool corresponding to this entry} + */ + int index(); + + /** + * {@return the number of constant pool slots this entry consumes} + */ + int width(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/StringEntry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/StringEntry.java new file mode 100644 index 0000000000000..718296012372f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/StringEntry.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_String_info} constant in the constant pool of a + * classfile. + */ +public sealed interface StringEntry + extends ConstantValueEntry + permits AbstractPoolEntry.StringEntryImpl { + /** + * {@return the UTF constant pool entry describing the string contents} + */ + Utf8Entry utf8(); + + /** + * {@return the string value for this entry} + */ + String stringValue(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/constantpool/Utf8Entry.java b/src/java.base/share/classes/jdk/internal/classfile/constantpool/Utf8Entry.java new file mode 100644 index 0000000000000..f437554705e5e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/constantpool/Utf8Entry.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.constantpool; + +import jdk.internal.classfile.impl.AbstractPoolEntry; + +/** + * Models a {@code CONSTANT_UTF8_info} constant in the constant pool of a + * classfile. + */ +public sealed interface Utf8Entry + extends CharSequence, AnnotationConstantValueEntry + permits AbstractPoolEntry.Utf8EntryImpl { + + /** + * {@return the string value for this entry} + */ + String stringValue(); + + /** + * {@return whether this entry describes the same string as the provided string} + * + * @param s the string to compare to + */ + boolean equalsString(String s); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java new file mode 100644 index 0000000000000..7b6e97b3a4b06 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.AttributeMapper; +import jdk.internal.classfile.BufWriter; + +import static jdk.internal.classfile.Classfile.JAVA_1_VERSION; + +public abstract class AbstractAttributeMapper> + implements AttributeMapper { + + private final String name; + private final boolean allowMultiple; + private final int majorVersion; + + protected abstract void writeBody(BufWriter buf, T attr); + + public AbstractAttributeMapper(String name) { + this(name, false); + } + + public AbstractAttributeMapper(String name, + boolean allowMultiple) { + this(name, allowMultiple, JAVA_1_VERSION); + } + + public AbstractAttributeMapper(String name, + int majorVersion) { + this(name, false, majorVersion); + } + + public AbstractAttributeMapper(String name, + boolean allowMultiple, + int majorVersion) { + this.name = name; + this.allowMultiple = allowMultiple; + this.majorVersion = majorVersion; + } + + @Override + public String name() { + return name; + } + + @Override + public void writeAttribute(BufWriter buf, T attr) { + buf.writeIndex(buf.constantPool().utf8Entry(name)); + buf.writeInt(0); + int start = buf.size(); + writeBody(buf, attr); + int written = buf.size() - start; + buf.patchInt(start - 4, 4, written); + } + + @Override + public boolean allowMultiple() { + return allowMultiple; + } + + @Override + public int validSince() { + return majorVersion; + } + + @Override + public String toString() { + return String.format("AttributeMapper[name=%s, allowMultiple=%b, validSince=%d]", + name, allowMultiple, majorVersion); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractBoundLocalVariable.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractBoundLocalVariable.java new file mode 100644 index 0000000000000..df0ffa1bd1989 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractBoundLocalVariable.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public class AbstractBoundLocalVariable + extends AbstractElement { + protected final CodeImpl code; + protected final int offset; + private Utf8Entry nameEntry; + private Utf8Entry secondaryEntry; + + public AbstractBoundLocalVariable(CodeImpl code, int offset) { + this.code = code; + this.offset = offset; + } + + protected int nameIndex() { + return code.classReader.readU2(offset + 4); + } + + public Utf8Entry name() { + if (nameEntry == null) + nameEntry = (Utf8Entry) code.constantPool().entryByIndex(nameIndex()); + return nameEntry; + } + + protected int secondaryIndex() { + return code.classReader.readU2(offset + 6); + } + + protected Utf8Entry secondaryEntry() { + if (secondaryEntry == null) + secondaryEntry = (Utf8Entry) code.constantPool().entryByIndex(secondaryIndex()); + return secondaryEntry; + } + + public Label startScope() { + return code.getLabel(startPc()); + } + + public Label endScope() { + return code.getLabel(startPc() + length()); + } + + public int startPc() { + return code.classReader.readU2(offset); + } + + public int length() { + return code.classReader.readU2(offset+2); + } + + public int slot() { + return code.classReader.readU2(offset + 8); + } + + public boolean writeTo(BufWriter b) { + var lc = ((BufWriterImpl)b).labelContext(); + int startBci = lc.labelToBci(startScope()); + int endBci = lc.labelToBci(endScope()); + if (startBci == -1 || endBci == -1) { + return false; + } + int length = endBci - startBci; + b.writeU2(startBci); + b.writeU2(length); + if (b.canWriteDirect(code.constantPool())) { + b.writeU2(nameIndex()); + b.writeU2(secondaryIndex()); + } + else { + b.writeIndex(name()); + b.writeIndex(secondaryEntry()); + } + b.writeU2(slot()); + return true; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractDirectBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractDirectBuilder.java new file mode 100644 index 0000000000000..6a167dd091a4f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractDirectBuilder.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; + +import jdk.internal.classfile.Attribute; + +public class AbstractDirectBuilder { + protected final SplitConstantPool constantPool; + protected final AttributeHolder attributes = new AttributeHolder(); + protected M original; + + public AbstractDirectBuilder(SplitConstantPool constantPool) { + this.constantPool = constantPool; + } + + public SplitConstantPool constantPool() { + return constantPool; + } + + public Optional original() { + return Optional.ofNullable(original); + } + + public void setOriginal(M original) { + this.original = original; + } + + public void writeAttribute(Attribute a) { + attributes.withAttribute(a); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractElement.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractElement.java new file mode 100644 index 0000000000000..37081ca5b6679 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractElement.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +public abstract class AbstractElement { + public AbstractElement() { } + + public void writeTo(DirectCodeBuilder builder) { + throw new UnsupportedOperationException(); + } + + public void writeTo(DirectClassBuilder builder) { + throw new UnsupportedOperationException(); + } + + public void writeTo(DirectMethodBuilder builder) { + throw new UnsupportedOperationException(); + } + + public void writeTo(DirectFieldBuilder builder) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java new file mode 100644 index 0000000000000..a0879be3efb71 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java @@ -0,0 +1,1342 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.instruction.SwitchCase; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.instruction.ArrayLoadInstruction; +import jdk.internal.classfile.instruction.ArrayStoreInstruction; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.ConvertInstruction; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.IncrementInstruction; +import jdk.internal.classfile.instruction.InvokeDynamicInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.LookupSwitchInstruction; +import jdk.internal.classfile.instruction.MonitorInstruction; +import jdk.internal.classfile.instruction.NewMultiArrayInstruction; +import jdk.internal.classfile.instruction.NewObjectInstruction; +import jdk.internal.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.internal.classfile.instruction.NewReferenceArrayInstruction; +import jdk.internal.classfile.instruction.NopInstruction; +import jdk.internal.classfile.instruction.OperatorInstruction; +import jdk.internal.classfile.instruction.ReturnInstruction; +import jdk.internal.classfile.instruction.StackInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; +import jdk.internal.classfile.instruction.TableSwitchInstruction; +import jdk.internal.classfile.instruction.ThrowInstruction; +import jdk.internal.classfile.instruction.TypeCheckInstruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; + +public abstract sealed class AbstractInstruction + extends AbstractElement + implements Instruction { + + private static final String + FMT_ArgumentConstant = "ArgumentConstant[OP=%s, val=%s]", + FMT_Branch = "Branch[OP=%s]", + FMT_Field = "Field[OP=%s, field=%s.%s:%s]", + FMT_Increment = "Increment[OP=%s, slot=%d, val=%d]", + FMT_Invoke = "Invoke[OP=%s, m=%s.%s%s]", + FMT_InvokeDynamic = "InvokeDynamic[OP=%s, bsm=%s %s]", + FMT_InvokeInterface = "InvokeInterface[OP=%s, m=%s.%s%s]", + FMT_Load = "Load[OP=%s, slot=%d]", + FMT_LoadConstant = "LoadConstant[OP=%s, val=%s]", + FMT_LookupSwitch = "LookupSwitch[OP=%s]", + FMT_NewMultiArray = "NewMultiArray[OP=%s, type=%s[%d]]", + FMT_NewObj = "NewObj[OP=%s, type=%s]", + FMT_NewPrimitiveArray = "NewPrimitiveArray[OP=%s, type=%s]", + FMT_NewRefArray = "NewRefArray[OP=%s, type=%s]", + FMT_Return = "Return[OP=%s]", + FMT_Store = "Store[OP=%s, slot=%d]", + FMT_TableSwitch = "TableSwitch[OP=%s]", + FMT_Throw = "Throw[OP=%s]", + FMT_TypeCheck = "TypeCheck[OP=%s, type=%s]", + FMT_Unbound = "%s[op=%s]"; + + final Opcode op; + final int size; + + @Override + public Opcode opcode() { + return op; + } + + @Override + public int sizeInBytes() { + return size; + } + + public AbstractInstruction(Opcode op, int size) { + this.op = op; + this.size = size; + } + + @Override + public abstract void writeTo(DirectCodeBuilder writer); + + public static abstract sealed class BoundInstruction extends AbstractInstruction { + final CodeImpl code; + final int pos; + + protected BoundInstruction(Opcode op, int size, CodeImpl code, int pos) { + super(op, size); + this.code = code; + this.pos = pos; + } + + protected Label offsetToLabel(int offset) { + return code.getLabel(pos - code.codeStart + offset); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + // Override this if the instruction has any CP references or labels! + code.classReader.copyBytesTo(writer.bytecodesBufWriter, pos, size); + } + } + + public static final class BoundLoadInstruction + extends BoundInstruction implements LoadInstruction { + + public BoundLoadInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public String toString() { + return String.format(FMT_Load, this.opcode(), slot()); + } + + @Override + public int slot() { + return switch (size) { + case 2 -> code.classReader.readU1(pos + 1); + case 4 -> code.classReader.readU2(pos + 2); + default -> throw new IllegalArgumentException("Unexpected op size: " + op.sizeIfFixed() + " -- " + op); + }; + } + + } + + public static final class BoundStoreInstruction + extends BoundInstruction implements StoreInstruction { + + public BoundStoreInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public String toString() { + return String.format(FMT_Store, this.opcode(), slot()); + } + + @Override + public int slot() { + return switch (size) { + case 2 -> code.classReader.readU1(pos + 1); + case 4 -> code.classReader.readU2(pos + 2); + default -> throw new IllegalArgumentException("Unexpected op size: " + size + " -- " + op); + }; + } + + } + + public static final class BoundIncrementInstruction + extends BoundInstruction implements IncrementInstruction { + + public BoundIncrementInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public int slot() { + return size == 6 ? code.classReader.readU2(pos + 2) : code.classReader.readU1(pos + 1); + } + + @Override + public int constant() { + return size == 6 ? code.classReader.readS2(pos + 4) : (byte) code.classReader.readS1(pos + 2); + } + + @Override + public String toString() { + return String.format(FMT_Increment, this.opcode(), slot(), constant()); + } + + } + + public static final class BoundBranchInstruction + extends BoundInstruction implements BranchInstruction { + + public BoundBranchInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public Label target() { + return offsetToLabel(branchByteOffset()); + } + + public int branchByteOffset() { + return size == 3 + ? (int) (short) code.classReader.readU2(pos + 1) + : code.classReader.readInt(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(opcode(), target()); + } + + @Override + public String toString() { + return String.format(FMT_Branch, this.opcode()); + } + + } + + public record SwitchCaseImpl(int caseValue, Label target) + implements SwitchCase { + } + + public static final class BoundLookupSwitchInstruction + extends BoundInstruction implements LookupSwitchInstruction { + + // will always need size, cache everything to there + private final int afterPad; + private final int npairs; + + BoundLookupSwitchInstruction(Opcode op, CodeImpl code, int pos) { + super(op, size(code, code.codeStart, pos), code, pos); + + this.afterPad = pos + 1 + ((4 - ((pos + 1 - code.codeStart) & 3)) & 3); + this.npairs = code.classReader.readInt(afterPad + 4); + } + + static int size(CodeImpl code, int codeStart, int pos) { + int afterPad = pos + 1 + ((4 - ((pos + 1 - codeStart) & 3)) & 3); + int pad = afterPad - (pos + 1); + int npairs = code.classReader.readInt(afterPad + 4); + return 1 + pad + 8 + npairs * 8; + } + + private int defaultOffset() { + return code.classReader.readInt(afterPad); + } + + @Override + public List cases() { + var cases = new SwitchCase[npairs]; + for (int i = 0; i < npairs; ++i) { + int z = afterPad + 8 + 8 * i; + cases[i] = SwitchCase.of(code.classReader.readInt(z), offsetToLabel(code.classReader.readInt(z + 4))); + } + return List.of(cases); + } + + @Override + public Label defaultTarget() { + return offsetToLabel(defaultOffset()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLookupSwitch(defaultTarget(), cases()); + } + + @Override + public String toString() { + return String.format(FMT_LookupSwitch, this.opcode()); + } + + } + + public static final class BoundTableSwitchInstruction + extends BoundInstruction implements TableSwitchInstruction { + + BoundTableSwitchInstruction(Opcode op, CodeImpl code, int pos) { + super(op, size(code, code.codeStart, pos), code, pos); + } + + static int size(CodeImpl code, int codeStart, int pos) { + int ap = pos + 1 + ((4 - ((pos + 1 - codeStart) & 3)) & 3); + int pad = ap - (pos + 1); + int low = code.classReader.readInt(ap + 4); + int high = code.classReader.readInt(ap + 8); + int cnt = high - low + 1; + return 1 + pad + 12 + cnt * 4; + } + + private int afterPadding() { + int p = pos; + return p + 1 + ((4 - ((p + 1 - code.codeStart) & 3)) & 3); + } + + @Override + public Label defaultTarget() { + return offsetToLabel(defaultOffset()); + } + + @Override + public int lowValue() { + return code.classReader.readInt(afterPadding() + 4); + } + + @Override + public int highValue() { + return code.classReader.readInt(afterPadding() + 8); + } + + @Override + public List cases() { + int low = lowValue(); + int high = highValue(); + int defOff = defaultOffset(); + var cases = new ArrayList(high - low + 1); + int z = afterPadding() + 12; + for (int i = lowValue(); i <= high; ++i) { + int off = code.classReader.readInt(z); + if (defOff != off) { + cases.add(SwitchCase.of(i, offsetToLabel(off))); + } + z += 4; + } + return Collections.unmodifiableList(cases); + } + + private int defaultOffset() { + return code.classReader.readInt(afterPadding()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTableSwitch(lowValue(), highValue(), defaultTarget(), cases()); + } + + @Override + public String toString() { + return String.format(FMT_TableSwitch, this.opcode()); + } + + } + + public static final class BoundFieldInstruction + extends BoundInstruction implements FieldInstruction { + + private FieldRefEntry fieldEntry; + + public BoundFieldInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public FieldRefEntry field() { + if (fieldEntry == null) + fieldEntry = (FieldRefEntry) code.classReader.readEntry(pos + 1); + return fieldEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeFieldAccess(op, field()); + } + + @Override + public String toString() { + return String.format(FMT_Field, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + + } + + public static final class BoundInvokeInstruction + extends BoundInstruction implements InvokeInstruction { + MemberRefEntry methodEntry; + + public BoundInvokeInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public MemberRefEntry method() { + if (methodEntry == null) + methodEntry = (MemberRefEntry) code.classReader.readEntry(pos + 1); + return methodEntry; + } + + @Override + public boolean isInterface() { + return method().tag() == Classfile.TAG_INTERFACEMETHODREF; + } + + @Override + public int count() { + return Util.parameterSlots(type().stringValue()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeInvokeNormal(op, method()); + } + + @Override + public String toString() { + return String.format(FMT_Invoke, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + + } + + public static final class BoundInvokeInterfaceInstruction + extends BoundInstruction implements InvokeInstruction { + InterfaceMethodRefEntry methodEntry; + + public BoundInvokeInterfaceInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public MemberRefEntry method() { + if (methodEntry == null) + methodEntry = (InterfaceMethodRefEntry) code.classReader.readEntry(pos + 1); + return methodEntry; + } + + @Override + public int count() { + return code.classReader.readU1(pos + 3); + } + + @Override + public boolean isInterface() { + return true; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeInvokeInterface(op, (InterfaceMethodRefEntry) method(), count()); + } + + @Override + public String toString() { + return String.format(FMT_InvokeInterface, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + + } + + public static final class BoundInvokeDynamicInstruction + extends BoundInstruction implements InvokeDynamicInstruction { + InvokeDynamicEntry indyEntry; + + BoundInvokeDynamicInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public InvokeDynamicEntry invokedynamic() { + if (indyEntry == null) + indyEntry = (InvokeDynamicEntry) code.classReader.readEntry(pos + 1); + return indyEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeInvokeDynamic(invokedynamic()); + } + + @Override + public String toString() { + return String.format(FMT_InvokeDynamic, this.opcode(), bootstrapMethod(), bootstrapArgs()); + } + + } + + public static final class BoundNewObjectInstruction + extends BoundInstruction implements NewObjectInstruction { + ClassEntry classEntry; + + BoundNewObjectInstruction(CodeImpl code, int pos) { + super(Opcode.NEW, Opcode.NEW.sizeIfFixed(), code, pos); + } + + @Override + public ClassEntry className() { + if (classEntry == null) + classEntry = code.classReader.readClassEntry(pos + 1); + return classEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeNewObject(className()); + } + + @Override + public String toString() { + return String.format(FMT_NewObj, this.opcode(), className().asInternalName()); + } + + } + + public static final class BoundNewPrimitiveArrayInstruction + extends BoundInstruction implements NewPrimitiveArrayInstruction { + + public BoundNewPrimitiveArrayInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public TypeKind typeKind() { + return TypeKind.fromNewArrayCode(code.classReader.readU1(pos + 1)); + } + + @Override + public String toString() { + return String.format(FMT_NewPrimitiveArray, this.opcode(), typeKind()); + } + + } + + public static final class BoundNewReferenceArrayInstruction + extends BoundInstruction implements NewReferenceArrayInstruction { + + public BoundNewReferenceArrayInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public ClassEntry componentType() { + return code.classReader.readClassEntry(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeNewReferenceArray(componentType()); + } + + @Override + public String toString() { + return String.format(FMT_NewRefArray, this.opcode(), componentType().asInternalName()); + } + } + + public static final class BoundNewMultidimensionalArrayInstruction + extends BoundInstruction implements NewMultiArrayInstruction { + + public BoundNewMultidimensionalArrayInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public int dimensions() { + return code.classReader.readU1(pos + 3); + } + + @Override + public ClassEntry arrayType() { + return code.classReader.readClassEntry(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeNewMultidimensionalArray(dimensions(), arrayType()); + } + + @Override + public String toString() { + return String.format(FMT_NewMultiArray, this.opcode(), arrayType().asInternalName(), dimensions()); + } + + } + + public static final class BoundTypeCheckInstruction + extends BoundInstruction implements TypeCheckInstruction { + ClassEntry typeEntry; + + public BoundTypeCheckInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public ClassEntry type() { + if (typeEntry == null) + typeEntry = code.classReader.readClassEntry(pos + 1); + return typeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeTypeCheck(op, type()); + } + + @Override + public String toString() { + return String.format(FMT_TypeCheck, this.opcode(), type().asInternalName()); + } + + } + + public static final class BoundArgumentConstantInstruction + extends BoundInstruction implements ConstantInstruction.ArgumentConstantInstruction { + + public BoundArgumentConstantInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public Integer constantValue() { + return constantInt(); + } + + public int constantInt() { + return size == 3 ? code.classReader.readS2(pos + 1) : code.classReader.readS1(pos + 1); + } + + @Override + public String toString() { + return String.format(FMT_ArgumentConstant, this.opcode(), constantValue()); + } + + } + + public static final class BoundLoadConstantInstruction + extends BoundInstruction implements ConstantInstruction.LoadConstantInstruction { + + public BoundLoadConstantInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public LoadableConstantEntry constantEntry() { + return (LoadableConstantEntry) + code.classReader.entryByIndex(op == Opcode.LDC + ? code.classReader.readU1(pos + 1) + : code.classReader.readU2(pos + 1)); + } + + @Override + public ConstantDesc constantValue() { + return constantEntry().constantValue(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeLoadConstant(op, constantEntry()); + } + + @Override + public String toString() { + return String.format(FMT_LoadConstant, this.opcode(), constantValue()); + } + + } + + public static abstract sealed class UnboundInstruction extends AbstractInstruction { + + UnboundInstruction(Opcode op) { + super(op, op.sizeIfFixed()); + } + + @Override + // Override this if there is anything more that just the bytecode + public void writeTo(DirectCodeBuilder writer) { + writer.writeBytecode(op); + } + + @Override + public String toString() { + return String.format(FMT_Unbound, this.getClass().getSimpleName(), op); + } + } + + public static final class UnboundLoadInstruction + extends UnboundInstruction implements LoadInstruction { + final int slot; + + public UnboundLoadInstruction(Opcode op, int slot) { + super(op); + this.slot = slot; + } + + @Override + public int slot() { + return slot; + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLoad(op, slot); + } + + @Override + public String toString() { + return String.format(FMT_Load, this.opcode(), slot()); + } + + } + + public static final class UnboundStoreInstruction + extends UnboundInstruction implements StoreInstruction { + final int slot; + + public UnboundStoreInstruction(Opcode op, int slot) { + super(op); + this.slot = slot; + } + + @Override + public int slot() { + return slot; + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeStore(op, slot); + } + + @Override + public String toString() { + return String.format(FMT_Store, this.opcode(), slot()); + } + + } + + public static final class UnboundIncrementInstruction + extends UnboundInstruction implements IncrementInstruction { + final int slot; + final int constant; + + public UnboundIncrementInstruction(int slot, int constant) { + super(slot <= 255 && constant < 128 && constant > -127 + ? Opcode.IINC + : Opcode.IINC_W); + this.slot = slot; + this.constant = constant; + } + + @Override + public int slot() { + return slot; + } + + @Override + public int constant() { + return 0; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeIncrement(slot, constant); + } + + @Override + public String toString() { + return String.format(FMT_Increment, this.opcode(), slot(), constant()); + } + } + + public static final class UnboundBranchInstruction + extends UnboundInstruction implements BranchInstruction { + final Label target; + + public UnboundBranchInstruction(Opcode op, Label target) { + super(op); + this.target = target; + } + + @Override + public Label target() { + return target; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(op, target); + } + + @Override + public String toString() { + return String.format(FMT_Branch, this.opcode()); + } + } + + public static final class UnboundLookupSwitchInstruction + extends UnboundInstruction implements LookupSwitchInstruction { + + private final Label defaultTarget; + private final List cases; + + public UnboundLookupSwitchInstruction(Label defaultTarget, List cases) { + super(Opcode.LOOKUPSWITCH); + this.defaultTarget = defaultTarget; + this.cases = List.copyOf(cases); + } + + @Override + public List cases() { + return cases; + } + + @Override + public Label defaultTarget() { + return defaultTarget; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLookupSwitch(defaultTarget, cases); + } + + @Override + public String toString() { + return String.format(FMT_LookupSwitch, this.opcode()); + } + } + + public static final class UnboundTableSwitchInstruction + extends UnboundInstruction implements TableSwitchInstruction { + + private final int lowValue, highValue; + private final Label defaultTarget; + private final List cases; + + public UnboundTableSwitchInstruction(int lowValue, int highValue, Label defaultTarget, List cases) { + super(Opcode.TABLESWITCH); + this.lowValue = lowValue; + this.highValue = highValue; + this.defaultTarget = defaultTarget; + this.cases = List.copyOf(cases); + } + + @Override + public int lowValue() { + return lowValue; + } + + @Override + public int highValue() { + return highValue; + } + + @Override + public Label defaultTarget() { + return defaultTarget; + } + + @Override + public List cases() { + return cases; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTableSwitch(lowValue, highValue, defaultTarget, cases); + } + + @Override + public String toString() { + return String.format(FMT_TableSwitch, this.opcode()); + } + } + + public static final class UnboundReturnInstruction + extends UnboundInstruction implements ReturnInstruction { + + public UnboundReturnInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public String toString() { + return String.format(FMT_Return, this.opcode()); + } + + } + + public static final class UnboundThrowInstruction + extends UnboundInstruction implements ThrowInstruction { + + public UnboundThrowInstruction() { + super(Opcode.ATHROW); + } + + @Override + public String toString() { + return String.format(FMT_Throw, this.opcode()); + } + + } + + public static final class UnboundFieldInstruction + extends UnboundInstruction implements FieldInstruction { + final FieldRefEntry fieldEntry; + + public UnboundFieldInstruction(Opcode op, + FieldRefEntry fieldEntry) { + super(op); + this.fieldEntry = fieldEntry; + } + + @Override + public FieldRefEntry field() { + return fieldEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeFieldAccess(op, fieldEntry); + } + + @Override + public String toString() { + return String.format(FMT_Field, this.opcode(), this.owner().asInternalName(), this.name().stringValue(), this.type().stringValue()); + } + } + + public static final class UnboundInvokeInstruction + extends UnboundInstruction implements InvokeInstruction { + final MemberRefEntry methodEntry; + + public UnboundInvokeInstruction(Opcode op, MemberRefEntry methodEntry) { + super(op); + this.methodEntry = methodEntry; + } + + @Override + public MemberRefEntry method() { + return methodEntry; + } + + @Override + public boolean isInterface() { + return op == Opcode.INVOKEINTERFACE || methodEntry instanceof InterfaceMethodRefEntry; + } + + @Override + public int count() { + return op == Opcode.INVOKEINTERFACE + ? Util.parameterSlots(methodEntry.nameAndType().type().stringValue()) + 1 + : 0; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (op == Opcode.INVOKEINTERFACE) + writer.writeInvokeInterface(op, (InterfaceMethodRefEntry) method(), count()); + else + writer.writeInvokeNormal(op, method()); + } + + @Override + public String toString() { + return String.format(FMT_Invoke, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + } + + public static final class UnboundInvokeDynamicInstruction + extends UnboundInstruction implements InvokeDynamicInstruction { + final InvokeDynamicEntry indyEntry; + + public UnboundInvokeDynamicInstruction(InvokeDynamicEntry indyEntry) { + super(Opcode.INVOKEDYNAMIC); + this.indyEntry = indyEntry; + } + + @Override + public InvokeDynamicEntry invokedynamic() { + return indyEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeInvokeDynamic(invokedynamic()); + } + + @Override + public String toString() { + return String.format(FMT_InvokeDynamic, this.opcode(), bootstrapMethod(), bootstrapArgs()); + } + } + + public static final class UnboundNewObjectInstruction + extends UnboundInstruction implements NewObjectInstruction { + final ClassEntry classEntry; + + public UnboundNewObjectInstruction(ClassEntry classEntry) { + super(Opcode.NEW); + this.classEntry = classEntry; + } + + @Override + public ClassEntry className() { + return classEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewObject(className()); + } + + @Override + public String toString() { + return String.format(FMT_NewObj, this.opcode(), className().asInternalName()); + } + } + + public static final class UnboundNewPrimitiveArrayInstruction + extends UnboundInstruction implements NewPrimitiveArrayInstruction { + final TypeKind typeKind; + + public UnboundNewPrimitiveArrayInstruction(TypeKind typeKind) { + super(Opcode.NEWARRAY); + this.typeKind = typeKind; + } + + @Override + public TypeKind typeKind() { + return typeKind; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewPrimitiveArray(typeKind.newarraycode()); + } + + @Override + public String toString() { + return String.format(FMT_NewPrimitiveArray, this.opcode(), typeKind()); + } + } + + public static final class UnboundNewReferenceArrayInstruction + extends UnboundInstruction implements NewReferenceArrayInstruction { + final ClassEntry componentTypeEntry; + + public UnboundNewReferenceArrayInstruction(ClassEntry componentTypeEntry) { + super(Opcode.ANEWARRAY); + this.componentTypeEntry = componentTypeEntry; + } + + @Override + public ClassEntry componentType() { + return componentTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewReferenceArray(componentType()); + } + + @Override + public String toString() { + return String.format(FMT_NewRefArray, this.opcode(), componentType().asInternalName()); + } + } + + public static final class UnboundNewMultidimensionalArrayInstruction + extends UnboundInstruction implements NewMultiArrayInstruction { + final ClassEntry arrayTypeEntry; + final int dimensions; + + public UnboundNewMultidimensionalArrayInstruction(ClassEntry arrayTypeEntry, + int dimensions) { + super(Opcode.MULTIANEWARRAY); + this.arrayTypeEntry = arrayTypeEntry; + this.dimensions = dimensions; + } + + @Override + public int dimensions() { + return dimensions; + } + + @Override + public ClassEntry arrayType() { + return arrayTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewMultidimensionalArray(dimensions(), arrayType()); + } + + @Override + public String toString() { + return String.format(FMT_NewMultiArray, this.opcode(), arrayType().asInternalName(), dimensions()); + } + + } + + public static final class UnboundArrayLoadInstruction + extends UnboundInstruction implements ArrayLoadInstruction { + + public UnboundArrayLoadInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + } + + public static final class UnboundArrayStoreInstruction + extends UnboundInstruction implements ArrayStoreInstruction { + + public UnboundArrayStoreInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + } + + public static final class UnboundTypeCheckInstruction + extends UnboundInstruction implements TypeCheckInstruction { + final ClassEntry typeEntry; + + public UnboundTypeCheckInstruction(Opcode op, ClassEntry typeEntry) { + super(op); + this.typeEntry = typeEntry; + } + + @Override + public ClassEntry type() { + return typeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTypeCheck(op, type()); + } + + @Override + public String toString() { + return String.format(FMT_TypeCheck, this.opcode(), type().asInternalName()); + } + } + + public static final class UnboundStackInstruction + extends UnboundInstruction implements StackInstruction { + + public UnboundStackInstruction(Opcode op) { + super(op); + } + + } + + public static final class UnboundConvertInstruction + extends UnboundInstruction implements ConvertInstruction { + + public UnboundConvertInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind fromType() { + return op.primaryTypeKind(); + } + + @Override + public TypeKind toType() { + return op.secondaryTypeKind(); + } + } + + public static final class UnboundOperatorInstruction + extends UnboundInstruction implements OperatorInstruction { + + public UnboundOperatorInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + } + + public static final class UnboundIntrinsicConstantInstruction + extends UnboundInstruction implements ConstantInstruction.IntrinsicConstantInstruction { + final ConstantDesc constant; + + public UnboundIntrinsicConstantInstruction(Opcode op) { + super(op); + constant = op.constantValue(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + super.writeTo(writer); + } + + @Override + public ConstantDesc constantValue() { + return constant; + } + } + + public static final class UnboundArgumentConstantInstruction + extends UnboundInstruction implements ConstantInstruction.ArgumentConstantInstruction { + final int value; + + public UnboundArgumentConstantInstruction(Opcode op, int value) { + super(op); + this.value = value; + } + + @Override + public Integer constantValue() { + return value; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeArgumentConstant(op, value); + } + + @Override + public String toString() { + return String.format(FMT_ArgumentConstant, this.opcode(), constantValue()); + } + } + + public static final class UnboundLoadConstantInstruction + extends UnboundInstruction implements ConstantInstruction.LoadConstantInstruction { + final LoadableConstantEntry constant; + + public UnboundLoadConstantInstruction(Opcode op, LoadableConstantEntry constant) { + super(op); + this.constant = constant; + } + + @Override + public LoadableConstantEntry constantEntry() { + return constant; + } + + @Override + public ConstantDesc constantValue() { + return constant.constantValue(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLoadConstant(op, constant); + } + + @Override + public String toString() { + return String.format(FMT_LoadConstant, this.opcode(), constantValue()); + } + } + + public static final class UnboundMonitorInstruction + extends UnboundInstruction implements MonitorInstruction { + + public UnboundMonitorInstruction(Opcode op) { + super(op); + } + + } + + public static final class UnboundNopInstruction + extends UnboundInstruction implements NopInstruction { + + public UnboundNopInstruction() { + super(Opcode.NOP); + } + + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java new file mode 100644 index 0000000000000..d2efb57ac5e3f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java @@ -0,0 +1,1151 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.constantpool.DoubleEntry; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.FloatEntry; +import jdk.internal.classfile.constantpool.IntegerEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LongEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.MethodRefEntry; +import jdk.internal.classfile.constantpool.MethodTypeEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.StringEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; + +public abstract sealed class AbstractPoolEntry { + /* + Invariant: a {CP,BSM} entry for pool P refer only to {CP,BSM} entries + from P or P's parent. This is enforced by the various xxxEntry methods + in SplitConstantPool. As a result, code in this file can use writeU2 + instead of writeIndex. + + Cloning of entries may be a no-op if the entry is already on the right pool + (which implies that the referenced entries will also be on the right pool.) + */ + + private static final int TAG_SMEAR = 0x13C4B2D1; + private static final int INT_PHI = 0x9E3779B9; + + public static int hash1(int tag, int x1) { + return phiMix(tag * TAG_SMEAR + x1); + } + + public static int hash2(int tag, int x1, int x2) { + return phiMix(tag * TAG_SMEAR + x1 + 31*x2); + } + + // Ensure that hash is never zero + public static int hashString(int stringHash) { + return phiMix(stringHash | (1 << 30)); + } + + public static int phiMix(int x) { + int h = x * INT_PHI; + return h ^ (h >> 16); + } + + public static Utf8Entry rawUtf8EntryFromStandardAttributeName(String name) { + //assuming standard attribute names are all US_ASCII + var raw = name.getBytes(StandardCharsets.US_ASCII); + return new Utf8EntryImpl(null, 0, raw, 0, raw.length); + } + + @SuppressWarnings("unchecked") + public static T maybeClone(ConstantPoolBuilder cp, T entry) { + return (T)((AbstractPoolEntry)entry).clone(cp); + } + + final ConstantPool constantPool; + public final byte tag; + private final int index; + private final int hash; + + private AbstractPoolEntry(ConstantPool constantPool, int tag, int index, int hash) { + this.tag = (byte) tag; + this.index = index; + this.hash = hash; + this.constantPool = constantPool; + } + + public ConstantPool constantPool() { return constantPool; } + + public int index() { return index; } + + @Override + public int hashCode() { + return hash; + } + + public byte tag() { + return tag; + } + + public int width() { + return (tag == Classfile.TAG_LONG || tag == Classfile.TAG_DOUBLE) ? 2 : 1; + } + + abstract PoolEntry clone(ConstantPoolBuilder cp); + + public static final class Utf8EntryImpl extends AbstractPoolEntry implements Utf8Entry { + // Processing UTF8 from the constant pool is one of the more expensive + // operations, and often, we don't actually need access to the constant + // as a string. So there are multiple layers of laziness in UTF8 + // constants. In the first stage, all we do is record the range of + // bytes in the classfile. If the size or hashCode is needed, then we + // process the raw bytes into a byte[] or char[], but do not inflate + // a String. If a string is needed, it too is inflated lazily. + // If we construct a Utf8Entry from a string, we generate the encoding + // at write time. + + enum State { RAW, BYTE, CHAR, STRING } + + private State state; + private final byte[] rawBytes; // null if initialized directly from a string + private final int offset; + private final int rawLen; + // Set in any state other than RAW + private int hash; + private int charLen; + // Set in CHAR state + private char[] chars; + // Only set in STRING state + private String stringValue; + + Utf8EntryImpl(ConstantPool cpm, int index, + byte[] rawBytes, int offset, int rawLen) { + super(cpm, Classfile.TAG_UTF8, index, 0); + this.rawBytes = rawBytes; + this.offset = offset; + this.rawLen = rawLen; + this.state = State.RAW; + } + + Utf8EntryImpl(ConstantPool cpm, int index, String s) { + super(cpm, Classfile.TAG_UTF8, index, 0); + this.rawBytes = null; + this.offset = 0; + this.rawLen = 0; + this.state = State.STRING; + this.stringValue = s; + this.charLen = s.length(); + this.hash = hashString(s.hashCode()); + } + + Utf8EntryImpl(ConstantPool cpm, int index, Utf8EntryImpl u) { + super(cpm, Classfile.TAG_UTF8, index, 0); + this.rawBytes = u.rawBytes; + this.offset = u.offset; + this.rawLen = u.rawLen; + this.state = u.state; + this.hash = u.hash; + this.charLen = u.charLen; + this.chars = u.chars; + this.stringValue = u.stringValue; + } + + /** + * {@jvms 4.4.7} String content is encoded in modified UTF-8. + * + * Modified UTF-8 strings are encoded so that code point sequences that + * contain only non-null ASCII characters can be represented using only 1 + * byte per code point, but all code points in the Unicode codespace can be + * represented. + * + * Modified UTF-8 strings are not null-terminated. + * + * Code points in the range '\u0001' to '\u007F' are represented by a single + * byte. + * + * The null code point ('\u0000') and code points in the range '\u0080' to + * '\u07FF' are represented by a pair of bytes. + * + * Code points in the range '\u0800' to '\uFFFF' are represented by 3 bytes. + * + * Characters with code points above U+FFFF (so-called supplementary + * characters) are represented by separately encoding the two surrogate code + * units of their UTF-16 representation. Each of the surrogate code units is + * represented by three bytes. This means supplementary characters are + * represented by six bytes. + * + * The bytes of multibyte characters are stored in the class file in + * big-endian (high byte first) order. + * + * There are two differences between this format and the "standard" UTF-8 + * format. First, the null character (char)0 is encoded using the 2-byte + * format rather than the 1-byte format, so that modified UTF-8 strings + * never have embedded nulls. Second, only the 1-byte, 2-byte, and 3-byte + * formats of standard UTF-8 are used. The Java Virtual Machine does not + * recognize the four-byte format of standard UTF-8; it uses its own + * two-times-three-byte format instead. + */ + private void inflate() { + int hash = 0; + boolean foundHigh = false; + + int px = offset; + int utfend = px + rawLen; + while (px < utfend) { + int c = (int) rawBytes[px] & 0xff; + if (c > 127) { + foundHigh = true; + break; + } + hash = 31 * hash + c; + px++; + } + + if (!foundHigh) { + this.hash = hashString(hash); + charLen = rawLen; + state = State.BYTE; + } + else { + char[] chararr = new char[rawLen]; + int chararr_count = 0; + // Inflate prefix of bytes to characters + for (int i = offset; i < px; i++) { + int c = (int) rawBytes[i] & 0xff; + chararr[chararr_count++] = (char) c; + } + while (px < utfend) { + int c = (int) rawBytes[px] & 0xff; + switch (c >> 4) { + case 0, 1, 2, 3, 4, 5, 6, 7: { + // 0xxx xxxx + px++; + chararr[chararr_count++] = (char) c; + hash = 31 * hash + c; + break; + } + case 12, 13: { + // 110x xxxx 10xx xxxx + px += 2; + if (px > utfend) { + throw new CpException("malformed input: partial character at end"); + } + int char2 = rawBytes[px - 1]; + if ((char2 & 0xC0) != 0x80) { + throw new CpException("malformed input around byte " + px); + } + char v = (char) (((c & 0x1F) << 6) | (char2 & 0x3F)); + chararr[chararr_count++] = v; + hash = 31 * hash + v; + break; + } + case 14: { + // 1110 xxxx 10xx xxxx 10xx xxxx + px += 3; + if (px > utfend) { + throw new CpException("malformed input: partial character at end"); + } + int char2 = rawBytes[px - 2]; + int char3 = rawBytes[px - 1]; + if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) { + throw new CpException("malformed input around byte " + (px - 1)); + } + char v = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F)); + chararr[chararr_count++] = v; + hash = 31 * hash + v; + break; + } + default: + // 10xx xxxx, 1111 xxxx + throw new CpException("malformed input around byte " + px); + } + } + this.hash = hashString(hash); + charLen = chararr_count; + this.chars = chararr; + state = State.CHAR; + } + + } + + @Override + public Utf8EntryImpl clone(ConstantPoolBuilder cp) { + if (cp.canWriteDirect(constantPool)) + return this; + return (state == State.STRING && rawBytes == null) + ? (Utf8EntryImpl) cp.utf8Entry(stringValue) + : ((SplitConstantPool) cp).maybeCloneUtf8Entry(this); + } + + @Override + public int hashCode() { + if (state == State.RAW) + inflate(); + return hash; + } + + @Override + public String toString() { + if (state == State.RAW) + inflate(); + if (state != State.STRING) { + stringValue = (chars != null) + ? new String(chars, 0, charLen) + : new String(rawBytes, offset, charLen, StandardCharsets.UTF_8); + state = State.STRING; + } + return stringValue; + } + + @Override + public String stringValue() { + return toString(); + } + + @Override + public ConstantDesc constantValue() { + return stringValue(); + } + + @Override + public int length() { + if (state == State.RAW) + inflate(); + return charLen; + } + + @Override + public char charAt(int index) { + if (state == State.STRING) + return stringValue.charAt(index); + if (state == State.RAW) + inflate(); + return (chars != null) + ? chars[index] + : (char) rawBytes[index + offset]; + } + + @Override + public CharSequence subSequence(int start, int end) { + return toString().subSequence(start, end); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof Utf8EntryImpl u) { + return equalsUtf8(u); + } + return false; + } + + public boolean equalsUtf8(Utf8EntryImpl u) { + if (hashCode() != u.hashCode() + || length() != u.length()) + return false; + if (rawBytes != null && u.rawBytes != null) + return Arrays.equals(rawBytes, offset, offset + rawLen, + u.rawBytes, u.offset, u.offset + u.rawLen); + else if ((state == State.STRING && u.state == State.STRING)) + return stringValue.equals(u.stringValue); + else + return stringValue().equals(u.stringValue()); + } + + @Override + public boolean equalsString(String s) { + if (state == State.RAW) + inflate(); + switch (state) { + case STRING: + return stringValue.equals(s); + case CHAR: + if (charLen != s.length() || hash != hashString(s.hashCode())) + return false; + for (int i=0; i 65535) { + throw new IllegalArgumentException("string too long"); + } + pool.writeU1(tag); + pool.writeU2(charLen); + for (int i = 0; i < charLen; ++i) { + char c = stringValue.charAt(i); + if (c >= '\001' && c <= '\177') { + // Optimistic writing -- hope everything is bytes + // If not, we bail out, and alternate path patches the length + pool.writeU1((byte) c); + } + else { + int charLength = stringValue.length(); + int byteLength = i; + char c1; + for (int j = i; j < charLength; ++j) { + c1 = (stringValue).charAt(j); + if (c1 >= '\001' && c1 <= '\177') { + byteLength++; + } else if (c1 > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + if (byteLength > 65535) { + throw new IllegalArgumentException(); + } + int byteLengthFinal = byteLength; + pool.patchInt(pool.size() - i - 2, 2, byteLengthFinal); + for (int j = i; j < charLength; ++j) { + c1 = (stringValue).charAt(j); + if (c1 >= '\001' && c1 <= '\177') { + pool.writeU1((byte) c1); + } else if (c1 > '\u07FF') { + pool.writeU1((byte) (0xE0 | c1 >> 12 & 0xF)); + pool.writeU1((byte) (0x80 | c1 >> 6 & 0x3F)); + pool.writeU1((byte) (0x80 | c1 & 0x3F)); + } else { + pool.writeU1((byte) (0xC0 | c1 >> 6 & 0x1F)); + pool.writeU1((byte) (0x80 | c1 & 0x3F)); + } + } + break; + } + } + } + } + } + + static abstract sealed class AbstractRefEntry extends AbstractPoolEntry { + protected final T ref1; + + public AbstractRefEntry(ConstantPool constantPool, int tag, int index, T ref1) { + super(constantPool, tag, index, hash1(tag, ref1.index())); + this.ref1 = ref1; + } + + public T ref1() { + return ref1; + } + + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU2(ref1.index()); + } + + @Override + public String toString() { + return tag() + " " + ref1(); + } + } + + static abstract sealed class AbstractRefsEntry + extends AbstractPoolEntry { + protected final T ref1; + protected final U ref2; + + public AbstractRefsEntry(ConstantPool constantPool, int tag, int index, T ref1, U ref2) { + super(constantPool, tag, index, hash2(tag, ref1.index(), ref2.index())); + this.ref1 = ref1; + this.ref2 = ref2; + } + + public T ref1() { + return ref1; + } + + public U ref2() { + return ref2; + } + + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU2(ref1.index()); + pool.writeU2(ref2.index()); + } + + @Override + public String toString() { + return tag() + " " + ref1 + "-" + ref2; + } + } + + static abstract sealed class AbstractNamedEntry extends AbstractRefEntry { + + public AbstractNamedEntry(ConstantPool constantPool, int tag, int index, Utf8EntryImpl ref1) { + super(constantPool, tag, index, ref1); + } + + public Utf8Entry name() { + return ref1; + } + + public String asInternalName() { + return ref1.stringValue(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { return true; } + if (o instanceof AbstractNamedEntry ne) { + return tag == ne.tag() && name().equals(ref1()); + } + return false; + } + } + + public static final class ClassEntryImpl extends AbstractNamedEntry implements ClassEntry { + + ClassEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) { + super(cpm, Classfile.TAG_CLASS, index, name); + } + + @Override + public ClassEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.classEntry(ref1); + } + + @Override + public ClassDesc asSymbol() { + return Util.toClassDesc(asInternalName()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ClassEntryImpl cce) { + return cce.name().equals(this.name()); + } else if (o instanceof ClassEntry c) { + return c.asSymbol().equals(this.asSymbol()); + } + return false; + } + } + + public static final class PackageEntryImpl extends AbstractNamedEntry implements PackageEntry { + + PackageEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) { + super(cpm, Classfile.TAG_PACKAGE, index, name); + } + + @Override + public PackageEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.packageEntry(ref1); + } + + @Override + public PackageDesc asSymbol() { + return PackageDesc.ofInternalName(asInternalName()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof PackageEntry p) { + return name().equals(p.name()); + } + return false; + } + } + + public static final class ModuleEntryImpl extends AbstractNamedEntry implements ModuleEntry { + + ModuleEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) { + super(cpm, Classfile.TAG_MODULE, index, name); + } + + @Override + public ModuleEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.moduleEntry(ref1); + } + + @Override + public ModuleDesc asSymbol() { + return ModuleDesc.of(asInternalName()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ModuleEntryImpl m) { + return name().equals(m.name()); + } + return false; + } + } + + public static final class NameAndTypeEntryImpl extends AbstractRefsEntry + implements NameAndTypeEntry { + + NameAndTypeEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name, Utf8EntryImpl type) { + super(cpm, Classfile.TAG_NAMEANDTYPE, index, name, type); + } + + @Override + public Utf8Entry name() { + return ref1; + } + + @Override + public Utf8Entry type() { + return ref2; + } + + @Override + public NameAndTypeEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.nameAndTypeEntry(ref1, ref2); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof NameAndTypeEntryImpl nat) { + return name().equals(nat.name()) && type().equals(nat.type()); + } + return false; + } + } + + public static abstract sealed class AbstractMemberRefEntry + extends AbstractRefsEntry + implements MemberRefEntry { + + AbstractMemberRefEntry(ConstantPool cpm, int tag, int index, ClassEntryImpl owner, + NameAndTypeEntryImpl nameAndType) { + super(cpm, tag, index, owner, nameAndType); + } + + @Override + public ClassEntryImpl owner() { + return ref1; + } + + @Override + public NameAndTypeEntryImpl nameAndType() { + return ref2; + } + + @Override + public String toString() { + return tag() + " " + owner().asInternalName() + "." + nameAndType().name().stringValue() + + "-" + nameAndType().type().stringValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof AbstractMemberRefEntry m) { + return tag == m.tag() + && owner().equals(m.owner()) + && nameAndType().equals(m.nameAndType()); + } + return false; + } + } + + public static final class FieldRefEntryImpl extends AbstractMemberRefEntry implements FieldRefEntry { + + FieldRefEntryImpl(ConstantPool cpm, int index, + ClassEntryImpl owner, NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_FIELDREF, index, owner, nameAndType); + } + + @Override + public FieldRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.fieldRefEntry(ref1, ref2); + } + } + + public static final class MethodRefEntryImpl extends AbstractMemberRefEntry implements MethodRefEntry { + + MethodRefEntryImpl(ConstantPool cpm, int index, + ClassEntryImpl owner, NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_METHODREF, index, owner, nameAndType); + } + + @Override + public MethodRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodRefEntry(ref1, ref2); + } + } + + public static final class InterfaceMethodRefEntryImpl extends AbstractMemberRefEntry implements InterfaceMethodRefEntry { + + InterfaceMethodRefEntryImpl(ConstantPool cpm, int index, ClassEntryImpl owner, + NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_INTERFACEMETHODREF, index, owner, nameAndType); + } + + @Override + public InterfaceMethodRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.interfaceMethodRefEntry(ref1, ref2); + } + } + + public static abstract sealed class AbstractDynamicConstantPoolEntry extends AbstractPoolEntry { + + private final int bsmIndex; + private BootstrapMethodEntryImpl bootstrapMethod; + private final NameAndTypeEntryImpl nameAndType; + + AbstractDynamicConstantPoolEntry(ConstantPool cpm, int tag, int index, int hash, BootstrapMethodEntryImpl bootstrapMethod, + NameAndTypeEntryImpl nameAndType) { + super(cpm, tag, index, hash); + this.bsmIndex = bootstrapMethod.bsmIndex(); + this.bootstrapMethod = bootstrapMethod; + this.nameAndType = nameAndType; + } + + AbstractDynamicConstantPoolEntry(ConstantPool cpm, int tag, int index, int hash, int bsmIndex, + NameAndTypeEntryImpl nameAndType) { + super(cpm, tag, index, hash); + this.bsmIndex = bsmIndex; + this.bootstrapMethod = null; + this.nameAndType = nameAndType; + } + + /** + * @return the bootstrapMethod + */ + public BootstrapMethodEntryImpl bootstrap() { + if (bootstrapMethod == null) { + bootstrapMethod = (BootstrapMethodEntryImpl) constantPool.bootstrapMethodEntry(bsmIndex); + } + return bootstrapMethod; + } + + /** + * @return the nameAndType + */ + public NameAndTypeEntryImpl nameAndType() { + return nameAndType; + } + + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU2(bsmIndex); + pool.writeU2(nameAndType.index()); + } + + @Override + public String toString() { + return tag() + " " + bootstrap() + "." + nameAndType().name().stringValue() + + "-" + nameAndType().type().stringValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof AbstractDynamicConstantPoolEntry d) { + return this.tag() == d.tag() + && bootstrap().equals(d.bootstrap()) + && nameAndType.equals(d.nameAndType()); + } + return false; + } + } + + public static final class InvokeDynamicEntryImpl + extends AbstractDynamicConstantPoolEntry + implements InvokeDynamicEntry { + + InvokeDynamicEntryImpl(ConstantPool cpm, int index, int hash, BootstrapMethodEntryImpl bootstrapMethod, + NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_INVOKEDYNAMIC, index, hash, bootstrapMethod, nameAndType); + } + + InvokeDynamicEntryImpl(ConstantPool cpm, int index, int bsmIndex, + NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_INVOKEDYNAMIC, index, hash2(Classfile.TAG_INVOKEDYNAMIC, bsmIndex, nameAndType.index()), + bsmIndex, nameAndType); + } + + @Override + public InvokeDynamicEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.invokeDynamicEntry(bootstrap(), nameAndType()); + } + } + + public static final class ConstantDynamicEntryImpl extends AbstractDynamicConstantPoolEntry + implements ConstantDynamicEntry { + + ConstantDynamicEntryImpl(ConstantPool cpm, int index, int hash, BootstrapMethodEntryImpl bootstrapMethod, + NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_CONSTANTDYNAMIC, index, hash, bootstrapMethod, nameAndType); + } + + ConstantDynamicEntryImpl(ConstantPool cpm, int index, int bsmIndex, + NameAndTypeEntryImpl nameAndType) { + super(cpm, Classfile.TAG_CONSTANTDYNAMIC, index, hash2(Classfile.TAG_CONSTANTDYNAMIC, bsmIndex, nameAndType.index()), + bsmIndex, nameAndType); + } + + @Override + public ConstantDynamicEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.constantDynamicEntry(bootstrap(), nameAndType()); + } + } + + public static final class MethodHandleEntryImpl extends AbstractPoolEntry + implements MethodHandleEntry { + + private final int refKind; + private final AbstractPoolEntry.AbstractMemberRefEntry reference; + + MethodHandleEntryImpl(ConstantPool cpm, int index, int hash, int refKind, AbstractPoolEntry.AbstractMemberRefEntry + reference) { + super(cpm, Classfile.TAG_METHODHANDLE, index, hash); + this.refKind = refKind; + this.reference = reference; + } + + MethodHandleEntryImpl(ConstantPool cpm, int index, int refKind, AbstractPoolEntry.AbstractMemberRefEntry + reference) { + super(cpm, Classfile.TAG_METHODHANDLE, index, hash2(Classfile.TAG_METHODHANDLE, refKind, reference.index())); + this.refKind = refKind; + this.reference = reference; + } + + @Override + public int kind() { + return refKind; + } + + @Override + public AbstractPoolEntry.AbstractMemberRefEntry reference() { + return reference; + } + + @Override + public DirectMethodHandleDesc asSymbol() { + return MethodHandleDesc.of( + DirectMethodHandleDesc.Kind.valueOf(kind(), reference() instanceof InterfaceMethodRefEntry), + ((MemberRefEntry) reference()).owner().asSymbol(), + ((MemberRefEntry) reference()).nameAndType().name().stringValue(), + ((MemberRefEntry) reference()).nameAndType().type().stringValue()); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU1(refKind); + pool.writeU2(reference.index()); + } + + @Override + public MethodHandleEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodHandleEntry(refKind, reference); + } + + @Override + public String toString() { + return tag() + " " + kind() + ":" + ((MemberRefEntry) reference()).owner().asInternalName() + "." + ((MemberRefEntry) reference()).nameAndType().name().stringValue() + + "-" + ((MemberRefEntry) reference()).nameAndType().type().stringValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof MethodHandleEntryImpl m) { + return kind() == m.kind() + && reference.equals(m.reference()); + } + return false; + } + } + + public static final class MethodTypeEntryImpl + extends AbstractRefEntry + implements MethodTypeEntry { + + MethodTypeEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl descriptor) { + super(cpm, Classfile.TAG_METHODTYPE, index, descriptor); + } + + @Override + public Utf8Entry descriptor() { + return ref1; + } + + @Override + public MethodTypeEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodTypeEntry(ref1); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof MethodTypeEntryImpl m) { + return descriptor().equals(m.descriptor()); + } + return false; + } + } + + public static final class StringEntryImpl + extends AbstractRefEntry + implements StringEntry { + + StringEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl utf8) { + super(cpm, Classfile.TAG_STRING, index, utf8); + } + + @Override + public Utf8EntryImpl utf8() { + return ref1; + } + + @Override + public String stringValue() { + return ref1.toString(); + } + + @Override + public ConstantDesc constantValue() { + return stringValue(); + } + + @Override + public StringEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.stringEntry(ref1); + } + + @Override + public String toString() { + return tag() + " \"" + stringValue() + "\""; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof StringEntryImpl s) { + // check utf8 rather allocating a string + return utf8().equals(s.utf8()); + } + return false; + } + + + } + + static abstract sealed class PrimitiveEntry + extends AbstractPoolEntry { + protected final T val; + + public PrimitiveEntry(ConstantPool constantPool, int tag, int index, T val) { + super(constantPool, tag, index, hash1(tag, val.hashCode())); + this.val = val; + } + + public T value() { + return val; + } + + public ConstantDesc constantValue() { + return value(); + } + + @Override + public String toString() { + return "" + tag() + value(); + } + } + + public static final class IntegerEntryImpl extends PrimitiveEntry + implements IntegerEntry { + + IntegerEntryImpl(ConstantPool cpm, int index, int i) { + super(cpm, Classfile.TAG_INTEGER, index, i); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeInt(val); + } + + @Override + public IntegerEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.intEntry(val); + } + + @Override + public int intValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof IntegerEntryImpl e) { + return intValue() == e.intValue(); + } + return false; + } + } + + public static final class FloatEntryImpl extends PrimitiveEntry + implements FloatEntry { + + FloatEntryImpl(ConstantPool cpm, int index, float f) { + super(cpm, Classfile.TAG_FLOAT, index, f); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeFloat(val); + } + + @Override + public FloatEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.floatEntry(val); + } + + @Override + public float floatValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof FloatEntryImpl e) { + return floatValue() == e.floatValue(); + } + return false; + } + } + + public static final class LongEntryImpl extends PrimitiveEntry implements LongEntry { + + LongEntryImpl(ConstantPool cpm, int index, long l) { + super(cpm, Classfile.TAG_LONG, index, l); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeLong(val); + } + + @Override + public LongEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.longEntry(val); + } + + @Override + public long longValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof LongEntryImpl e) { + return longValue() == e.longValue(); + } + return false; + } + } + + public static final class DoubleEntryImpl extends PrimitiveEntry implements DoubleEntry { + + DoubleEntryImpl(ConstantPool cpm, int index, double d) { + super(cpm, Classfile.TAG_DOUBLE, index, d); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeDouble(val); + } + + @Override + public DoubleEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.doubleEntry(val); + } + + @Override + public double doubleValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof DoubleEntryImpl e) { + return doubleValue() == e.doubleValue(); + } + return false; + } + } + + static class CpException extends RuntimeException { + static final long serialVersionUID = 32L; + + CpException(String s) { + super(s); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPseudoInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPseudoInstruction.java new file mode 100644 index 0000000000000..dcc4cb83a3b9c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPseudoInstruction.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.instruction.CharacterRange; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.PseudoInstruction; + +public abstract sealed class AbstractPseudoInstruction + extends AbstractElement + implements PseudoInstruction { + + @Override + public abstract void writeTo(DirectCodeBuilder writer); + + public static final class ExceptionCatchImpl + extends AbstractPseudoInstruction + implements ExceptionCatch { + + public final ClassEntry catchTypeEntry; + public final Label handler; + public final Label tryStart; + public final Label tryEnd; + + public ExceptionCatchImpl(Label handler, Label tryStart, Label tryEnd, + ClassEntry catchTypeEntry) { + this.catchTypeEntry = catchTypeEntry; + this.handler = handler; + this.tryStart = tryStart; + this.tryEnd = tryEnd; + } + + public ExceptionCatchImpl(Label handler, Label tryStart, Label tryEnd, + Optional catchTypeEntry) { + this.catchTypeEntry = catchTypeEntry.orElse(null); + this.handler = handler; + this.tryStart = tryStart; + this.tryEnd = tryEnd; + } + + @Override + public Label tryStart() { + return tryStart; + } + + @Override + public Label handler() { + return handler; + } + + @Override + public Label tryEnd() { + return tryEnd; + } + + @Override + public Optional catchType() { + return Optional.ofNullable(catchTypeEntry); + } + + ClassEntry catchTypeEntry() { + return catchTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addHandler(this); + } + + @Override + public String toString() { + return String.format("ExceptionCatch[catchType=%s]", catchTypeEntry == null ? "" : catchTypeEntry.name().stringValue()); + } + } + + public static final class UnboundCharacterRange + extends AbstractPseudoInstruction + implements CharacterRange { + + public final Label startScope; + public final Label endScope; + public final int characterRangeStart; + public final int characterRangeEnd; + public final int flags; + + public UnboundCharacterRange(Label startScope, Label endScope, int characterRangeStart, + int characterRangeEnd, int flags) { + this.startScope = startScope; + this.endScope = endScope; + this.characterRangeStart = characterRangeStart; + this.characterRangeEnd = characterRangeEnd; + this.flags = flags; + } + + @Override + public Label startScope() { + return startScope; + } + + @Override + public Label endScope() { + return endScope; + } + + @Override + public int characterRangeStart() { + return characterRangeStart; + } + + @Override + public int characterRangeEnd() { + return characterRangeEnd; + } + + @Override + public int flags() { + return flags; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addCharacterRange(this); + } + + } + + private static abstract sealed class AbstractLocalPseudo extends AbstractPseudoInstruction { + protected final int slot; + protected final Utf8Entry name; + protected final Utf8Entry descriptor; + protected final Label startScope; + protected final Label endScope; + + public AbstractLocalPseudo(int slot, Utf8Entry name, Utf8Entry descriptor, Label startScope, Label endScope) { + this.slot = slot; + this.name = name; + this.descriptor = descriptor; + this.startScope = startScope; + this.endScope = endScope; + } + + public int slot() { + return slot; + } + + public Utf8Entry name() { + return name; + } + + public String nameString() { + return name.stringValue(); + } + + public Label startScope() { + return startScope; + } + + public Label endScope() { + return endScope; + } + + public boolean writeTo(BufWriter b) { + var lc = ((BufWriterImpl)b).labelContext(); + int startBci = lc.labelToBci(startScope()); + int endBci = lc.labelToBci(endScope()); + if (startBci == -1 || endBci == -1) { + return false; + } + int length = endBci - startBci; + b.writeU2(startBci); + b.writeU2(length); + b.writeIndex(name); + b.writeIndex(descriptor); + b.writeU2(slot()); + return true; + } + } + + public static final class UnboundLocalVariable extends AbstractLocalPseudo + implements LocalVariable { + + public UnboundLocalVariable(int slot, Utf8Entry name, Utf8Entry descriptor, Label startScope, Label endScope) { + super(slot, name, descriptor, startScope, endScope); + } + + @Override + public Utf8Entry type() { + return descriptor; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariable(this); + } + + @Override + public String toString() { + return "LocalVariable[Slot=" + slot() + + ", name=" + nameString() + + ", descriptor='" + type().stringValue() + + "']"; + } + } + + public static final class UnboundLocalVariableType extends AbstractLocalPseudo + implements LocalVariableType { + + public UnboundLocalVariableType(int slot, Utf8Entry name, Utf8Entry signature, Label startScope, Label endScope) { + super(slot, name, signature, startScope, endScope); + } + + @Override + public Utf8Entry signature() { + return descriptor; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariableType(this); + } + + @Override + public String toString() { + return "LocalVariableType[Slot=" + slot() + + ", name=" + nameString() + + ", signature='" + signature().stringValue() + + "']"; + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractUnboundModel.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractUnboundModel.java new file mode 100644 index 0000000000000..80ab68398e597 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractUnboundModel.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.AttributedElement; +import jdk.internal.classfile.ClassfileElement; +import jdk.internal.classfile.CompoundElement; + +public abstract sealed class AbstractUnboundModel + extends AbstractElement + implements CompoundElement, AttributedElement + permits BufferedCodeBuilder.Model, BufferedFieldBuilder.Model, BufferedMethodBuilder.Model { + private final List elements; + private List> attributes; + + public AbstractUnboundModel(List elements) { + this.elements = elements; + } + + @Override + public void forEachElement(Consumer consumer) { + elements.forEach(consumer); + } + + @Override + public Stream elementStream() { + return elements.stream(); + } + + @Override + public List elementList() { + return elements; + } + + @Override + public List> attributes() { + if (attributes == null) + attributes = elements.stream() + .filter(e -> e instanceof Attribute) + .>map(e -> (Attribute) e) + .toList(); + return attributes; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java new file mode 100644 index 0000000000000..3ab3de02d27a0 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Set; +import jdk.internal.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; + +public final class AccessFlagsImpl extends AbstractElement + implements AccessFlags { + + private final AccessFlag.Location location; + private final int flagsMask; + private Set flags; + + public AccessFlagsImpl(AccessFlag.Location location, AccessFlag... flags) { + this.location = location; + this.flagsMask = Util.flagsToBits(location, flags); + this.flags = Set.of(flags); + } + + public AccessFlagsImpl(AccessFlag.Location location, int mask) { + this.location = location; + this.flagsMask = mask; + } + + @Override + public int flagsMask() { + return flagsMask; + } + + @Override + public Set flags() { + if (flags == null) + flags = AccessFlag.maskToAccessFlags(flagsMask, location); + return flags; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setFlags(flagsMask); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.setFlags(flagsMask); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + builder.setFlags(flagsMask); + } + + @Override + public AccessFlag.Location location() { + return location; + } + + @Override + public boolean has(AccessFlag flag) { + return Util.has(location, flagsMask, flag); + } + + @Override + public String toString() { + return String.format("AccessFlags[flags=%d]", flagsMask); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java new file mode 100644 index 0000000000000..8e50866954193 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.*; + +import java.lang.constant.ConstantDesc; +import java.util.List; + +public final class AnnotationImpl implements Annotation { + private final Utf8Entry className; + private final List elements; + + public AnnotationImpl(Utf8Entry className, + List elems) { + this.className = className; + this.elements = List.copyOf(elems); + } + + @Override + public Utf8Entry className() { + return className; + } + + @Override + public List elements() { + return elements; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeIndex(className()); + buf.writeList(elements()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Annotation["); + sb.append(className().stringValue()); + List evps = elements(); + if (!evps.isEmpty()) + sb.append(" ["); + for (AnnotationElement evp : evps) { + sb.append(evp.name().stringValue()) + .append("=") + .append(evp.value().toString()) + .append(", "); + } + if (!evps.isEmpty()) { + sb.delete(sb.length()-1, sb.length()); + sb.append("]"); + } + sb.append("]"); + return sb.toString(); + } + + public record AnnotationElementImpl(Utf8Entry name, + AnnotationValue value) + implements AnnotationElement { + + @Override + public void writeTo(BufWriter buf) { + buf.writeIndex(name()); + value().writeTo(buf); + } + } + + public sealed interface OfConstantImpl extends AnnotationValue.OfConstant + permits AnnotationImpl.OfStringImpl, AnnotationImpl.OfDoubleImpl, + AnnotationImpl.OfFloatImpl, AnnotationImpl.OfLongImpl, + AnnotationImpl.OfIntegerImpl, AnnotationImpl.OfShortImpl, + AnnotationImpl.OfCharacterImpl, AnnotationImpl.OfByteImpl, + AnnotationImpl.OfBooleanImpl { + + @Override + default void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(constant()); + } + + @Override + default ConstantDesc constantValue() { + return constant().constantValue(); + } + + } + + public record OfStringImpl(Utf8Entry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfString { + + @Override + public char tag() { + return 's'; + } + + @Override + public String stringValue() { + return constant().stringValue(); + } + } + + public record OfDoubleImpl(DoubleEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfDouble { + + @Override + public char tag() { + return 'D'; + } + + @Override + public double doubleValue() { + return constant().doubleValue(); + } + } + + public record OfFloatImpl(FloatEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfFloat { + + @Override + public char tag() { + return 'F'; + } + + @Override + public float floatValue() { + return constant().floatValue(); + } + } + + public record OfLongImpl(LongEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfLong { + + @Override + public char tag() { + return 'J'; + } + + @Override + public long longValue() { + return constant().longValue(); + } + } + + public record OfIntegerImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfInteger { + + @Override + public char tag() { + return 'I'; + } + + @Override + public int intValue() { + return constant().intValue(); + } + } + + public record OfShortImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfShort { + + @Override + public char tag() { + return 'S'; + } + + @Override + public short shortValue() { + return (short)constant().intValue(); + } + } + + public record OfCharacterImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfCharacter { + + @Override + public char tag() { + return 'C'; + } + + @Override + public char charValue() { + return (char)constant().intValue(); + } + } + + public record OfByteImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfByte { + + @Override + public char tag() { + return 'B'; + } + + @Override + public byte byteValue() { + return (byte)constant().intValue(); + } + } + + public record OfBooleanImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfBoolean { + + @Override + public char tag() { + return 'Z'; + } + + @Override + public boolean booleanValue() { + return constant().intValue() == 1; + } + } + + public record OfArrayImpl(List values) + implements AnnotationValue.OfArray { + + public OfArrayImpl(List values) { + this.values = List.copyOf(values); + } + + @Override + public char tag() { + return '['; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeList(values); + } + + } + + public record OfEnumImpl(Utf8Entry className, Utf8Entry constantName) + implements AnnotationValue.OfEnum { + @Override + public char tag() { + return 'e'; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(className); + buf.writeIndex(constantName); + } + + } + + public record OfAnnotationImpl(Annotation annotation) + implements AnnotationValue.OfAnnotation { + @Override + public char tag() { + return '@'; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + annotation.writeTo(buf); + } + + } + + public record OfClassImpl(Utf8Entry className) + implements AnnotationValue.OfClass { + @Override + public char tag() { + return 'c'; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(className); + } + + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationReader.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationReader.java new file mode 100644 index 0000000000000..c11f07e60243e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationReader.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.AnnotationElement; +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.ClassReader; +import jdk.internal.classfile.constantpool.*; +import jdk.internal.classfile.TypeAnnotation; +import static jdk.internal.classfile.Classfile.*; +import static jdk.internal.classfile.TypeAnnotation.TargetInfo.*; + +import java.util.List; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.access.SharedSecrets; + +class AnnotationReader { + private AnnotationReader() { } + + public static List readAnnotations(ClassReader classReader, int p) { + int pos = p; + int numAnnotations = classReader.readU2(pos); + var annos = new Object[numAnnotations]; + pos += 2; + for (int i = 0; i < numAnnotations; ++i) { + annos[i] = readAnnotation(classReader, pos); + pos = skipAnnotation(classReader, pos); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(annos); + } + + public static AnnotationValue readElementValue(ClassReader classReader, int p) { + char tag = (char) classReader.readU1(p); + ++p; + return switch (tag) { + case 'B' -> new AnnotationImpl.OfByteImpl((IntegerEntry)classReader.readEntry(p)); + case 'C' -> new AnnotationImpl.OfCharacterImpl((IntegerEntry)classReader.readEntry(p)); + case 'D' -> new AnnotationImpl.OfDoubleImpl((DoubleEntry)classReader.readEntry(p)); + case 'F' -> new AnnotationImpl.OfFloatImpl((FloatEntry)classReader.readEntry(p)); + case 'I' -> new AnnotationImpl.OfIntegerImpl((IntegerEntry)classReader.readEntry(p)); + case 'J' -> new AnnotationImpl.OfLongImpl((LongEntry)classReader.readEntry(p)); + case 'S' -> new AnnotationImpl.OfShortImpl((IntegerEntry)classReader.readEntry(p)); + case 'Z' -> new AnnotationImpl.OfBooleanImpl((IntegerEntry)classReader.readEntry(p)); + case 's' -> new AnnotationImpl.OfStringImpl(classReader.readUtf8Entry(p)); + case 'e' -> new AnnotationImpl.OfEnumImpl(classReader.readUtf8Entry(p), classReader.readUtf8Entry(p + 2)); + case 'c' -> new AnnotationImpl.OfClassImpl(classReader.readUtf8Entry(p)); + case '@' -> new AnnotationImpl.OfAnnotationImpl(readAnnotation(classReader, p)); + case '[' -> { + int numValues = classReader.readU2(p); + p += 2; + var values = new Object[numValues]; + for (int i = 0; i < numValues; ++i) { + values[i] = readElementValue(classReader, p); + p = skipElementValue(classReader, p); + } + yield new AnnotationImpl.OfArrayImpl(SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(values)); + } + default -> throw new IllegalArgumentException( + "Unexpected tag '%s' in AnnotationValue, pos = %d".formatted(tag, p - 1)); + }; + } + + public static List readTypeAnnotations(ClassReader classReader, int p, LabelContext lc) { + int numTypeAnnotations = classReader.readU2(p); + p += 2; + var annotations = new Object[numTypeAnnotations]; + for (int i = 0; i < numTypeAnnotations; ++i) { + annotations[i] = readTypeAnnotation(classReader, p, lc); + p = skipTypeAnnotation(classReader, p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(annotations); + } + + public static List> readParameterAnnotations(ClassReader classReader, int p) { + int cnt = classReader.readU1(p++); + var pas = new Object[cnt]; + for (int i = 0; i < cnt; ++i) { + pas[i] = readAnnotations(classReader, p); + p = skipAnnotations(classReader, p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(pas); + } + + private static int skipElementValue(ClassReader classReader, int p) { + char tag = (char) classReader.readU1(p); + ++p; + return switch (tag) { + case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's', 'c' -> p + 2; + case 'e' -> p + 4; + case '@' -> skipAnnotation(classReader, p); + case '[' -> { + int numValues = classReader.readU2(p); + p += 2; + for (int i = 0; i < numValues; ++i) { + p = skipElementValue(classReader, p); + } + yield p; + } + default -> throw new IllegalArgumentException( + "Unexpected tag '%s' in AnnotationValue, pos = %d".formatted(tag, p - 1)); + }; + } + + private static Annotation readAnnotation(ClassReader classReader, int p) { + Utf8Entry annotationClass = classReader.utf8EntryByIndex(classReader.readU2(p)); + p += 2; + List elems = readAnnotationElementValuePairs(classReader, p); + return new AnnotationImpl(annotationClass, elems); + } + + private static int skipAnnotations(ClassReader classReader, int p) { + int numAnnotations = classReader.readU2(p); + p += 2; + for (int i = 0; i < numAnnotations; ++i) + p = skipAnnotation(classReader, p); + return p; + } + + private static int skipAnnotation(ClassReader classReader, int p) { + return skipElementValuePairs(classReader, p + 2); + } + + private static List readAnnotationElementValuePairs(ClassReader classReader, int p) { + int numElementValuePairs = classReader.readU2(p); + p += 2; + var annotationElements = new Object[numElementValuePairs]; + for (int i = 0; i < numElementValuePairs; ++i) { + Utf8Entry elementName = classReader.readUtf8Entry(p); + p += 2; + AnnotationValue value = readElementValue(classReader, p); + annotationElements[i] = new AnnotationImpl.AnnotationElementImpl(elementName, value); + p = skipElementValue(classReader, p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(annotationElements); + } + + private static int skipElementValuePairs(ClassReader classReader, int p) { + int numElementValuePairs = classReader.readU2(p); + p += 2; + for (int i = 0; i < numElementValuePairs; ++i) { + p = skipElementValue(classReader, p + 2); + } + return p; + } + + private static Label getLabel(LabelContext lc, int bciOffset, int targetType, int p) { + //helper method to avoid NPE + if (lc == null) throw new IllegalArgumentException("Unexpected targetType '%d' in TypeAnnotation outside of Code attribute, pos = %d".formatted(targetType, p - 1)); + return lc.getLabel(bciOffset); + } + + private static TypeAnnotation readTypeAnnotation(ClassReader classReader, int p, LabelContext lc) { + int targetType = classReader.readU1(p++); + var targetInfo = switch (targetType) { + case TAT_CLASS_TYPE_PARAMETER -> + ofClassTypeParameter(classReader.readU1(p)); + case TAT_METHOD_TYPE_PARAMETER -> + ofMethodTypeParameter(classReader.readU1(p)); + case TAT_CLASS_EXTENDS -> + ofClassExtends(classReader.readU2(p)); + case TAT_CLASS_TYPE_PARAMETER_BOUND -> + ofClassTypeParameterBound(classReader.readU1(p), classReader.readU1(p + 1)); + case TAT_METHOD_TYPE_PARAMETER_BOUND -> + ofMethodTypeParameterBound(classReader.readU1(p), classReader.readU1(p + 1)); + case TAT_FIELD -> + ofField(); + case TAT_METHOD_RETURN -> + ofMethodReturn(); + case TAT_METHOD_RECEIVER -> + ofMethodReceiver(); + case TAT_METHOD_FORMAL_PARAMETER -> + ofMethodFormalParameter(classReader.readU1(p)); + case TAT_THROWS -> + ofThrows(classReader.readU2(p)); + case TAT_LOCAL_VARIABLE -> + ofLocalVariable(readLocalVarEntries(classReader, p, lc, targetType)); + case TAT_RESOURCE_VARIABLE -> + ofResourceVariable(readLocalVarEntries(classReader, p, lc, targetType)); + case TAT_EXCEPTION_PARAMETER -> + ofExceptionParameter(classReader.readU2(p)); + case TAT_INSTANCEOF -> + ofInstanceofExpr(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_NEW -> + ofNewExpr(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_CONSTRUCTOR_REFERENCE -> + ofConstructorReference(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_METHOD_REFERENCE -> + ofMethodReference(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_CAST -> + ofCastExpr(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT -> + ofConstructorInvocationTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_METHOD_INVOCATION_TYPE_ARGUMENT -> + ofMethodInvocationTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT -> + ofConstructorReferenceTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_METHOD_REFERENCE_TYPE_ARGUMENT -> + ofMethodReferenceTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + default -> + throw new IllegalArgumentException("Unexpected targetType '%d' in TypeAnnotation, pos = %d".formatted(targetType, p - 1)); + }; + p += targetInfo.size(); + int pathLength = classReader.readU1(p++); + TypeAnnotation.TypePathComponent[] typePath = new TypeAnnotation.TypePathComponent[pathLength]; + for (int i = 0; i < pathLength; ++i) { + int typePathKindTag = classReader.readU1(p++); + int typeArgumentIndex = classReader.readU1(p++); + typePath[i] = switch (typePathKindTag) { + case 0 -> TypeAnnotation.TypePathComponent.ARRAY; + case 1 -> TypeAnnotation.TypePathComponent.INNER_TYPE; + case 2 -> TypeAnnotation.TypePathComponent.WILDCARD; + case 3 -> new UnboundAttribute.TypePathComponentImpl(TypeAnnotation.TypePathComponent.Kind.TYPE_ARGUMENT, typeArgumentIndex); + default -> throw new IllegalArgumentException("Unknown type annotation path component kind: " + typePathKindTag); + }; + } + // the annotation info for this annotation + Utf8Entry type = classReader.readUtf8Entry(p); + p += 2; + return TypeAnnotation.of(targetInfo, List.of(typePath), type, + readAnnotationElementValuePairs(classReader, p)); + } + + private static List readLocalVarEntries(ClassReader classReader, int p, LabelContext lc, int targetType) { + int tableLength = classReader.readU2(p); + p += 2; + var entries = new Object[tableLength]; + for (int i = 0; i < tableLength; ++i) { + int startPc = classReader.readU2(p); + entries[i] = TypeAnnotation.LocalVarTargetInfo.of( + getLabel(lc, startPc, targetType, p), + getLabel(lc, startPc + classReader.readU2(p + 2), targetType, p - 2), + classReader.readU2(p + 4)); + p += 6; + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(entries); + } + + private static int skipTypeAnnotation(ClassReader classReader, int p) { + int targetType = classReader.readU1(p++); + p += switch (targetType) { + case 0x13, 0x14, 0x15 -> 0; + case 0x00, 0x01, 0x16 -> 1; + case 0x10, 0x11, 0x12, 0x17, 0x42, 0x43, 0x44, 0x45, 0x46 -> 2; + case 0x47, 0x48, 0x49, 0x4A, 0x4B -> 3; + case 0x40, 0x41 -> 2 + classReader.readU2(p) * 6; + default -> throw new IllegalArgumentException( + "Unexpected targetType '%d' in TypeAnnotation, pos = %d".formatted(targetType, p - 1)); + }; + int pathLength = classReader.readU1(p++); + p += pathLength * 2; + + // the annotation info for this annotation + p += 2; + p = skipElementValuePairs(classReader, p); + return p; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java new file mode 100644 index 0000000000000..e41cb41feb46f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.AttributeMapper; +import jdk.internal.classfile.BufWriter; + +public class AttributeHolder { + private final List> attributes = new ArrayList<>(); + + public
> void withAttribute(Attribute a) { + if (a == null) + return; + + @SuppressWarnings("unchecked") + AttributeMapper am = (AttributeMapper) a.attributeMapper(); + if (!am.allowMultiple() && isPresent(am)) { + remove(am); + } + attributes.add(a); + } + + public int size() { + return attributes.size(); + } + + public void writeTo(BufWriter buf) { + buf.writeU2(attributes.size()); + for (Attribute a : attributes) + a.writeTo(buf); + } + + boolean isPresent(AttributeMapper am) { + for (Attribute a : attributes) + if (a.attributeMapper() == am) + return true; + return false; + } + + private void remove(AttributeMapper am) { + attributes.removeIf(a -> a.attributeMapper() == am); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BlockCodeBuilderImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BlockCodeBuilderImpl.java new file mode 100644 index 0000000000000..e57bfd76d300b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BlockCodeBuilderImpl.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.LabelTarget; + +import java.util.Objects; +import jdk.internal.classfile.Instruction; + +public final class BlockCodeBuilderImpl + extends NonterminalCodeBuilder + implements CodeBuilder.BlockCodeBuilder { + private final Label startLabel, endLabel, breakLabel; + private boolean reachable = true; + private boolean hasInstructions = false; + private int topLocal; + private int terminalMaxLocals; + + public BlockCodeBuilderImpl(CodeBuilder parent, Label breakLabel) { + super(parent); + this.startLabel = parent.newLabel(); + this.endLabel = parent.newLabel(); + this.breakLabel = Objects.requireNonNull(breakLabel); + } + + public void start() { + topLocal = topLocal(parent); + terminalMaxLocals = topLocal(terminal); + terminal.with((LabelTarget) startLabel); + } + + public void end() { + terminal.with((LabelTarget) endLabel); + if (terminalMaxLocals != topLocal(terminal)) { + throw new IllegalStateException("Interference in local variable slot management"); + } + } + + public boolean reachable() { + return reachable; + } + + public boolean isEmpty() { + return !hasInstructions; + } + + private int topLocal(CodeBuilder parent) { + return switch (parent) { + case BlockCodeBuilderImpl b -> b.topLocal; + case ChainedCodeBuilder b -> topLocal(b.terminal); + case DirectCodeBuilder b -> b.curTopLocal(); + case BufferedCodeBuilder b -> b.curTopLocal(); + case TransformingCodeBuilder b -> topLocal(b.delegate); + }; + } + + @Override + public CodeBuilder with(CodeElement element) { + parent.with(element); + + hasInstructions |= element instanceof Instruction; + + if (reachable) { + if (element instanceof Instruction i && i.opcode().isUnconditionalBranch()) + reachable = false; + } + else if (element instanceof LabelTarget) { + reachable = true; + } + return this; + } + + @Override + public Label startLabel() { + return startLabel; + } + + @Override + public Label endLabel() { + return endLabel; + } + + @Override + public int allocateLocal(TypeKind typeKind) { + int retVal = topLocal; + topLocal += typeKind.slotSize(); + return retVal; + } + + @Override + public Label breakLabel() { + return breakLabel; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.java new file mode 100644 index 0000000000000..48b94c1dedfb7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.List; + +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; + +import static jdk.internal.classfile.impl.AbstractPoolEntry.MethodHandleEntryImpl; + +public final class BootstrapMethodEntryImpl implements BootstrapMethodEntry { + + final int index; + final int hash; + private final ConstantPool constantPool; + private final MethodHandleEntryImpl handle; + private final List arguments; + + BootstrapMethodEntryImpl(ConstantPool constantPool, int bsmIndex, int hash, + MethodHandleEntryImpl handle, + List arguments) { + this.index = bsmIndex; + this.hash = hash; + this.constantPool = constantPool; + this.handle = handle; + this.arguments = List.copyOf(arguments); + } + + @Override + public ConstantPool constantPool() { + return constantPool; + } + + @Override + public MethodHandleEntry bootstrapMethod() { + return handle; + } + + @Override + public List arguments() { + return arguments; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BootstrapMethodEntry e + && e.bootstrapMethod().equals(handle) + && e.arguments().equals(arguments); + } + + static int computeHashCode(MethodHandleEntryImpl handle, + List arguments) { + int hash = handle.hashCode(); + hash = 31 * hash + arguments.hashCode(); + return AbstractPoolEntry.phiMix(hash); + } + + @Override + public int bsmIndex() { return index; } + + @Override + public int hashCode() { + return hash; + } + + @Override + public void writeTo(BufWriter writer) { + writer.writeIndex(bootstrapMethod()); + writer.writeListIndices(arguments()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java new file mode 100644 index 0000000000000..d7243e27345fd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java @@ -0,0 +1,1001 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.ConstantValueEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.access.SharedSecrets; + +public abstract sealed class BoundAttribute> + extends AbstractElement + implements Attribute { + + static final int NAME_AND_LENGTH_PREFIX = 6; + private final AttributeMapper mapper; + final ClassReader classReader; + final int payloadStart; + + BoundAttribute(ClassReader classReader, AttributeMapper mapper, int payloadStart) { + this.mapper = mapper; + this.classReader = classReader; + this.payloadStart = payloadStart; + } + + public int payloadLen() { + return classReader.readInt(payloadStart - 4); + } + + @Override + public String attributeName() { + return mapper.name(); + } + + @Override + public AttributeMapper attributeMapper() { + return mapper; + } + + public byte[] contents() { + return classReader.readBytes(payloadStart, payloadLen()); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + builder.writeAttribute(this); + } + + @Override + @SuppressWarnings("unchecked") + public void writeTo(BufWriter buf) { + if (!buf.canWriteDirect(classReader)) + attributeMapper().writeAttribute(buf, (T) this); + else + classReader.copyBytesTo(buf, payloadStart - NAME_AND_LENGTH_PREFIX, payloadLen() + NAME_AND_LENGTH_PREFIX); + } + + public ConstantPool constantPool() { + return classReader; + } + + @Override + public String toString() { + return String.format("Attribute[name=%s]", mapper.name()); + } + + List readEntryList(int p) { + int cnt = classReader.readU2(p); + p += 2; + var entries = new Object[cnt]; + int end = p + (cnt * 2); + for (int i = 0; p < end; i++, p += 2) { + entries[i] = classReader.readEntry(p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(entries); + } + + public static List> readAttributes(AttributedElement enclosing, ClassReader reader, int pos, + Function> customAttributes) { + int size = reader.readU2(pos); + var filled = new Object[size]; + int p = pos + 2; + for (int i = 0; i < size; ++i) { + Utf8Entry name = reader.readUtf8Entry(p); + int len = reader.readInt(p + 2); + p += 6; + + var mapper = Attributes.standardAttribute(name); + if (mapper == null) { + mapper = customAttributes.apply(name); + } + if (mapper != null) { + filled[i] = mapper.readAttribute(enclosing, reader, p); + } else if (((ClassReaderImpl)reader).options().processUnknownAttributes) { + AttributeMapper fakeMapper = new AttributeMapper<>() { + @Override + public String name() { + return name.stringValue(); + } + + @Override + public UnknownAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + // Will never get called + throw new UnsupportedOperationException(); + } + + @Override + public void writeAttribute(BufWriter buf, UnknownAttribute attr) { + throw new UnsupportedOperationException("Write of unknown attribute " + name() + " not supported"); + } + + @Override + public boolean allowMultiple() { + return true; + } + }; + filled[i] = new BoundUnknownAttribute(reader, fakeMapper, p); + } + p += len; + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(filled); + } + + public static final class BoundUnknownAttribute extends BoundAttribute + implements UnknownAttribute { + public BoundUnknownAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + checkWriteSupported(builder::canWriteDirect); + super.writeTo(builder); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + checkWriteSupported(builder::canWriteDirect); + super.writeTo(builder); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + checkWriteSupported(builder::canWriteDirect); + super.writeTo(builder); + } + + @Override + public void writeTo(BufWriter buf) { + checkWriteSupported(buf::canWriteDirect); + super.writeTo(buf); + } + + private void checkWriteSupported(Function condition) { + if (!condition.apply(classReader)) + throw new UnsupportedOperationException("Write of unknown attribute " + attributeName() + " not supported to alien constant pool"); + } + } + + public static final class BoundStackMapTableAttribute + extends BoundAttribute + implements StackMapTableAttribute { + final MethodModel method; + final LabelContext ctx; + List entries = null; + + public BoundStackMapTableAttribute(CodeImpl code, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + method = code.parent().orElseThrow(); + ctx = code; + } + + @Override + public List entries() { + if (entries == null) { + entries = new StackMapDecoder(classReader, payloadStart, ctx, StackMapDecoder.initFrameLocals(method)).entries(); + } + return entries; + } + } + + public static final class BoundSyntheticAttribute extends BoundAttribute + implements SyntheticAttribute { + public BoundSyntheticAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundLineNumberTableAttribute + extends BoundAttribute + implements LineNumberTableAttribute { + private List lineNumbers = null; + + public BoundLineNumberTableAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List lineNumbers() { + if (lineNumbers == null) { + int nLn = classReader.readU2(payloadStart); + LineNumberInfo[] elements = new LineNumberInfo[nLn]; + int p = payloadStart + 2; + int pEnd = p + (nLn * 4); + for (int i = 0; p < pEnd; p += 4, i++) { + int startPc = classReader.readU2(p); + int lineNumber = classReader.readU2(p + 2); + elements[i] = LineNumberInfo.of(startPc, lineNumber); + } + lineNumbers = List.of(elements); + } + return lineNumbers; + } + } + + public static final class BoundCharacterRangeTableAttribute extends BoundAttribute implements CharacterRangeTableAttribute { + private List characterRangeTable = null; + + public BoundCharacterRangeTableAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List characterRangeTable() { + if (characterRangeTable == null) { + int nLn = classReader.readU2(payloadStart); + CharacterRangeInfo[] elements = new CharacterRangeInfo[nLn]; + int p = payloadStart + 2; + int pEnd = p + (nLn * 14); + for (int i = 0; p < pEnd; p += 14, i++) { + int startPc = classReader.readU2(p); + int endPc = classReader.readU2(p + 2); + int characterRangeStart = classReader.readInt(p + 4); + int characterRangeEnd = classReader.readInt(p + 8); + int flags = classReader.readU2(p + 12); + elements[i] = CharacterRangeInfo.of(startPc, endPc, characterRangeStart, characterRangeEnd, flags); + } + characterRangeTable = List.of(elements); + } + return characterRangeTable; + } + } + + public static final class BoundLocalVariableTableAttribute + extends BoundAttribute + implements LocalVariableTableAttribute { + private final CodeImpl codeAttribute; + private List localVars = null; + + public BoundLocalVariableTableAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + codeAttribute = (CodeImpl) enclosing; + } + + @Override + public List localVariables() { + if (localVars == null) { + int cnt = classReader.readU2(payloadStart); + BoundLocalVariable[] elements = new BoundLocalVariable[cnt]; + int p = payloadStart + 2; + int pEnd = p + (cnt * 10); + for (int i = 0; p < pEnd; p += 10, i++) { + elements[i] = new BoundLocalVariable(codeAttribute, p); + } + localVars = List.of(elements); + } + return localVars; + } + } + + public static final class BoundLocalVariableTypeTableAttribute + extends BoundAttribute + implements LocalVariableTypeTableAttribute { + private final CodeImpl codeAttribute; + private List localVars = null; + + public BoundLocalVariableTypeTableAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.codeAttribute = (CodeImpl) enclosing; + } + + @Override + public List localVariableTypes() { + if (localVars == null) { + final int cnt = classReader.readU2(payloadStart); + BoundLocalVariableType[] elements = new BoundLocalVariableType[cnt]; + int p = payloadStart + 2; + int pEnd = p + (cnt * 10); + for (int i = 0; p < pEnd; p += 10, i++) { + elements[i] = new BoundLocalVariableType(codeAttribute, p); + } + localVars = List.of(elements); + } + return localVars; + } + } + + public static final class BoundMethodParametersAttribute extends BoundAttribute + implements MethodParametersAttribute { + private List parameters = null; + + public BoundMethodParametersAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List parameters() { + if (parameters == null) { + final int cnt = classReader.readU1(payloadStart); + MethodParameterInfo[] elements = new MethodParameterInfo[cnt]; + int p = payloadStart + 1; + int pEnd = p + (cnt * 4); + for (int i = 0; p < pEnd; p += 4, i++) { + Utf8Entry name = classReader.readUtf8Entry(p); + int accessFlags = classReader.readU2(p + 2); + elements[i] = MethodParameterInfo.of(Optional.ofNullable(name), accessFlags); + } + parameters = List.of(elements); + } + return parameters; + } + } + + public static final class BoundModuleHashesAttribute extends BoundAttribute + implements ModuleHashesAttribute { + private List hashes = null; + + public BoundModuleHashesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry algorithm() { + return classReader.readUtf8Entry(payloadStart); + } + + @Override + public List hashes() { + if (hashes == null) { + final int cnt = classReader.readU2(payloadStart + 2); + ModuleHashInfo[] elements = new ModuleHashInfo[cnt]; + int p = payloadStart + 4; + //System.err.printf("%5d: ModuleHashesAttr alg = %s, cnt = %d%n", pos, algorithm(), cnt); + for (int i = 0; i < cnt; ++i) { + ModuleEntry module = classReader.readModuleEntry(p); + int hashLength = classReader.readU2(p + 2); + //System.err.printf("%5d: [%d] module = %s, hashLength = %d%n", p, i, module, hashLength); + p += 4; + elements[i] = ModuleHashInfo.of(module, classReader.readBytes(p, hashLength)); + p += hashLength; + } + hashes = List.of(elements); + } + return hashes; + } + } + + public static final class BoundRecordAttribute extends BoundAttribute + implements RecordAttribute { + private List components = null; + + public BoundRecordAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List components() { + if (components == null) { + final int cnt = classReader.readU2(payloadStart); + RecordComponentInfo[] elements = new RecordComponentInfo[cnt]; + int p = payloadStart + 2; + for (int i = 0; i < cnt; i++) { + elements[i] = new BoundRecordComponentInfo(classReader, p); + p = classReader.skipAttributeHolder(p + 4); + } + components = List.of(elements); + } + return components; + } + } + + public static final class BoundDeprecatedAttribute extends BoundAttribute + implements DeprecatedAttribute { + public BoundDeprecatedAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundSignatureAttribute extends BoundAttribute + implements SignatureAttribute { + public BoundSignatureAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry signature() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundSourceFileAttribute extends BoundAttribute + implements SourceFileAttribute { + public BoundSourceFileAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry sourceFile() { + return classReader.readUtf8Entry(payloadStart); + } + + } + + public static final class BoundModuleMainClassAttribute extends BoundAttribute implements ModuleMainClassAttribute { + public BoundModuleMainClassAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ClassEntry mainClass() { + return classReader.readClassEntry(payloadStart); + } + } + + public static final class BoundNestHostAttribute extends BoundAttribute + implements NestHostAttribute { + public BoundNestHostAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ClassEntry nestHost() { + return classReader.readClassEntry(payloadStart); + } + } + + public static final class BoundSourceDebugExtensionAttribute extends BoundAttribute + implements SourceDebugExtensionAttribute { + public BoundSourceDebugExtensionAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundConstantValueAttribute extends BoundAttribute + implements ConstantValueAttribute { + public BoundConstantValueAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ConstantValueEntry constant() { + return (ConstantValueEntry) classReader.readEntry(payloadStart); + } + + } + + public static final class BoundModuleTargetAttribute extends BoundAttribute + implements ModuleTargetAttribute { + public BoundModuleTargetAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry targetPlatform() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundCompilationIDAttribute extends BoundAttribute + implements CompilationIDAttribute { + public BoundCompilationIDAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry compilationId() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundSourceIDAttribute extends BoundAttribute + implements SourceIDAttribute { + public BoundSourceIDAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry sourceId() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundModuleResolutionAttribute extends BoundAttribute + implements ModuleResolutionAttribute { + public BoundModuleResolutionAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public int resolutionFlags() { + return classReader.readU2(payloadStart); + } + } + + public static final class BoundExceptionsAttribute extends BoundAttribute + implements ExceptionsAttribute { + private List exceptions = null; + + public BoundExceptionsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List exceptions() { + if (exceptions == null) { + exceptions = readEntryList(payloadStart); + } + return exceptions; + } + } + + public static final class BoundModuleAttribute extends BoundAttribute + implements ModuleAttribute { + private List requires = null; + private List exports = null; + private List opens = null; + private List uses = null; + private List provides = null; + + public BoundModuleAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ModuleEntry moduleName() { + return classReader.readModuleEntry(payloadStart); + } + + @Override + public int moduleFlagsMask() { + return classReader.readU2(payloadStart + 2); + } + + @Override + public Optional moduleVersion() { + return Optional.ofNullable(classReader.readUtf8EntryOrNull(payloadStart + 4)); + } + + @Override + public List requires() { + if (requires == null) { + structure(); + } + return requires; + } + + @Override + public List exports() { + if (exports == null) { + structure(); + } + return exports; + } + + @Override + public List opens() { + if (opens == null) { + structure(); + } + return opens; + } + + @Override + public List uses() { + if (uses == null) { + structure(); + } + return uses; + } + + @Override + public List provides() { + if (provides == null) { + structure(); + } + return provides; + } + + private void structure() { + int p = payloadStart + 8; + + { + int cnt = classReader.readU2(payloadStart + 6); + ModuleRequireInfo[] elements = new ModuleRequireInfo[cnt]; + int end = p + (cnt * 6); + for (int i = 0; p < end; p += 6, i++) { + elements[i] = ModuleRequireInfo.of(classReader.readModuleEntry(p), + classReader.readU2(p + 2), + (Utf8Entry) classReader.readEntryOrNull(p + 4)); + } + requires = List.of(elements); + } + + { + int cnt = classReader.readU2(p); + p += 2; + ModuleExportInfo[] elements = new ModuleExportInfo[cnt]; + for (int i = 0; i < cnt; i++) { + PackageEntry pe = classReader.readPackageEntry(p); + int exportFlags = classReader.readU2(p + 2); + p += 4; + List exportsTo = readEntryList(p); + p += 2 + exportsTo.size() * 2; + elements[i] = ModuleExportInfo.of(pe, exportFlags, exportsTo); + } + exports = List.of(elements); + } + + { + int cnt = classReader.readU2(p); + p += 2; + ModuleOpenInfo[] elements = new ModuleOpenInfo[cnt]; + for (int i = 0; i < cnt; i++) { + PackageEntry po = classReader.readPackageEntry(p); + int opensFlags = classReader.readU2(p + 2); + p += 4; + List opensTo = readEntryList(p); + p += 2 + opensTo.size() * 2; + elements[i] = ModuleOpenInfo.of(po, opensFlags, opensTo); + } + opens = List.of(elements); + } + + { + uses = readEntryList(p); + p += 2 + uses.size() * 2; + int cnt = classReader.readU2(p); + p += 2; + ModuleProvideInfo[] elements = new ModuleProvideInfo[cnt]; + provides = new ArrayList<>(cnt); + for (int i = 0; i < cnt; i++) { + ClassEntry c = classReader.readClassEntry(p); + p += 2; + List providesWith = readEntryList(p); + p += 2 + providesWith.size() * 2; + elements[i] = ModuleProvideInfo.of(c, providesWith); + } + provides = List.of(elements); + } + } + } + + public static final class BoundModulePackagesAttribute extends BoundAttribute + implements ModulePackagesAttribute { + private List packages = null; + + public BoundModulePackagesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List packages() { + if (packages == null) { + packages = readEntryList(payloadStart); + } + return packages; + } + } + + public static final class BoundNestMembersAttribute extends BoundAttribute + implements NestMembersAttribute { + + private List members = null; + + public BoundNestMembersAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List nestMembers() { + if (members == null) { + members = readEntryList(payloadStart); + } + return members; + } + } + + public static final class BoundBootstrapMethodsAttribute extends BoundAttribute + implements BootstrapMethodsAttribute { + + private List bootstraps = null; + private final int size; + + public BoundBootstrapMethodsAttribute(ClassReader reader, AttributeMapper mapper, int pos) { + super(reader, mapper, pos); + size = classReader.readU2(pos); + } + + @Override + public int bootstrapMethodsSize() { + return size; + } + + @Override + public List bootstrapMethods() { + if (bootstraps == null) { + BootstrapMethodEntry[] bs = new BootstrapMethodEntry[size]; + int p = payloadStart + 2; + for (int i = 0; i < size; ++i) { + final AbstractPoolEntry.MethodHandleEntryImpl handle + = (AbstractPoolEntry.MethodHandleEntryImpl) classReader.readMethodHandleEntry(p); + final List args = readEntryList(p + 2); + p += 4 + args.size() * 2; + int hash = BootstrapMethodEntryImpl.computeHashCode(handle, args); + bs[i] = new BootstrapMethodEntryImpl(classReader, i, hash, handle, args); + } + bootstraps = List.of(bs); + } + return bootstraps; + } + } + + public static final class BoundInnerClassesAttribute extends BoundAttribute + implements InnerClassesAttribute { + private List classes; + + public BoundInnerClassesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List classes() { + if (classes == null) { + final int cnt = classReader.readU2(payloadStart); + int p = payloadStart + 2; + InnerClassInfo[] elements = new InnerClassInfo[cnt]; + for (int i = 0; i < cnt; i++) { + ClassEntry innerClass = classReader.readClassEntry(p); // TODO FIXME + int outerClassIndex = classReader.readU2(p + 2); + ClassEntry outerClass = outerClassIndex == 0 + ? null + : (ClassEntry) classReader.entryByIndex(outerClassIndex); + int innerNameIndex = classReader.readU2(p + 4); + Utf8Entry innerName = innerNameIndex == 0 + ? null + : (Utf8Entry) classReader.entryByIndex(innerNameIndex); + int flags = classReader.readU2(p + 6); + p += 8; + elements[i] = InnerClassInfo.of(innerClass, Optional.ofNullable(outerClass), Optional.ofNullable(innerName), flags); + } + classes = List.of(elements); + } + return classes; + } + } + + public static final class BoundEnclosingMethodAttribute extends BoundAttribute + implements EnclosingMethodAttribute { + public BoundEnclosingMethodAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ClassEntry enclosingClass() { + return classReader.readClassEntry(payloadStart); + } + + @Override + public Optional enclosingMethod() { + return Optional.ofNullable((NameAndTypeEntry) classReader.readEntryOrNull(payloadStart + 2)); + } + } + + public static final class BoundAnnotationDefaultAttr + extends BoundAttribute + implements AnnotationDefaultAttribute { + private AnnotationValue annotationValue; + + public BoundAnnotationDefaultAttr(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public AnnotationValue defaultValue() { + if (annotationValue == null) + annotationValue = AnnotationReader.readElementValue(classReader, payloadStart); + return annotationValue; + } + } + + public static final class BoundRuntimeVisibleTypeAnnotationsAttribute extends BoundAttribute + implements RuntimeVisibleTypeAnnotationsAttribute { + + private final LabelContext labelContext; + + public BoundRuntimeVisibleTypeAnnotationsAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.labelContext = (enclosing instanceof LabelContext lc) ? lc : null; + } + + @Override + public List annotations() { + return AnnotationReader.readTypeAnnotations(classReader, payloadStart, labelContext); + } + } + + public static final class BoundRuntimeInvisibleTypeAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleTypeAnnotationsAttribute { + public BoundRuntimeInvisibleTypeAnnotationsAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.labelContext = (enclosing instanceof LabelContext lc) ? lc : null; + } + + private final LabelContext labelContext; + + @Override + public List annotations() { + return AnnotationReader.readTypeAnnotations(classReader, payloadStart, labelContext); + } + } + + public static final class BoundRuntimeVisibleParameterAnnotationsAttribute + extends BoundAttribute + implements RuntimeVisibleParameterAnnotationsAttribute { + + public BoundRuntimeVisibleParameterAnnotationsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List> parameterAnnotations() { + return AnnotationReader.readParameterAnnotations(classReader, payloadStart); + } + } + + public static final class BoundRuntimeInvisibleParameterAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleParameterAnnotationsAttribute { + + public BoundRuntimeInvisibleParameterAnnotationsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List> parameterAnnotations() { + return AnnotationReader.readParameterAnnotations(classReader, payloadStart); + } + } + + public static final class BoundRuntimeInvisibleAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleAnnotationsAttribute { + private List inflated; + + public BoundRuntimeInvisibleAnnotationsAttribute(ClassReader cf, + int payloadStart) { + super(cf, Attributes.RUNTIME_INVISIBLE_ANNOTATIONS, payloadStart); + } + + @Override + public List annotations() { + if (inflated == null) + inflated = AnnotationReader.readAnnotations(classReader, payloadStart); + return inflated; + } + } + + public static final class BoundRuntimeVisibleAnnotationsAttribute + extends BoundAttribute + implements RuntimeVisibleAnnotationsAttribute { + private List inflated; + + public BoundRuntimeVisibleAnnotationsAttribute(ClassReader cf, + int payloadStart) { + super(cf, Attributes.RUNTIME_VISIBLE_ANNOTATIONS, payloadStart); + } + + @Override + public List annotations() { + if (inflated == null) + inflated = AnnotationReader.readAnnotations(classReader, payloadStart); + return inflated; + } + } + + public static final class BoundPermittedSubclassesAttribute extends BoundAttribute + implements PermittedSubclassesAttribute { + private List permittedSubclasses = null; + + public BoundPermittedSubclassesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List permittedSubclasses() { + if (permittedSubclasses == null) { + permittedSubclasses = readEntryList(payloadStart); + } + return permittedSubclasses; + } + } + + public static abstract sealed class BoundCodeAttribute + extends BoundAttribute + implements CodeAttribute + permits CodeImpl { + protected final int codeStart; + protected final int codeLength; + protected final int codeEnd; + protected final int attributePos; + protected final int exceptionHandlerPos; + protected final int exceptionHandlerCnt; + protected final MethodModel enclosingMethod; + + public BoundCodeAttribute(AttributedElement enclosing, + ClassReader reader, + AttributeMapper mapper, + int payloadStart) { + super(reader, mapper, payloadStart); + this.codeLength = classReader.readInt(payloadStart + 4); + this.enclosingMethod = (MethodModel) enclosing; + this.codeStart = payloadStart + 8; + this.codeEnd = codeStart + codeLength; + this.exceptionHandlerPos = codeEnd; + this.exceptionHandlerCnt = classReader.readU2(exceptionHandlerPos); + this.attributePos = exceptionHandlerPos + 2 + exceptionHandlerCnt * 8; + } + + // CodeAttribute + + @Override + public int maxStack() { + return classReader.readU2(payloadStart); + } + + @Override + public int maxLocals() { + return classReader.readU2(payloadStart + 2); + } + + @Override + public int codeLength() { + return codeLength; + } + + @Override + public byte[] codeArray() { + return classReader.readBytes(payloadStart + 8, codeLength()); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundCharacterRange.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundCharacterRange.java new file mode 100644 index 0000000000000..902b861ac6361 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundCharacterRange.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.Label; +import jdk.internal.classfile.instruction.CharacterRange; + +public final class BoundCharacterRange + extends AbstractElement + implements CharacterRange { + + private final CodeImpl code; + private final int offset; + + public BoundCharacterRange(CodeImpl code, int offset) { + this.code = code; + this.offset = offset; + } + + int startPc() { + return code.classReader.readU2(offset); + } + + int endPc() { + return code.classReader.readU2(offset + 2); + } + + @Override + public int characterRangeStart() { + return code.classReader.readInt(offset + 4); + } + + @Override + public int characterRangeEnd() { + return code.classReader.readInt(offset + 8); + } + + @Override + public int flags() { + return code.classReader.readU2(offset + 12); + } + + @Override + public Label startScope() { + return code.getLabel(startPc()); + } + + @Override + public Label endScope() { + return code.getLabel(endPc() + 1); + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.addCharacterRange(this); + } + + @Override + public String toString() { + return String.format("CharacterRange[startScope=%s, endScope=%s, characterRangeStart=%s, characterRangeEnd=%s, flags=%d]", + startScope(), endScope(), characterRangeStart(), characterRangeEnd(), flags()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundLocalVariable.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundLocalVariable.java new file mode 100644 index 0000000000000..ff9284f979e53 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundLocalVariable.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import jdk.internal.classfile.attribute.LocalVariableInfo; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.instruction.LocalVariable; + +public final class BoundLocalVariable + extends AbstractBoundLocalVariable + implements LocalVariableInfo, + LocalVariable { + + public BoundLocalVariable(CodeImpl code, int offset) { + super(code, offset); + } + + @Override + public Utf8Entry type() { + return secondaryEntry(); + } + + @Override + public ClassDesc typeSymbol() { + return ClassDesc.ofDescriptor(type().stringValue()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariable(this); + } + + @Override + public String toString() { + return String.format("LocalVariable[name=%s, slot=%d, type=%s]", name().stringValue(), slot(), type().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundLocalVariableType.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundLocalVariableType.java new file mode 100644 index 0000000000000..563f543a565bd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundLocalVariableType.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.attribute.LocalVariableTypeInfo; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.instruction.LocalVariableType; + +public final class BoundLocalVariableType + extends AbstractBoundLocalVariable + implements LocalVariableTypeInfo, + LocalVariableType { + + public BoundLocalVariableType(CodeImpl code, int offset) { + super(code, offset); + } + + @Override + public Utf8Entry signature() { + return secondaryEntry(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariableType(this); + } + + @Override + public String toString() { + return String.format("LocalVariableType[name=%s, slot=%d, signature=%s]", name().stringValue(), slot(), signature().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundRecordComponentInfo.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundRecordComponentInfo.java new file mode 100644 index 0000000000000..c9b025affc098 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundRecordComponentInfo.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassReader; +import jdk.internal.classfile.attribute.RecordComponentInfo; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class BoundRecordComponentInfo + implements RecordComponentInfo { + + private final ClassReader reader; + private final int startPos, attributesPos; + private List> attributes; + + public BoundRecordComponentInfo(ClassReader reader, int startPos) { + this.reader = reader; + this.startPos = startPos; + attributesPos = startPos + 4; + } + + @Override + public Utf8Entry name() { + return reader.readUtf8Entry(startPos); + } + + @Override + public Utf8Entry descriptor() { + return reader.readUtf8Entry(startPos + 2); + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(null, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java new file mode 100644 index 0000000000000..cc1d132d6b024 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.WritableElement; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.PoolEntry; + +public final class BufWriterImpl implements BufWriter { + + private final ConstantPoolBuilder constantPool; + private LabelContext labelContext; + private ClassEntry thisClass; + byte[] elems; + int offset = 0; + + public BufWriterImpl(ConstantPoolBuilder constantPool) { + this(constantPool, 64); + } + + public BufWriterImpl(ConstantPoolBuilder constantPool, int initialSize) { + this.constantPool = constantPool; + elems = new byte[initialSize]; + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + public LabelContext labelContext() { + return labelContext; + } + + public void setLabelContext(LabelContext labelContext) { + this.labelContext = labelContext; + } + @Override + public boolean canWriteDirect(ConstantPool other) { + return constantPool.canWriteDirect(other); + } + + public ClassEntry thisClass() { + return thisClass; + } + + public void setThisClass(ClassEntry thisClass) { + this.thisClass = thisClass; + } + + @Override + public void writeU1(int x) { + writeIntBytes(1, x); + } + + @Override + public void writeU2(int x) { + writeIntBytes(2, x); + } + + @Override + public void writeInt(int x) { + writeIntBytes(4, x); + } + + @Override + public void writeFloat(float x) { + writeInt(Float.floatToIntBits(x)); + } + + @Override + public void writeLong(long x) { + writeIntBytes(8, x); + } + + @Override + public void writeDouble(double x) { + writeLong(Double.doubleToLongBits(x)); + } + + @Override + public void writeBytes(byte[] arr) { + writeBytes(arr, 0, arr.length); + } + + @Override + public void writeBytes(BufWriter other) { + BufWriterImpl o = (BufWriterImpl) other; + writeBytes(o.elems, 0, o.offset); + } + + @Override + public void writeBytes(byte[] arr, int start, int length) { + reserveSpace(length); + System.arraycopy(arr, start, elems, offset, length); + offset += length; + } + + @Override + public void patchInt(int offset, int size, int value) { + int prevOffset = this.offset; + this.offset = offset; + writeIntBytes(size, value); + this.offset = prevOffset; + } + + @Override + public void writeIntBytes(int intSize, long intValue) { + reserveSpace(intSize); + for (int i = 0; i < intSize; i++) { + elems[offset++] = (byte) ((intValue >> 8 * (intSize - i - 1)) & 0xFF); + } + } + + @Override + public void reserveSpace(int freeBytes) { + if (offset + freeBytes > elems.length) { + int newsize = elems.length * 2; + while (offset + freeBytes > newsize) { + newsize *= 2; + } + elems = Arrays.copyOf(elems, newsize); + } + } + + @Override + public int size() { + return offset; + } + + @Override + public ByteBuffer asByteBuffer() { + return ByteBuffer.wrap(elems, 0, offset); + } + + @Override + public void copyTo(byte[] array, int bufferOffset) { + System.arraycopy(elems, 0, array, bufferOffset, size()); + } + + // writeIndex methods ensure that any CP info written + // is relative to the correct constant pool + + @Override + public void writeIndex(PoolEntry entry) { + int idx = AbstractPoolEntry.maybeClone(constantPool, entry).index(); + if (idx < 1 || idx > Character.MAX_VALUE) + throw new IllegalArgumentException(idx + " is not a valid index. Entry: " + entry); + writeU2(idx); + } + + @Override + public void writeIndexOrZero(PoolEntry entry) { + if (entry == null || entry.index() == 0) + writeU2(0); + else + writeIndex(entry); + } + + @Override + public> void writeList(List list) { + writeU2(list.size()); + for (T t : list) { + t.writeTo(this); + } + } + + @Override + public void writeListIndices(List list) { + writeU2(list.size()); + for (PoolEntry info : list) { + writeIndex(info); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedCodeBuilder.java new file mode 100644 index 0000000000000..f9da397c707b1 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedCodeBuilder.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.IncrementInstruction; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public final class BufferedCodeBuilder + implements TerminalCodeBuilder, LabelContext { + private final SplitConstantPool constantPool; + private final List elements = new ArrayList<>(); + private final LabelImpl startLabel, endLabel; + private final CodeModel original; + private final MethodInfo methodInfo; + private boolean finished; + private int maxLocals; + + public BufferedCodeBuilder(MethodInfo methodInfo, + SplitConstantPool constantPool, + CodeModel original) { + this.constantPool = constantPool; + this.startLabel = new LabelImpl(this, -1); + this.endLabel = new LabelImpl(this, -1); + this.original = original; + this.methodInfo = methodInfo; + this.maxLocals = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodType().stringValue()); + if (original != null) + this.maxLocals = Math.max(this.maxLocals, original.maxLocals()); + + elements.add(startLabel); + } + + @Override + public Optional original() { + return Optional.ofNullable(original); + } + + @Override + public Label newLabel() { + return new LabelImpl(this, -1); + } + + @Override + public Label startLabel() { + return startLabel; + } + + @Override + public Label endLabel() { + return endLabel; + } + + @Override + public int receiverSlot() { + return methodInfo.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return methodInfo.parameterSlot(paramNo); + } + + public int curTopLocal() { + return maxLocals; + } + + @Override + public int allocateLocal(TypeKind typeKind) { + int retVal = maxLocals; + maxLocals += typeKind.slotSize(); + return retVal; + } + + @Override + public Label getLabel(int bci) { + throw new UnsupportedOperationException("Lookup by BCI not supported by BufferedCodeBuilder"); + } + + @Override + public int labelToBci(Label label) { + throw new UnsupportedOperationException("Label mapping not supported by BufferedCodeBuilder"); + } + + @Override + public void setLabelTarget(Label label, int bci) { + throw new UnsupportedOperationException("Label mapping not supported by BufferedCodeBuilder"); + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + @Override + public CodeBuilder with(CodeElement element) { + if (finished) + throw new IllegalStateException("Can't add elements after traversal"); + elements.add(element); + return this; + } + + @Override + public String toString() { + return String.format("CodeModel[id=%d]", System.identityHashCode(this)); + } + + public BufferedCodeBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + public CodeModel toModel() { + if (!finished) { + elements.add(endLabel); + finished = true; + } + return new Model(); + } + + public final class Model + extends AbstractUnboundModel + implements CodeModel { + + private Model() { + super(elements); + } + + @Override + public List exceptionHandlers() { + return elements.stream() + .filter(x -> x instanceof ExceptionCatch) + .map(x -> (ExceptionCatch) x) + .toList(); + } + + @Override + public int maxLocals() { + for (CodeElement element : elements) { + if (element instanceof LoadInstruction i) + maxLocals = Math.max(maxLocals, i.slot() + i.typeKind().slotSize()); + else if (element instanceof StoreInstruction i) + maxLocals = Math.max(maxLocals, i.slot() + i.typeKind().slotSize()); + else if (element instanceof IncrementInstruction i) + maxLocals = Math.max(maxLocals, i.slot() + 1); + } + return maxLocals; + } + + @Override + public int maxStack() { + throw new UnsupportedOperationException("nyi"); + } + + @Override + public Optional parent() { + return Optional.empty(); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.withCode(new Consumer<>() { + @Override + public void accept(CodeBuilder cb) { + forEachElement(cb); + } + }); + } + + public void writeTo(BufWriter buf) { + DirectCodeBuilder.build(methodInfo, cb -> elements.forEach(cb), constantPool, null).writeTo(buf); + } + + @Override + public String toString() { + return String.format("CodeModel[id=%s]", Integer.toHexString(System.identityHashCode(this))); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedFieldBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedFieldBuilder.java new file mode 100644 index 0000000000000..598ae3dc58f5d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedFieldBuilder.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class BufferedFieldBuilder + implements TerminalFieldBuilder { + private final SplitConstantPool constantPool; + private final Utf8Entry name; + private final Utf8Entry desc; + private final List elements = new ArrayList<>(); + private AccessFlags flags; + private final FieldModel original; + + public BufferedFieldBuilder(SplitConstantPool constantPool, + Utf8Entry name, + Utf8Entry type, + FieldModel original) { + this.constantPool = constantPool; + this.name = name; + this.desc = type; + this.flags = AccessFlags.ofField(); + this.original = original; + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + @Override + public Optional original() { + return Optional.ofNullable(original); + } + + @Override + public FieldBuilder with(FieldElement element) { + elements.add(element); + if (element instanceof AccessFlags f) this.flags = f; + return this; + } + + public BufferedFieldBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + public FieldModel toModel() { + return new Model(); + } + + public final class Model + extends AbstractUnboundModel + implements FieldModel { + public Model() { + super(elements); + } + + @Override + public Optional parent() { + FieldModel fm = original().orElse(null); + return fm == null? Optional.empty() : fm.parent(); + } + + @Override + public AccessFlags flags() { + return flags; + } + + @Override + public Utf8Entry fieldName() { + return name; + } + + @Override + public Utf8Entry fieldType() { + return desc; + } + + @Override + public void writeTo(BufWriter buf) { + DirectFieldBuilder fb = new DirectFieldBuilder(constantPool, name, desc, null); + elements.forEach(fb); + fb.writeTo(buf); + } + + @Override + public String toString() { + return String.format("FieldModel[fieldName=%s, fieldType=%s, flags=%d]", name.stringValue(), desc.stringValue(), flags.flagsMask()); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedMethodBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedMethodBuilder.java new file mode 100644 index 0000000000000..f147a04c144e6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufferedMethodBuilder.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.AccessFlags; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class BufferedMethodBuilder + implements TerminalMethodBuilder, MethodInfo { + private final List elements; + private final SplitConstantPool constantPool; + private final Utf8Entry name; + private final Utf8Entry desc; + private AccessFlags flags; + private final MethodModel original; + private int[] parameterSlots; + + public BufferedMethodBuilder(SplitConstantPool constantPool, + Utf8Entry nameInfo, + Utf8Entry typeInfo, + MethodModel original) { + this.elements = new ArrayList<>(); + this.constantPool = constantPool; + this.name = nameInfo; + this.desc = typeInfo; + this.flags = AccessFlags.ofMethod(); + this.original = original; + } + + @Override + public MethodBuilder with(MethodElement element) { + elements.add(element); + if (element instanceof AccessFlags f) this.flags = f; + return this; + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + @Override + public Optional original() { + return Optional.ofNullable(original); + } + + @Override + public Utf8Entry methodName() { + return name; + } + + @Override + public Utf8Entry methodType() { + return desc; + } + + @Override + public int methodFlags() { + return flags.flagsMask(); + } + + @Override + public int parameterSlot(int paramNo) { + if (parameterSlots == null) + parameterSlots = Util.parseParameterSlots(methodFlags(), methodType().stringValue()); + return parameterSlots[paramNo]; + } + + @Override + public MethodBuilder withCode(Consumer handler) { + return with(new BufferedCodeBuilder(this, constantPool, null) + .run(handler) + .toModel()); + } + + @Override + public MethodBuilder transformCode(CodeModel code, CodeTransform transform) { + BufferedCodeBuilder builder = new BufferedCodeBuilder(this, constantPool, code); + builder.transform(code, transform); + return with(builder.toModel()); + } + + @Override + public BufferedCodeBuilder bufferedCodeBuilder(CodeModel original) { + return new BufferedCodeBuilder(this, constantPool, original); + } + + public BufferedMethodBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + public MethodModel toModel() { + return new Model(); + } + + public final class Model + extends AbstractUnboundModel + implements MethodModel, MethodInfo { + public Model() { + super(elements); + } + + @Override + public AccessFlags flags() { + return flags; + } + + @Override + public Optional parent() { + return original().flatMap(MethodModel::parent); + } + + @Override + public Utf8Entry methodName() { + return name; + } + + @Override + public Utf8Entry methodType() { + return desc; + } + + @Override + public int methodFlags() { + return flags.flagsMask(); + } + + @Override + public int parameterSlot(int paramNo) { + return BufferedMethodBuilder.this.parameterSlot(paramNo); + } + + @Override + public Optional code() { + throw new UnsupportedOperationException("nyi"); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.withMethod(methodName(), methodType(), methodFlags(), new Consumer<>() { + @Override + public void accept(MethodBuilder mb) { + forEachElement(mb); + } + }); + } + + @Override + public void writeTo(BufWriter buf) { + DirectMethodBuilder mb = new DirectMethodBuilder(constantPool, name, desc, methodFlags(), null); + elements.forEach(mb); + mb.writeTo(buf); + } + + @Override + public String toString() { + return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]", + name.stringValue(), desc.stringValue(), flags.flagsMask()); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java new file mode 100644 index 0000000000000..8e3ec3016ebbd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandleInfo; +import java.util.ArrayList; +import java.util.List; + +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; + +public class BytecodeHelpers { + + private BytecodeHelpers() { + } + + public static Opcode loadOpcode(TypeKind tk, int slot) { + return switch (tk) { + case IntType, ShortType, ByteType, CharType, BooleanType -> switch (slot) { + case 0 -> Opcode.ILOAD_0; + case 1 -> Opcode.ILOAD_1; + case 2 -> Opcode.ILOAD_2; + case 3 -> Opcode.ILOAD_3; + default -> (slot < 256) ? Opcode.ILOAD : Opcode.ILOAD_W; + }; + case LongType -> switch (slot) { + case 0 -> Opcode.LLOAD_0; + case 1 -> Opcode.LLOAD_1; + case 2 -> Opcode.LLOAD_2; + case 3 -> Opcode.LLOAD_3; + default -> (slot < 256) ? Opcode.LLOAD : Opcode.LLOAD_W; + }; + case DoubleType -> switch (slot) { + case 0 -> Opcode.DLOAD_0; + case 1 -> Opcode.DLOAD_1; + case 2 -> Opcode.DLOAD_2; + case 3 -> Opcode.DLOAD_3; + default -> (slot < 256) ? Opcode.DLOAD : Opcode.DLOAD_W; + }; + case FloatType -> switch (slot) { + case 0 -> Opcode.FLOAD_0; + case 1 -> Opcode.FLOAD_1; + case 2 -> Opcode.FLOAD_2; + case 3 -> Opcode.FLOAD_3; + default -> (slot < 256) ? Opcode.FLOAD : Opcode.FLOAD_W; + }; + case ReferenceType -> switch (slot) { + case 0 -> Opcode.ALOAD_0; + case 1 -> Opcode.ALOAD_1; + case 2 -> Opcode.ALOAD_2; + case 3 -> Opcode.ALOAD_3; + default -> (slot < 256) ? Opcode.ALOAD : Opcode.ALOAD_W; + }; + case VoidType -> throw new IllegalArgumentException("void"); + }; + } + + public static Opcode storeOpcode(TypeKind tk, int slot) { + return switch (tk) { + case IntType, ShortType, ByteType, CharType, BooleanType -> switch (slot) { + case 0 -> Opcode.ISTORE_0; + case 1 -> Opcode.ISTORE_1; + case 2 -> Opcode.ISTORE_2; + case 3 -> Opcode.ISTORE_3; + default -> (slot < 256) ? Opcode.ISTORE : Opcode.ISTORE_W; + }; + case LongType -> switch (slot) { + case 0 -> Opcode.LSTORE_0; + case 1 -> Opcode.LSTORE_1; + case 2 -> Opcode.LSTORE_2; + case 3 -> Opcode.LSTORE_3; + default -> (slot < 256) ? Opcode.LSTORE : Opcode.LSTORE_W; + }; + case DoubleType -> switch (slot) { + case 0 -> Opcode.DSTORE_0; + case 1 -> Opcode.DSTORE_1; + case 2 -> Opcode.DSTORE_2; + case 3 -> Opcode.DSTORE_3; + default -> (slot < 256) ? Opcode.DSTORE : Opcode.DSTORE_W; + }; + case FloatType -> switch (slot) { + case 0 -> Opcode.FSTORE_0; + case 1 -> Opcode.FSTORE_1; + case 2 -> Opcode.FSTORE_2; + case 3 -> Opcode.FSTORE_3; + default -> (slot < 256) ? Opcode.FSTORE : Opcode.FSTORE_W; + }; + case ReferenceType -> switch (slot) { + case 0 -> Opcode.ASTORE_0; + case 1 -> Opcode.ASTORE_1; + case 2 -> Opcode.ASTORE_2; + case 3 -> Opcode.ASTORE_3; + default -> (slot < 256) ? Opcode.ASTORE : Opcode.ASTORE_W; + }; + case VoidType -> throw new IllegalArgumentException("void"); + }; + } + + public static Opcode returnOpcode(TypeKind tk) { + return switch (tk) { + case ByteType, ShortType, IntType, CharType, BooleanType -> Opcode.IRETURN; + case FloatType -> Opcode.FRETURN; + case LongType -> Opcode.LRETURN; + case DoubleType -> Opcode.DRETURN; + case ReferenceType -> Opcode.ARETURN; + case VoidType -> Opcode.RETURN; + }; + } + + public static Opcode arrayLoadOpcode(TypeKind tk) { + return switch (tk) { + case ByteType, BooleanType -> Opcode.BALOAD; + case ShortType -> Opcode.SALOAD; + case IntType -> Opcode.IALOAD; + case FloatType -> Opcode.FALOAD; + case LongType -> Opcode.LALOAD; + case DoubleType -> Opcode.DALOAD; + case ReferenceType -> Opcode.AALOAD; + case CharType -> Opcode.CALOAD; + case VoidType -> throw new IllegalArgumentException("void not an allowable array type"); + }; + } + + public static Opcode arrayStoreOpcode(TypeKind tk) { + return switch (tk) { + case ByteType, BooleanType -> Opcode.BASTORE; + case ShortType -> Opcode.SASTORE; + case IntType -> Opcode.IASTORE; + case FloatType -> Opcode.FASTORE; + case LongType -> Opcode.LASTORE; + case DoubleType -> Opcode.DASTORE; + case ReferenceType -> Opcode.AASTORE; + case CharType -> Opcode.CASTORE; + case VoidType -> throw new IllegalArgumentException("void not an allowable array type"); + }; + } + + public static Opcode reverseBranchOpcode(Opcode op) { + return switch (op) { + case IFEQ -> Opcode.IFNE; + case IFNE -> Opcode.IFEQ; + case IFLT -> Opcode.IFGE; + case IFGE -> Opcode.IFLT; + case IFGT -> Opcode.IFLE; + case IFLE -> Opcode.IFGT; + case IF_ICMPEQ -> Opcode.IF_ICMPNE; + case IF_ICMPNE -> Opcode.IF_ICMPEQ; + case IF_ICMPLT -> Opcode.IF_ICMPGE; + case IF_ICMPGE -> Opcode.IF_ICMPLT; + case IF_ICMPGT -> Opcode.IF_ICMPLE; + case IF_ICMPLE -> Opcode.IF_ICMPGT; + case IF_ACMPEQ -> Opcode.IF_ACMPNE; + case IF_ACMPNE -> Opcode.IF_ACMPEQ; + case IFNULL -> Opcode.IFNONNULL; + case IFNONNULL -> Opcode.IFNULL; + default -> throw new IllegalArgumentException("Unknown branch instruction: " + op); + }; + } + + public static Opcode convertOpcode(TypeKind from, TypeKind to) { + return switch (from) { + case IntType -> + switch (to) { + case LongType -> Opcode.I2L; + case FloatType -> Opcode.I2F; + case DoubleType -> Opcode.I2D; + case ByteType -> Opcode.I2B; + case CharType -> Opcode.I2C; + case ShortType -> Opcode.I2S; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + case LongType -> + switch (to) { + case FloatType -> Opcode.L2F; + case DoubleType -> Opcode.L2D; + case IntType -> Opcode.L2I; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + case DoubleType -> + switch (to) { + case FloatType -> Opcode.D2F; + case LongType -> Opcode.D2L; + case IntType -> Opcode.D2I; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + case FloatType -> + switch (to) { + case LongType -> Opcode.F2L; + case DoubleType -> Opcode.F2D; + case IntType -> Opcode.F2I; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + } + + static void validateSIPUSH(ConstantDesc d) { + if (d instanceof Integer iVal && Short.MIN_VALUE <= iVal && iVal <= Short.MAX_VALUE) + return; + + if (d instanceof Long lVal && Short.MIN_VALUE <= lVal && Short.MAX_VALUE <= lVal) + return; + + throw new IllegalArgumentException("SIPUSH: value must be within: Short.MIN_VALUE <= value <= Short.MAX_VALUE" + + ", found: " + d); + } + + static void validateBIPUSH(ConstantDesc d) { + if (d instanceof Integer iVal && Byte.MIN_VALUE <= iVal && iVal <= Byte.MAX_VALUE) + return; + + if (d instanceof Long lVal && Byte.MIN_VALUE <= lVal && Byte.MAX_VALUE <= lVal) + return; + + throw new IllegalArgumentException("BIPUSH: value must be within: Byte.MIN_VALUE <= value <= Byte.MAX_VALUE" + + ", found: " + d); + } + + public static MethodHandleEntry handleDescToHandleInfo(ConstantPoolBuilder constantPool, DirectMethodHandleDesc bootstrapMethod) { + ClassEntry bsOwner = constantPool.classEntry(bootstrapMethod.owner()); + NameAndTypeEntry bsNameAndType = constantPool.nameAndTypeEntry(constantPool.utf8Entry(bootstrapMethod.methodName()), + constantPool.utf8Entry(bootstrapMethod.lookupDescriptor())); + int bsRefKind = bootstrapMethod.refKind(); + MemberRefEntry bsReference = toBootstrapMemberRef(constantPool, bsRefKind, bsOwner, bsNameAndType, bootstrapMethod.isOwnerInterface()); + + return constantPool.methodHandleEntry(bsRefKind, bsReference); + } + + static MemberRefEntry toBootstrapMemberRef(ConstantPoolBuilder constantPool, int bsRefKind, ClassEntry owner, NameAndTypeEntry nat, boolean isOwnerInterface) { + return isOwnerInterface + ? constantPool.interfaceMethodRefEntry(owner, nat) + : bsRefKind <= MethodHandleInfo.REF_putStatic + ? constantPool.fieldRefEntry(owner, nat) + : constantPool.methodRefEntry(owner, nat); + } + + static ConstantDynamicEntry handleConstantDescToHandleInfo(ConstantPoolBuilder constantPool, DynamicConstantDesc desc) { + ConstantDesc[] bootstrapArgs = desc.bootstrapArgs(); + List staticArgs = new ArrayList<>(bootstrapArgs.length); + for (ConstantDesc bootstrapArg : bootstrapArgs) + staticArgs.add(constantPool.loadableConstantEntry(bootstrapArg)); + + var bootstrapDesc = desc.bootstrapMethod(); + ClassEntry bsOwner = constantPool.classEntry(bootstrapDesc.owner()); + NameAndTypeEntry bsNameAndType = constantPool.nameAndTypeEntry(bootstrapDesc.methodName(), + bootstrapDesc.invocationType()); + int bsRefKind = bootstrapDesc.refKind(); + + MemberRefEntry memberRefEntry = toBootstrapMemberRef(constantPool, bsRefKind, bsOwner, bsNameAndType, bootstrapDesc.isOwnerInterface()); + MethodHandleEntry methodHandleEntry = constantPool.methodHandleEntry(bsRefKind, memberRefEntry); + BootstrapMethodEntry bme = constantPool.bsmEntry(methodHandleEntry, staticArgs); + return constantPool.constantDynamicEntry(bme, + constantPool.nameAndTypeEntry(desc.constantName(), + desc.constantType())); + } + + public static void validateValue(Opcode opcode, ConstantDesc v) { + switch (opcode) { + case ACONST_NULL -> { + if (v != null && v != ConstantDescs.NULL) + throw new IllegalArgumentException("value must be null or ConstantDescs.NULL with opcode ACONST_NULL"); + } + case SIPUSH -> + validateSIPUSH(v); + case BIPUSH -> + validateBIPUSH(v); + case LDC, LDC_W, LDC2_W -> { + if (v == null) + throw new IllegalArgumentException("`null` must use ACONST_NULL"); + } + default -> { + var exp = opcode.constantValue(); + if (exp == null) + throw new IllegalArgumentException("Can not use Opcode: " + opcode + " with constant()"); + if (v == null || !(v.equals(exp) || (exp instanceof Long l && v.equals(l.intValue())))) { + var t = (exp instanceof Long) ? "L" : (exp instanceof Float) ? "f" : (exp instanceof Double) ? "d" : ""; + throw new IllegalArgumentException("value must be " + exp + t + " with opcode " + opcode.name()); + } + } + } + } + + public static LoadableConstantEntry constantEntry(ConstantPoolBuilder constantPool, + ConstantDesc constantValue) { + // this method is invoked during JVM bootstrap - cannot use pattern switch + if (constantValue instanceof Integer value) { + return constantPool.intEntry(value); + } + if (constantValue instanceof String value) { + return constantPool.stringEntry(value); + } + if (constantValue instanceof ClassDesc value) { + return constantPool.classEntry(value); + } + if (constantValue instanceof Long value) { + return constantPool.longEntry(value); + } + if (constantValue instanceof Float value) { + return constantPool.floatEntry(value); + } + if (constantValue instanceof Double value) { + return constantPool.doubleEntry(value); + } + if (constantValue instanceof MethodTypeDesc value) { + return constantPool.methodTypeEntry(value); + } + if (constantValue instanceof DirectMethodHandleDesc value) { + return handleDescToHandleInfo(constantPool, value); + } if (constantValue instanceof DynamicConstantDesc value) { + return handleConstantDescToHandleInfo(constantPool, value); + } + throw new UnsupportedOperationException("not yet: " + constantValue); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java new file mode 100644 index 0000000000000..4d594fb7c7cc4 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { + final CodeBuilder b; + final BlockCodeBuilderImpl tryBlock; + final Label tryCatchEnd; + final Set catchTypes; + BlockCodeBuilderImpl catchBlock; + + public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilderImpl tryBlock, Label tryCatchEnd) { + this.b = b; + this.tryBlock = tryBlock; + this.tryCatchEnd = tryCatchEnd; + this.catchTypes = new HashSet<>(); + } + + @Override + public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler) { + return catchingMulti(exceptionType == null ? List.of() : List.of(exceptionType), catchHandler); + } + + @Override + public CodeBuilder.CatchBuilder catchingMulti(List exceptionTypes, Consumer catchHandler) { + Objects.requireNonNull(exceptionTypes); + Objects.requireNonNull(catchHandler); + + if (catchBlock == null) { + if (tryBlock.reachable()) { + b.branchInstruction(Opcode.GOTO, tryCatchEnd); + } + } + + for (var exceptionType : exceptionTypes) { + if (!catchTypes.add(exceptionType)) { + throw new IllegalArgumentException("Existing catch block catches exception of type: " + exceptionType); + } + } + + // Finish prior catch block + if (catchBlock != null) { + catchBlock.end(); + if (catchBlock.reachable()) { + b.branchInstruction(Opcode.GOTO, tryCatchEnd); + } + } + + catchBlock = new BlockCodeBuilderImpl(b, tryCatchEnd); + Label tryStart = tryBlock.startLabel(); + Label tryEnd = tryBlock.endLabel(); + if (exceptionTypes.isEmpty()) { + catchBlock.exceptionCatchAll(tryStart, tryEnd, catchBlock.startLabel()); + } + else { + for (var exceptionType : exceptionTypes) { + catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), exceptionType); + } + } + catchBlock.start(); + catchHandler.accept(catchBlock); + + return this; + } + + @Override + public void catchingAll(Consumer catchAllHandler) { + catchingMulti(List.of(), catchAllHandler); + } + + public void finish() { + if (catchBlock != null) { + catchBlock.end(); + } + b.labelBinding(tryCatchEnd); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedClassBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedClassBuilder.java new file mode 100644 index 0000000000000..6c76dbcbcffb4 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedClassBuilder.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class ChainedClassBuilder + implements ClassBuilder, Consumer { + private final ClassBuilder downstream; + private final DirectClassBuilder terminal; + private final Consumer consumer; + + public ChainedClassBuilder(ClassBuilder downstream, + Consumer consumer) { + this.downstream = downstream; + this.consumer = consumer; + this.terminal = switch (downstream) { + case ChainedClassBuilder cb -> cb.terminal; + case DirectClassBuilder db -> db; + }; + } + + @Override + public ClassBuilder with(ClassElement element) { + consumer.accept(element); + return this; + } + + @Override + public Optional original() { + return terminal.original(); + } + + @Override + public ClassBuilder withField(Utf8Entry name, Utf8Entry descriptor, Consumer handler) { + return downstream.with(new BufferedFieldBuilder(terminal.constantPool, + name, descriptor, null) + .run(handler) + .toModel()); + } + + @Override + public ClassBuilder transformField(FieldModel field, FieldTransform transform) { + BufferedFieldBuilder builder = new BufferedFieldBuilder(terminal.constantPool, + field.fieldName(), field.fieldType(), + field); + builder.transform(field, transform); + return downstream.with(builder.toModel()); + } + + @Override + public ClassBuilder withMethod(Utf8Entry name, Utf8Entry descriptor, int flags, + Consumer handler) { + return downstream.with(new BufferedMethodBuilder(terminal.constantPool, + name, descriptor, null) + .run(handler) + .toModel()); + } + + @Override + public ClassBuilder transformMethod(MethodModel method, MethodTransform transform) { + BufferedMethodBuilder builder = new BufferedMethodBuilder(terminal.constantPool, + method.methodName(), method.methodType(), method); + builder.transform(method, transform); + return downstream.with(builder.toModel()); + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedCodeBuilder.java new file mode 100644 index 0000000000000..2e029fbc9a9c6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedCodeBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.Label; + +import java.util.function.Consumer; + +public final class ChainedCodeBuilder + extends NonterminalCodeBuilder + implements CodeBuilder { + private final Consumer consumer; + + public ChainedCodeBuilder(CodeBuilder downstream, + Consumer consumer) { + super(downstream); + this.consumer = consumer; + } + + @Override + public Label startLabel() { + return terminal.startLabel(); + } + + @Override + public Label endLabel() { + return terminal.endLabel(); + } + + @Override + public int allocateLocal(TypeKind typeKind) { + return parent.allocateLocal(typeKind); + } + + @Override + public CodeBuilder with(CodeElement element) { + consumer.accept(element); + return this; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedFieldBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedFieldBuilder.java new file mode 100644 index 0000000000000..0acd1a18b97f3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedFieldBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.FieldBuilder; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; + +public final class ChainedFieldBuilder implements FieldBuilder { + private final TerminalFieldBuilder terminal; + private final Consumer consumer; + + public ChainedFieldBuilder(FieldBuilder downstream, + Consumer consumer) { + this.consumer = consumer; + this.terminal = switch (downstream) { + case ChainedFieldBuilder cb -> cb.terminal; + case TerminalFieldBuilder tb -> tb; + }; + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + + @Override + public Optional original() { + return terminal.original(); + } + + @Override + public FieldBuilder with(FieldElement element) { + consumer.accept(element); + return this; + } + +} + diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedMethodBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedMethodBuilder.java new file mode 100644 index 0000000000000..cbac818f53da3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ChainedMethodBuilder.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; + +public final class ChainedMethodBuilder implements MethodBuilder { + final MethodBuilder downstream; + final TerminalMethodBuilder terminal; + final Consumer consumer; + + public ChainedMethodBuilder(MethodBuilder downstream, + Consumer consumer) { + this.downstream = downstream; + this.consumer = consumer; + this.terminal = switch (downstream) { + case ChainedMethodBuilder cb -> cb.terminal; + case TerminalMethodBuilder tb -> tb; + }; + } + + @Override + public MethodBuilder with(MethodElement element) { + consumer.accept(element); + return this; + } + + @Override + public MethodBuilder withCode(Consumer handler) { + return downstream.with(terminal.bufferedCodeBuilder(null) + .run(handler) + .toModel()); + } + + @Override + public MethodBuilder transformCode(CodeModel code, CodeTransform transform) { + BufferedCodeBuilder builder = terminal.bufferedCodeBuilder(code); + builder.transform(code, transform); + return downstream.with(builder.toModel()); + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + + @Override + public Optional original() { + return terminal.original(); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java new file mode 100644 index 0000000000000..fb697e1df059d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.impl; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.InputStream; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import jdk.internal.classfile.ClassHierarchyResolver; + +/** + * Class hierarchy resolution framework is answering questions about classes assignability, common classes ancestor and whether the class represents an interface. + * All the requests are handled without class loading nor full verification, optionally with incomplete dependencies and with focus on maximum performance. + * + */ +public final class ClassHierarchyImpl { + + private final ClassHierarchyResolver resolver; + + //defer initialization of logging until needed + private static System.Logger logger; + + /** + * Public constructor of ClassHierarchyImpl accepting instances of ClassHierarchyInfoResolver to resolve individual class streams. + * @param classHierarchyResolver ClassHierarchyInfoResolver instance + */ + public ClassHierarchyImpl(ClassHierarchyResolver classHierarchyResolver) { + this.resolver = classHierarchyResolver; + } + + private ClassHierarchyResolver.ClassHierarchyInfo resolve(ClassDesc classDesc) { + var res = resolver.getClassInfo(classDesc); + if (res != null) return res; + //maybe throw an exception here to avoid construction of potentially invalid stack maps + if (logger == null) + logger = System.getLogger("jdk.internal.classfile"); + if (logger.isLoggable(System.Logger.Level.DEBUG)) + logger.log(System.Logger.Level.DEBUG, "Could not resolve class " + classDesc.displayName()); + return new ClassHierarchyResolver.ClassHierarchyInfo(classDesc, false, null); + } + + /** + * Method answering question whether given class is an interface, + * responding without the class stream resolution and parsing is preferred in case the interface status is known from previous activities. + * @param classDesc class path in form of <package>/<class_name>.class + * @return true if the given class name represents an interface + */ + public boolean isInterface(ClassDesc classDesc) { + return resolve(classDesc).isInterface(); + } + + /** + * Method resolving common ancestor of two classes + * @param symbol1 first class descriptor + * @param symbol2 second class descriptor + * @return common ancestor class name or null if it could not be identified + */ + public ClassDesc commonAncestor(ClassDesc symbol1, ClassDesc symbol2) { + //calculation of common ancestor is a robust (yet fast) way to decide about assignability in incompletely resolved class hierarchy + //exact order of symbol loops is critical for performance of the above isAssignableFrom method, so standard situations are resolved in linear time + //this method returns null if common ancestor could not be identified + if (isInterface(symbol1) || isInterface(symbol2)) return ConstantDescs.CD_Object; + for (var s1 = symbol1; s1 != null; s1 = resolve(s1).superClass()) { + for (var s2 = symbol2; s2 != null; s2 = resolve(s2).superClass()) { + if (s1.equals(s2)) return s1; + } + } + return null; + } + + public boolean isAssignableFrom(ClassDesc thisClass, ClassDesc fromClass) { + //extra check if fromClass is an interface is necessay to handle situation when thisClass might not been fully resolved and so it is potentially an unidentified interface + //this special corner-case handling has been added based on better success rate of constructing stack maps with simulated broken resulution of classes and interfaces + if (isInterface(fromClass)) return resolve(thisClass).superClass() == null; + //regular calculation of assignability is based on common ancestor calculation + var anc = commonAncestor(thisClass, fromClass); + //if common ancestor does not exist (as the class hierarchy could not be fully resolved) we optimistically assume the classes might be accessible + //if common ancestor is equal to thisClass then the classes are clearly accessible + //if other common ancestor is calculated (which works even when their grand-parents could not be resolved) then it is clear that thisClass could not be asigned from fromClass + return anc == null || thisClass.equals(anc); + } + + public static final class CachedClassHierarchyResolver implements ClassHierarchyResolver { + + private final Function streamProvider; + private final Map resolvedCache; + + public CachedClassHierarchyResolver(Function classStreamProvider) { + this.streamProvider = classStreamProvider; + this.resolvedCache = Collections.synchronizedMap(new HashMap<>()); + } + + + // resolve method looks for the class file using ClassStreamResolver instance and tries to briefly scan it just for minimal information necessary + // minimal information includes: identification of the class as interface, obtaining its superclass name and identification of all potential interfaces (to avoid unnecessary future resolutions of them) + // empty ClInfo is stored in case of an exception to avoid repeated scanning failures + @Override + public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + var res = resolvedCache.get(classDesc); + //additional test for null value is important to avoid repeated resolution attempts + if (res == null && !resolvedCache.containsKey(classDesc)) { + var ci = streamProvider.apply(classDesc); + if (ci != null) { + try (var in = new DataInputStream(new BufferedInputStream(ci))) { + in.skipBytes(8); + int cpLength = in.readUnsignedShort(); + String[] cpStrings = new String[cpLength]; + int[] cpClasses = new int[cpLength]; + for (int i=1; i cpStrings[i] = in.readUTF(); + case 7 -> cpClasses[i] = in.readUnsignedShort(); + case 8, 16, 19, 20 -> in.skipBytes(2); + case 15 -> in.skipBytes(3); + case 3, 4, 9, 10, 11, 12, 17, 18 -> in.skipBytes(4); + case 5, 6 -> {in.skipBytes(8); i++;} + } + } + boolean isInterface = (in.readUnsignedShort() & 0x0200) != 0; + in.skipBytes(2); + int superIndex = in.readUnsignedShort(); + var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null; + res = new ClassHierarchyInfo(classDesc, isInterface, superClass); + int interfCount = in.readUnsignedShort(); + for (int i=0; i map; + + public StaticClassHierarchyResolver(Collection interfaceNames, Map classToSuperClass) { + map = new HashMap<>(interfaceNames.size() + classToSuperClass.size()); + for (var e : classToSuperClass.entrySet()) + map.put(e.getKey(), new ClassHierarchyInfo(e.getKey(), false, e.getValue())); + for (var i : interfaceNames) + map.put(i, new ClassHierarchyInfo(i, true, null)); + } + + @Override + public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + return map.get(classDesc); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassImpl.java new file mode 100644 index 0000000000000..cdb0b3149e88c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassImpl.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.constantpool.ClassEntry; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.AccessFlags; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.AttributeMapper; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassReader; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.ClassfileVersion; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.Interfaces; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.Superclass; +import jdk.internal.access.SharedSecrets; + +public final class ClassImpl + extends AbstractElement + implements ClassModel { + + final ClassReader reader; + private final int attributesPos; + private final List methods; + private final List fields; + private List> attributes; + private List interfaces; + + public ClassImpl(byte[] cfbytes, + Collection options) { + this.reader = new ClassReaderImpl(cfbytes, options); + ClassReaderImpl reader = (ClassReaderImpl) this.reader; + int p = reader.interfacesPos; + int icnt = reader.readU2(p); + p += 2 + icnt * 2; + int fcnt = reader.readU2(p); + FieldImpl[] fields = new FieldImpl[fcnt]; + p += 2; + for (int i = 0; i < fcnt; ++i) { + int startPos = p; + int attrStart = p + 6; + p = reader.skipAttributeHolder(attrStart); + fields[i] = new FieldImpl(reader, startPos, p, attrStart); + } + this.fields = List.of(fields); + int mcnt = reader.readU2(p); + MethodImpl[] methods = new MethodImpl[mcnt]; + p += 2; + for (int i = 0; i < mcnt; ++i) { + int startPos = p; + int attrStart = p + 6; + p = reader.skipAttributeHolder(attrStart); + methods[i] = new MethodImpl(reader, startPos, p, attrStart); + } + this.methods = List.of(methods); + this.attributesPos = p; + reader.setContainedClass(this); + } + + @Override + public AccessFlags flags() { + return AccessFlags.ofClass(reader.flags()); + } + + @Override + public int majorVersion() { + return reader.readU2(6); + } + + @Override + public int minorVersion() { + return reader.readU2(4); + } + + @Override + public ConstantPool constantPool() { + return reader; + } + + @Override + public ClassEntry thisClass() { + return reader.thisClassEntry(); + } + + @Override + public Optional superclass() { + return reader.superclassEntry(); + } + + @Override + public List interfaces() { + if (interfaces == null) { + int pos = reader.thisClassPos() + 4; + int cnt = reader.readU2(pos); + pos += 2; + var arr = new Object[cnt]; + for (int i = 0; i < cnt; ++i) { + arr[i] = reader.readClassEntry(pos); + pos += 2; + } + this.interfaces = SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(arr); + } + return interfaces; + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } + + // ClassModel + + @Override + public void forEachElement(Consumer consumer) { + consumer.accept(flags()); + consumer.accept(ClassfileVersion.of(majorVersion(), minorVersion())); + superclass().ifPresent(new Consumer() { + @Override + public void accept(ClassEntry entry) { + consumer.accept(Superclass.of(entry)); + } + }); + consumer.accept(Interfaces.of(interfaces())); + fields().forEach(consumer); + methods().forEach(consumer); + for (Attribute attr : attributes()) { + if (attr instanceof ClassElement e) + consumer.accept(e); + } + } + + @Override + public byte[] transform(ClassTransform transform) { + ConstantPoolBuilder constantPool = ConstantPoolBuilder.of(this); + return Classfile.build(thisClass(), constantPool, + new Consumer() { + @Override + public void accept(ClassBuilder builder) { + ((DirectClassBuilder) builder).setOriginal(ClassImpl.this); + ((DirectClassBuilder) builder).setSizeHint(reader.classfileLength()); + builder.transform(ClassImpl.this, transform); + } + }); + } + + @Override + public List fields() { + return fields; + } + + @Override + public List methods() { + return methods; + } + + @Override + public boolean isModuleInfo() { + AccessFlags flags = flags(); + // move to where? + return flags.has(AccessFlag.MODULE) + && majorVersion() >= Classfile.JAVA_9_VERSION + && thisClass().asInternalName().equals("module-info") + && (superclass().isEmpty()) + && interfaces().isEmpty() + && fields().isEmpty() + && methods().isEmpty() + && verifyModuleAttributes(); + } + + @Override + public String toString() { + return String.format("ClassModel[thisClass=%s, flags=%d]", thisClass().name().stringValue(), flags().flagsMask()); + } + + private boolean verifyModuleAttributes() { + if (findAttribute(Attributes.MODULE).isEmpty()) + return false; + + Set> found = attributes().stream() + .map(Attribute::attributeMapper) + .collect(Collectors.toSet()); + + found.removeAll(allowedModuleAttributes); + found.retainAll(Attributes.PREDEFINED_ATTRIBUTES); + return found.isEmpty(); + } + + private static final Set> allowedModuleAttributes + = Set.of(Attributes.MODULE, + Attributes.MODULE_HASHES, + Attributes.MODULE_MAIN_CLASS, + Attributes.MODULE_PACKAGES, + Attributes.MODULE_RESOLUTION, + Attributes.MODULE_TARGET, + Attributes.INNER_CLASSES, + Attributes.SOURCE_FILE, + Attributes.SOURCE_DEBUG_EXTENSION, + Attributes.RUNTIME_VISIBLE_ANNOTATIONS, + Attributes.RUNTIME_INVISIBLE_ANNOTATIONS); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java new file mode 100644 index 0000000000000..795c7d0816b13 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java @@ -0,0 +1,1056 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.reflect.AccessFlag; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import jdk.internal.classfile.Annotation; + +import jdk.internal.classfile.AnnotationElement; +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.AnnotationValue.*; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.components.ClassPrinter.*; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.TypeAnnotation; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.attribute.StackMapFrameInfo.*; +import jdk.internal.classfile.constantpool.*; +import jdk.internal.classfile.instruction.*; + +import static jdk.internal.classfile.Classfile.*; +import jdk.internal.classfile.CompoundElement; +import jdk.internal.classfile.FieldModel; +import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.*; + +public final class ClassPrinterImpl { + + public enum Style { BLOCK, FLOW } + + public record LeafNodeImpl(ConstantDesc name, ConstantDesc value) implements LeafNode { + + @Override + public Stream walk() { + return Stream.of(this); + } + } + + public static final class ListNodeImpl extends AbstractList implements ListNode { + + private final Style style; + private final ConstantDesc name; + private final Node[] nodes; + + public ListNodeImpl(Style style, ConstantDesc name, Stream nodes) { + this.style = style; + this.name = name; + this.nodes = nodes.toArray(Node[]::new); + } + + @Override + public ConstantDesc name() { + return name; + } + + @Override + public Stream walk() { + return Stream.concat(Stream.of(this), stream().flatMap(Node::walk)); + } + + public Style style() { + return style; + } + + @Override + public Node get(int index) { + Objects.checkIndex(index, nodes.length); + return nodes[index]; + } + + @Override + public int size() { + return nodes.length; + } + } + + public static final class MapNodeImpl implements MapNode { + + private final Style style; + private final ConstantDesc name; + private final Map map; + + public MapNodeImpl(Style style, ConstantDesc name) { + this.style = style; + this.name = name; + this.map = new LinkedHashMap<>(); + } + + @Override + public ConstantDesc name() { + return name; + } + + @Override + public Stream walk() { + return Stream.concat(Stream.of(this), values().stream().flatMap(Node::walk)); + } + + public Style style() { + return style; + } + + @Override + public int size() { + return map.size(); + } + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public Node get(Object key) { + return map.get(key); + } + + @Override + public Node put(ConstantDesc key, Node value) { + throw new UnsupportedOperationException(); + } + + @Override + public Node remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(map.values()); + } + + @Override + public Set> entrySet() { + return Collections.unmodifiableSet(map.entrySet()); + } + + + MapNodeImpl with(Node... nodes) { + for (var n : nodes) + if (n != null && map.put(n.name(), n) != null) + throw new AssertionError("Double entry of " + n.name() + " into " + name); + return this; + } + } + + private static Node leaf(ConstantDesc name, ConstantDesc value) { + return new LeafNodeImpl(name, value); + } + + private static Node[] leafs(ConstantDesc... namesAndValues) { + if ((namesAndValues.length & 1) > 0) + throw new AssertionError("Odd number of arguments: " + Arrays.toString(namesAndValues)); + var nodes = new Node[namesAndValues.length >> 1]; + for (int i = 0, j = 0; i < nodes.length; i ++) { + nodes[i] = leaf(namesAndValues[j++], namesAndValues[j++]); + } + return nodes; + } + + private static Node list(ConstantDesc listName, ConstantDesc itemsName, Stream values) { + return new ListNodeImpl(FLOW, listName, values.map(v -> leaf(itemsName, v))); + } + + private static Node map(ConstantDesc mapName, ConstantDesc... keysAndValues) { + return new MapNodeImpl(FLOW, mapName).with(leafs(keysAndValues)); + } + + private static final String NL = System.lineSeparator(); + + private static final char[] DIGITS = "0123456789ABCDEF".toCharArray(); + + private static void escape(int c, StringBuilder sb) { + switch (c) { + case '\\' -> sb.append('\\').append('\\'); + case '"' -> sb.append('\\').append('"'); + case '\b' -> sb.append('\\').append('b'); + case '\n' -> sb.append('\\').append('n'); + case '\t' -> sb.append('\\').append('t'); + case '\f' -> sb.append('\\').append('f'); + case '\r' -> sb.append('\\').append('r'); + default -> { + if (c >= 0x20 && c < 0x7f) { + sb.append((char)c); + } else { + sb.append('\\').append('u').append(DIGITS[(c >> 12) & 0xf]) + .append(DIGITS[(c >> 8) & 0xf]).append(DIGITS[(c >> 4) & 0xf]).append(DIGITS[(c) & 0xf]); + } + } + } + } + + public static void toYaml(Node node, Consumer out) { + toYaml(0, false, new ListNodeImpl(BLOCK, null, Stream.of(node)), out); + out.accept(NL); + } + + private static void toYaml(int indent, boolean skipFirstIndent, Node node, Consumer out) { + switch (node) { + case LeafNode leaf -> { + out.accept(quoteAndEscapeYaml(leaf.value())); + } + case ListNodeImpl list -> { + switch (list.style()) { + case FLOW -> { + out.accept("["); + boolean first = true; + for (var n : list) { + if (first) first = false; + else out.accept(", "); + toYaml(0, false, n, out); + } + out.accept("]"); + } + case BLOCK -> { + for (var n : list) { + out.accept(NL + " ".repeat(indent) + " - "); + toYaml(indent + 1, true, n, out); + } + } + } + } + case MapNodeImpl map -> { + switch (map.style()) { + case FLOW -> { + out.accept("{"); + boolean first = true; + for (var n : map.values()) { + if (first) first = false; + else out.accept(", "); + out.accept(quoteAndEscapeYaml(n.name()) + ": "); + toYaml(0, false, n, out); + } + out.accept("}"); + } + case BLOCK -> { + for (var n : map.values()) { + if (skipFirstIndent) { + skipFirstIndent = false; + } else { + out.accept(NL + " ".repeat(indent)); + } + out.accept(quoteAndEscapeYaml(n.name()) + ": "); + toYaml(n instanceof ListNodeImpl pl && pl.style() == BLOCK ? indent : indent + 1, false, n, out); + } + } + } + } + } + } + + private static String quoteAndEscapeYaml(ConstantDesc value) { + String s = String.valueOf(value); + if (value instanceof Number) return s; + if (s.length() == 0) return "''"; + var sb = new StringBuilder(s.length() << 1); + s.chars().forEach(c -> { + switch (c) { + case '\'' -> sb.append("''"); + default -> escape(c, sb); + }}); + String esc = sb.toString(); + if (esc.length() != s.length()) return "'" + esc + "'"; + switch (esc.charAt(0)) { + case '-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + return "'" + esc + "'"; + } + for (int i = 1; i < esc.length(); i++) { + switch (esc.charAt(i)) { + case ',', '[', ']', '{', '}': + return "'" + esc + "'"; + } + } + return esc; + } + + public static void toJson(Node node, Consumer out) { + toJson(1, true, node, out); + out.accept(NL); + } + + private static void toJson(int indent, boolean skipFirstIndent, Node node, Consumer out) { + switch (node) { + case LeafNode leaf -> { + out.accept(quoteAndEscapeJson(leaf.value())); + } + case ListNodeImpl list -> { + out.accept("["); + boolean first = true; + switch (list.style()) { + case FLOW -> { + for (var n : list) { + if (first) first = false; + else out.accept(", "); + toJson(0, false, n, out); + } + } + case BLOCK -> { + for (var n : list) { + if (first) first = false; + else out.accept(","); + out.accept(NL + " ".repeat(indent)); + toJson(indent + 1, true, n, out); + } + } + } + out.accept("]"); + } + case MapNodeImpl map -> { + switch (map.style()) { + case FLOW -> { + out.accept("{"); + boolean first = true; + for (var n : map.values()) { + if (first) first = false; + else out.accept(", "); + out.accept(quoteAndEscapeJson(n.name().toString()) + ": "); + toJson(0, false, n, out); + } + } + case BLOCK -> { + if (skipFirstIndent) out.accept(" { "); + else out.accept("{"); + boolean first = true; + for (var n : map.values()) { + if (first) first = false; + else out.accept(","); + if (skipFirstIndent) skipFirstIndent = false; + else out.accept(NL + " ".repeat(indent)); + out.accept(quoteAndEscapeJson(n.name().toString()) + ": "); + toJson(indent + 1, false, n, out); + } + } + } + out.accept("}"); + } + } + } + + private static String quoteAndEscapeJson(ConstantDesc value) { + String s = String.valueOf(value); + if (value instanceof Number) return s; + var sb = new StringBuilder(s.length() << 1); + sb.append('"'); + s.chars().forEach(c -> escape(c, sb)); + sb.append('"'); + return sb.toString(); + } + + public static void toXml(Node node, Consumer out) { + out.accept(""); + toXml(0, false, node, out); + out.accept(NL); + } + + private static void toXml(int indent, boolean skipFirstIndent, Node node, Consumer out) { + var name = toXmlName(node.name().toString()); + switch (node) { + case LeafNode leaf -> { + out.accept("<" + name + ">"); + out.accept(xmlEscape(leaf.value())); + } + case ListNodeImpl list -> { + switch (list.style()) { + case FLOW -> { + out.accept("<" + name + ">"); + for (var n : list) { + toXml(0, false, n, out); + } + } + case BLOCK -> { + if (!skipFirstIndent) + out.accept(NL + " ".repeat(indent)); + out.accept("<" + name + ">"); + for (var n : list) { + out.accept(NL + " ".repeat(indent + 1)); + toXml(indent + 1, true, n, out); + } + } + } + } + case MapNodeImpl map -> { + switch (map.style()) { + case FLOW -> { + out.accept("<" + name + ">"); + for (var n : map.values()) { + toXml(0, false, n, out); + } + } + case BLOCK -> { + if (!skipFirstIndent) + out.accept(NL + " ".repeat(indent)); + out.accept("<" + name + ">"); + for (var n : map.values()) { + out.accept(NL + " ".repeat(indent + 1)); + toXml(indent + 1, true, n, out); + } + } + } + } + } + out.accept(""); + } + + private static String xmlEscape(ConstantDesc value) { + var s = String.valueOf(value); + var sb = new StringBuilder(s.length() << 1); + s.chars().forEach(c -> { + switch (c) { + case '<' -> sb.append("<"); + case '>' -> sb.append(">"); + case '"' -> sb.append("""); + case '&' -> sb.append("&"); + case '\'' -> sb.append("'"); + default -> escape(c, sb); + }}); + return sb.toString(); + } + + private static String toXmlName(String name) { + if (Character.isDigit(name.charAt(0))) + name = "_" + name; + return name.replaceAll("[^A-Za-z_0-9]", "_"); + } + + private static Node[] elementValueToTree(AnnotationValue v) { + return switch (v) { + case OfString cv -> leafs("string", String.valueOf(cv.constantValue())); + case OfDouble cv -> leafs("double", String.valueOf(cv.constantValue())); + case OfFloat cv -> leafs("float", String.valueOf(cv.constantValue())); + case OfLong cv -> leafs("long", String.valueOf(cv.constantValue())); + case OfInteger cv -> leafs("int", String.valueOf(cv.constantValue())); + case OfShort cv -> leafs("short", String.valueOf(cv.constantValue())); + case OfCharacter cv -> leafs("char", String.valueOf(cv.constantValue())); + case OfByte cv -> leafs("byte", String.valueOf(cv.constantValue())); + case OfBoolean cv -> leafs("boolean", String.valueOf((int)cv.constantValue() != 0)); + case OfClass clv -> leafs("class", clv.className().stringValue()); + case OfEnum ev -> leafs("enum class", ev.className().stringValue(), + "contant name", ev.constantName().stringValue()); + case OfAnnotation av -> leafs("annotation class", av.annotation().className().stringValue()); + case OfArray av -> new Node[]{new ListNodeImpl(FLOW, "array", av.values().stream().map( + ev -> new MapNodeImpl(FLOW, "value").with(elementValueToTree(ev))))}; + }; + } + + private static Node elementValuePairsToTree(List evps) { + return new ListNodeImpl(FLOW, "values", evps.stream().map(evp -> new MapNodeImpl(FLOW, "pair").with( + leaf("name", evp.name().stringValue()), + new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value()))))); + } + + private static Stream convertVTIs(CodeAttribute lr, List vtis) { + return vtis.stream().mapMulti((vti, ret) -> { + switch (vti) { + case SimpleVerificationTypeInfo s -> { + switch (s) { + case ITEM_DOUBLE -> { + ret.accept("double"); + ret.accept("double2"); + } + case ITEM_FLOAT -> + ret.accept("float"); + case ITEM_INTEGER -> + ret.accept("int"); + case ITEM_LONG -> { + ret.accept("long"); + ret.accept("long2"); + } + case ITEM_NULL -> ret.accept("null"); + case ITEM_TOP -> ret.accept("?"); + case ITEM_UNINITIALIZED_THIS -> ret.accept("THIS"); + } + } + case ObjectVerificationTypeInfo o -> + ret.accept(o.className().name().stringValue()); + case UninitializedVerificationTypeInfo u -> + ret.accept("UNITIALIZED @" + lr.labelToBci(u.newTarget())); + } + }); + } + + private record ExceptionHandler(int start, int end, int handler, String catchType) {} + + public static MapNode modelToTree(CompoundElement model, Verbosity verbosity) { + return switch(model) { + case ClassModel cm -> classToTree(cm, verbosity); + case FieldModel fm -> fieldToTree(fm, verbosity); + case MethodModel mm -> methodToTree(mm, verbosity); + case CodeModel com -> codeToTree((CodeAttribute)com, verbosity); + }; + } + + private static MapNode classToTree(ClassModel clm, Verbosity verbosity) { + return new MapNodeImpl(BLOCK, "class") + .with(leaf("class name", clm.thisClass().asInternalName()), + leaf("version", clm.majorVersion() + "." + clm.minorVersion()), + list("flags", "flag", clm.flags().flags().stream().map(AccessFlag::name)), + leaf("superclass", clm.superclass().map(ClassEntry::asInternalName).orElse("")), + list("interfaces", "interface", clm.interfaces().stream().map(ClassEntry::asInternalName)), + list("attributes", "attribute", clm.attributes().stream().map(Attribute::attributeName))) + .with(constantPoolToTree(clm.constantPool(), verbosity)) + .with(attributesToTree(clm.attributes(), verbosity)) + .with(new ListNodeImpl(BLOCK, "fields", clm.fields().stream().map(f -> + fieldToTree(f, verbosity)))) + .with(new ListNodeImpl(BLOCK, "methods", clm.methods().stream().map(mm -> + methodToTree(mm, verbosity)))); + } + + private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) { + if (verbosity == Verbosity.TRACE_ALL) { + var cpNode = new MapNodeImpl(BLOCK, "constant pool"); + for (int i = 1; i < cp.entryCount();) { + var e = cp.entryByIndex(i); + cpNode.with(new MapNodeImpl(FLOW, i) + .with(leaf("tag", switch (e.tag()) { + case TAG_UTF8 -> "Utf8"; + case TAG_INTEGER -> "Integer"; + case TAG_FLOAT -> "Float"; + case TAG_LONG -> "Long"; + case TAG_DOUBLE -> "Double"; + case TAG_CLASS -> "Class"; + case TAG_STRING -> "String"; + case TAG_FIELDREF -> "Fieldref"; + case TAG_METHODREF -> "Methodref"; + case TAG_INTERFACEMETHODREF -> "InterfaceMethodref"; + case TAG_NAMEANDTYPE -> "NameAndType"; + case TAG_METHODHANDLE -> "MethodHandle"; + case TAG_METHODTYPE -> "MethodType"; + case TAG_CONSTANTDYNAMIC -> "Dynamic"; + case TAG_INVOKEDYNAMIC -> "InvokeDynamic"; + case TAG_MODULE -> "Module"; + case TAG_PACKAGE -> "Package"; + default -> throw new AssertionError("Unknown CP tag: " + e.tag()); + })) + .with(switch (e) { + case ClassEntry ce -> leafs( + "class name index", ce.name().index(), + "class internal name", ce.asInternalName()); + case ModuleEntry me -> leafs( + "module name index", me.name().index(), + "module name", me.name().stringValue()); + case PackageEntry pe -> leafs( + "package name index", pe.name().index(), + "package name", pe.name().stringValue()); + case StringEntry se -> leafs( + "value index", se.utf8().index(), + "value", se.stringValue()); + case MemberRefEntry mre -> leafs( + "owner index", mre.owner().index(), + "name and type index", mre.nameAndType().index(), + "owner", mre.owner().name().stringValue(), + "name", mre.name().stringValue(), + "type", mre.type().stringValue()); + case NameAndTypeEntry nte -> leafs( + "name index", nte.name().index(), + "type index", nte.type().index(), + "name", nte.name().stringValue(), + "type", nte.type().stringValue()); + case MethodHandleEntry mhe -> leafs( + "reference kind", DirectMethodHandleDesc.Kind.valueOf(mhe.kind()).name(), + "reference index", mhe.reference().index(), + "owner", mhe.reference().owner().asInternalName(), + "name", mhe.reference().name().stringValue(), + "type", mhe.reference().type().stringValue()); + case MethodTypeEntry mte -> leafs( + "descriptor index", mte.descriptor().index(), + "descriptor", mte.descriptor().stringValue()); + case DynamicConstantPoolEntry dcpe -> new Node[] { + leaf("bootstrap method handle index", dcpe.bootstrap().bootstrapMethod().index()), + list("bootstrap method arguments indexes", + "index", dcpe.bootstrap().arguments().stream().map(en -> en.index())), + leaf("name and type index", dcpe.nameAndType().index()), + leaf("name", dcpe.name().stringValue()), + leaf("type", dcpe.type().stringValue())}; + case AnnotationConstantValueEntry ve -> leafs( + "value", String.valueOf(ve.constantValue()) + ); + })); + i += e.width(); + } + return new Node[]{cpNode}; + } else { + return new Node[0]; + } + } + + private static Node frameToTree(ConstantDesc name, CodeAttribute lr, StackMapFrameInfo f) { + return new MapNodeImpl(FLOW, name).with( + list("locals", "item", convertVTIs(lr, f.locals())), + list("stack", "item", convertVTIs(lr, f.stack()))); + } + + private static MapNode fieldToTree(FieldModel f, Verbosity verbosity) { + return new MapNodeImpl(BLOCK, "field") + .with(leaf("field name", f.fieldName().stringValue()), + list("flags", + "flag", f.flags().flags().stream().map(AccessFlag::name)), + leaf("field type", f.fieldType().stringValue()), + list("attributes", + "attribute", f.attributes().stream().map(Attribute::attributeName))) + .with(attributesToTree(f.attributes(), verbosity)); + } + + public static MapNode methodToTree(MethodModel m, Verbosity verbosity) { + return new MapNodeImpl(BLOCK, "method") + .with(leaf("method name", m.methodName().stringValue()), + list("flags", + "flag", m.flags().flags().stream().map(AccessFlag::name)), + leaf("method type", m.methodType().stringValue()), + list("attributes", + "attribute", m.attributes().stream().map(Attribute::attributeName))) + .with(attributesToTree(m.attributes(), verbosity)) + .with(codeToTree((CodeAttribute)m.code().orElse(null), verbosity)); + } + + private static MapNode codeToTree(CodeAttribute com, Verbosity verbosity) { + if (verbosity != Verbosity.MEMBERS_ONLY && com != null) { + var codeNode = new MapNodeImpl(BLOCK, "code"); + codeNode.with(leaf("max stack", com.maxStack())); + codeNode.with(leaf("max locals", com.maxLocals())); + codeNode.with(list("attributes", + "attribute", com.attributes().stream().map(Attribute::attributeName))); + var stackMap = new MapNodeImpl(BLOCK, "stack map frames"); + var visibleTypeAnnos = new LinkedHashMap>(); + var invisibleTypeAnnos = new LinkedHashMap>(); + List locals = List.of(); + for (var attr : com.attributes()) { + if (attr instanceof StackMapTableAttribute smta) { + codeNode.with(stackMap); + for (var smf : smta.entries()) { + stackMap.with(frameToTree(com.labelToBci(smf.target()), com, smf)); + } + } else if (verbosity == Verbosity.TRACE_ALL && attr != null) switch (attr) { + case LocalVariableTableAttribute lvta -> { + locals = lvta.localVariables(); + codeNode.with(new ListNodeImpl(BLOCK, "local variables", + IntStream.range(0, locals.size()).mapToObj(i -> { + var lv = lvta.localVariables().get(i); + return map(i + 1, + "start", lv.startPc(), + "end", lv.startPc() + lv.length(), + "slot", lv.slot(), + "name", lv.name().stringValue(), + "type", lv.type().stringValue()); + }))); + } + case LocalVariableTypeTableAttribute lvtta -> { + codeNode.with(new ListNodeImpl(BLOCK, "local variable types", + IntStream.range(0, lvtta.localVariableTypes().size()).mapToObj(i -> { + var lvt = lvtta.localVariableTypes().get(i); + return map(i + 1, + "start", lvt.startPc(), + "end", lvt.startPc() + lvt.length(), + "slot", lvt.slot(), + "name", lvt.name().stringValue(), + "signature", lvt.signature().stringValue()); + }))); + } + case LineNumberTableAttribute lnta -> { + codeNode.with(new ListNodeImpl(BLOCK, "line numbers", + IntStream.range(0, lnta.lineNumbers().size()).mapToObj(i -> { + var ln = lnta.lineNumbers().get(i); + return map(i + 1, + "start", ln.startPc(), + "line number", ln.lineNumber()); + }))); + } + case CharacterRangeTableAttribute crta -> { + codeNode.with(new ListNodeImpl(BLOCK, "character ranges", + IntStream.range(0, crta.characterRangeTable().size()).mapToObj(i -> { + var cr = crta.characterRangeTable().get(i); + return map(i + 1, + "start", cr.startPc(), + "end", cr.endPc(), + "range start", cr.characterRangeStart(), + "range end", cr.characterRangeEnd(), + "flags", cr.flags()); + }))); + } + case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> + rvtaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) -> + visibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an))); + case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> + ritaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) -> + invisibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an))); + case Object o -> {} + } + } + codeNode.with(attributesToTree(com.attributes(), verbosity)); + if (!stackMap.containsKey(0)) { + codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @0").with( + list("locals", "item", convertVTIs(com, StackMapDecoder.initFrameLocals(com.parent().get()))), + list("stack", "item", Stream.of()))); + } + var excHandlers = com.exceptionHandlers().stream().map(exc -> new ExceptionHandler( + com.labelToBci(exc.tryStart()), + com.labelToBci(exc.tryEnd()), + com.labelToBci(exc.handler()), + exc.catchType().map(ct -> ct.name().stringValue()).orElse(null))).toList(); + int bci = 0; + for (var coe : com) { + if (coe instanceof Instruction ins) { + var frame = stackMap.get(bci); + if (frame != null) { + codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @" + bci) + .with(((MapNodeImpl)frame).values().toArray(new Node[2]))); + } + var annos = invisibleTypeAnnos.get(bci); + if (annos != null) { + codeNode.with(typeAnnotationsToTree(FLOW, "//invisible type annotations @" + bci, annos)); + } + annos = visibleTypeAnnos.get(bci); + if (annos != null) { + codeNode.with(typeAnnotationsToTree(FLOW, "//visible type annotations @" + bci, annos)); + } + for (int i = 0; i < excHandlers.size(); i++) { + var exc = excHandlers.get(i); + if (exc.start() == bci) { + codeNode.with(map("//try block " + (i + 1) + " start", + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "catch type", exc.catchType())); + } + if (exc.end() == bci) { + codeNode.with(map("//try block " + (i + 1) + " end", + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "catch type", exc.catchType())); + } + if (exc.handler() == bci) { + codeNode.with(map("//exception handler " + (i + 1) + " start", + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "catch type", exc.catchType())); + } + } + var in = new MapNodeImpl(FLOW, bci).with(leaf("opcode", ins.opcode().name())); + codeNode.with(in); + switch (coe) { + case IncrementInstruction inc -> in.with(leafs( + "slot", inc.slot(), + "const", inc.constant())) + .with(localInfoToTree(locals, inc.slot(), bci)); + case LoadInstruction lv -> in.with(leaf( + "slot", lv.slot())) + .with(localInfoToTree(locals, lv.slot(), bci)); + case StoreInstruction lv -> in.with(leaf( + "slot", lv.slot())) + .with(localInfoToTree(locals, lv.slot(), bci)); + case FieldInstruction fa -> in.with(leafs( + "owner", fa.owner().name().stringValue(), + "field name", fa.name().stringValue(), + "field type", fa.type().stringValue())); + case InvokeInstruction inv -> in.with(leafs( + "owner", inv.owner().name().stringValue(), + "method name", inv.name().stringValue(), + "method type", inv.type().stringValue())); + case InvokeDynamicInstruction invd -> in.with(leafs( + "name", invd.name().stringValue(), + "descriptor", invd.type().stringValue(), + "kind", invd.bootstrapMethod().kind().name(), + "owner", invd.bootstrapMethod().owner().descriptorString(), + "method name", invd.bootstrapMethod().methodName(), + "invocation type", invd.bootstrapMethod().invocationType().descriptorString())); + case NewObjectInstruction newo -> in.with(leaf( + "type", newo.className().name().stringValue())); + case NewPrimitiveArrayInstruction newa -> in.with(leafs( + "dimensions", 1, + "descriptor", newa.typeKind().typeName())); + case NewReferenceArrayInstruction newa -> in.with(leafs( + "dimensions", 1, + "descriptor", newa.componentType().name().stringValue())); + case NewMultiArrayInstruction newa -> in.with(leafs( + "dimensions", newa.dimensions(), + "descriptor", newa.arrayType().name().stringValue())); + case TypeCheckInstruction tch -> in.with(leaf( + "type", tch.type().name().stringValue())); + case ConstantInstruction cons -> in.with(leaf( + "constant value", cons.constantValue())); + case BranchInstruction br -> in.with(leaf( + "target", com.labelToBci(br.target()))); + case LookupSwitchInstruction si -> in.with(list( + "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) + .map(com::labelToBci), si.cases().stream() + .map(sc -> com.labelToBci(sc.target()))))); + case TableSwitchInstruction si -> in.with(list( + "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) + .map(com::labelToBci), si.cases().stream() + .map(sc -> com.labelToBci(sc.target()))))); + default -> {} + } + bci += ins.sizeInBytes(); + } + } + if (!excHandlers.isEmpty()) { + var handlersNode = new MapNodeImpl(BLOCK, "exception handlers"); + codeNode.with(handlersNode); + for (int i = 0; i < excHandlers.size(); i++) { + var exc = excHandlers.get(i); + handlersNode.with(map("handler " + (i + 1), + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "type", exc.catchType())); + } + } + return codeNode; + } + return null; + } + + private static Node[] attributesToTree(List> attributes, Verbosity verbosity) { + var nodes = new LinkedList(); + if (verbosity != Verbosity.MEMBERS_ONLY) for (var attr : attributes) { + switch (attr) { + case BootstrapMethodsAttribute bma -> + nodes.add(new ListNodeImpl(BLOCK, "bootstrap methods", bma.bootstrapMethods().stream().map( + bm -> { + var mh = bm.bootstrapMethod(); + var mref = mh.reference(); + return map("bm", + "kind", DirectMethodHandleDesc.Kind.valueOf(mh.kind(), + mref instanceof InterfaceMethodRefEntry).name(), + "owner", mref.owner().name().stringValue(), + "name", mref.nameAndType().name().stringValue(), + "type", mref.nameAndType().type().stringValue()); + }))); + case ConstantValueAttribute cva -> + nodes.add(leaf("constant value", cva.constant().constantValue())); + case NestHostAttribute nha -> + nodes.add(leaf("nest host", nha.nestHost().name().stringValue())); + case NestMembersAttribute nma -> + nodes.add(list("nest members", "member", nma.nestMembers().stream() + .map(mp -> mp.name().stringValue()))); + case PermittedSubclassesAttribute psa -> + nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream() + .map(e -> e.name().stringValue()))); + default -> {} + } + if (verbosity == Verbosity.TRACE_ALL) switch (attr) { + case EnclosingMethodAttribute ema -> + nodes.add(map("enclosing method", + "class", ema.enclosingClass().name().stringValue(), + "method name", ema.enclosingMethodName() + .map(Utf8Entry::stringValue).orElse("null"), + "method type", ema.enclosingMethodType() + .map(Utf8Entry::stringValue).orElse("null"))); + case ExceptionsAttribute exa -> + nodes.add(list("excceptions", "exc", exa.exceptions().stream() + .map(e -> e.name().stringValue()))); + case InnerClassesAttribute ica -> + nodes.add(new ListNodeImpl(BLOCK, "inner classes", ica.classes().stream() + .map(ic -> new MapNodeImpl(FLOW, "cls").with( + leaf("inner class", ic.innerClass().name().stringValue()), + leaf("outer class", ic.outerClass() + .map(cle -> cle.name().stringValue()).orElse("null")), + leaf("inner name", ic.innerName().map(Utf8Entry::stringValue).orElse("null")), + list("flags", "flag", ic.flags().stream().map(AccessFlag::name)))))); + case MethodParametersAttribute mpa -> { + var n = new MapNodeImpl(BLOCK, "method parameters"); + for (int i = 0; i < mpa.parameters().size(); i++) { + var p = mpa.parameters().get(i); + n.with(new MapNodeImpl(FLOW, i + 1).with( + leaf("name", p.name().map(Utf8Entry::stringValue).orElse("null")), + list("flags", "flag", p.flags().stream().map(AccessFlag::name)))); + } + } + case ModuleAttribute ma -> + nodes.add(new MapNodeImpl(BLOCK, "module") + .with(leaf("name", ma.moduleName().name().stringValue()), + list("flags","flag", ma.moduleFlags().stream().map(AccessFlag::name)), + leaf("version", ma.moduleVersion().map(Utf8Entry::stringValue).orElse("null")), + list("uses", "class", ma.uses().stream().map(ce -> ce.name().stringValue())), + new ListNodeImpl(BLOCK, "requires", ma.requires().stream().map(req -> + new MapNodeImpl(FLOW, "req").with( + leaf("name", req.requires().name().stringValue()), + list("flags", "flag", req.requiresFlags().stream() + .map(AccessFlag::name)), + leaf("version", req.requiresVersion() + .map(Utf8Entry::stringValue).orElse(null))))), + new ListNodeImpl(BLOCK, "exports", ma.exports().stream().map(exp -> + new MapNodeImpl(FLOW, "exp").with( + leaf("package", exp.exportedPackage().asSymbol().packageName()), + list("flags", "flag", exp.exportsFlags().stream() + .map(AccessFlag::name)), + list("to", "module", exp.exportsTo().stream() + .map(me -> me.name().stringValue()))))), + new ListNodeImpl(BLOCK, "opens", ma.opens().stream().map(opn -> + new MapNodeImpl(FLOW, "opn").with( + leaf("package", opn.openedPackage().asSymbol().packageName()), + list("flags", "flag", opn.opensFlags().stream() + .map(AccessFlag::name)), + list("to", "module", opn.opensTo().stream() + .map(me -> me.name().stringValue()))))), + new ListNodeImpl(BLOCK, "provides", ma.provides().stream() + .map(prov -> new MapNodeImpl(FLOW, "prov").with( + leaf("class", prov.provides().name().stringValue()), + list("with", "cls", prov.providesWith().stream() + .map(ce -> ce.name().stringValue()))))))); + case ModulePackagesAttribute mopa -> + nodes.add(list("module packages", "subclass", mopa.packages().stream() + .map(mp -> mp.asSymbol().packageName()))); + case ModuleMainClassAttribute mmca -> + nodes.add(leaf("module main class", mmca.mainClass().name().stringValue())); + case RecordAttribute ra -> + nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream() + .map(rc -> new MapNodeImpl(BLOCK, "record") + .with(leafs( + "name", rc.name().stringValue(), + "type", rc.descriptor().stringValue())) + .with(list("attributes", "attribute", rc.attributes().stream() + .map(Attribute::attributeName))) + .with(attributesToTree(rc.attributes(), verbosity))))); + case AnnotationDefaultAttribute ada -> + nodes.add(new MapNodeImpl(FLOW, "annotation default").with(elementValueToTree(ada.defaultValue()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + nodes.add(annotationsToTree("invisible annotations", aa.annotations())); + case RuntimeVisibleAnnotationsAttribute aa -> + nodes.add(annotationsToTree("visible annotations", aa.annotations())); + case RuntimeInvisibleParameterAnnotationsAttribute aa -> + nodes.add(parameterAnnotationsToTree("invisible parameter annotations", aa.parameterAnnotations())); + case RuntimeVisibleParameterAnnotationsAttribute aa -> + nodes.add(parameterAnnotationsToTree("visible parameter annotations", aa.parameterAnnotations())); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + nodes.add(typeAnnotationsToTree(BLOCK, "invisible type annotations", aa.annotations())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + nodes.add(typeAnnotationsToTree(BLOCK, "visible type annotations", aa.annotations())); + case SignatureAttribute sa -> + nodes.add(leaf("signature", sa.signature().stringValue())); + case SourceFileAttribute sfa -> + nodes.add(leaf("source file", sfa.sourceFile().stringValue())); + default -> {} + } + } + return nodes.toArray(Node[]::new); + } + + private static Node annotationsToTree(String name, List annos) { + return new ListNodeImpl(BLOCK, name, annos.stream().map(a -> + new MapNodeImpl(FLOW, "anno") + .with(leaf("annotation class", a.className().stringValue())) + .with(elementValuePairsToTree(a.elements())))); + + } + + private static Node typeAnnotationsToTree(Style style, String name, List annos) { + return new ListNodeImpl(style, name, annos.stream().map(a -> + new MapNodeImpl(FLOW, "anno") + .with(leaf("annotation class", a.className().stringValue()), + leaf("target info", a.targetInfo().targetType().name())) + .with(elementValuePairsToTree(a.elements())))); + + } + + private static MapNodeImpl parameterAnnotationsToTree(String name, List> paramAnnotations) { + var node = new MapNodeImpl(BLOCK, name); + for (int i = 0; i < paramAnnotations.size(); i++) { + var annos = paramAnnotations.get(i); + if (!annos.isEmpty()) { + node.with(new ListNodeImpl(FLOW, "parameter " + (i + 1), annos.stream().map(a -> + new MapNodeImpl(FLOW, "anno") + .with(leaf("annotation class", a.className().stringValue())) + .with(elementValuePairsToTree(a.elements()))))); + } + } + return node; + } + + private static Node[] localInfoToTree(List locals, int slot, int bci) { + if (locals != null) { + for (var l : locals) { + if (l.slot() == slot && l.startPc() <= bci && l.length() + l.startPc() >= bci) { + return leafs("type", l.type().stringValue(), + "variable name", l.name().stringValue()); + } + } + } + return new Node[0]; + } + + private static void forEachOffset(TypeAnnotation ta, CodeAttribute lr, BiConsumer consumer) { + switch (ta.targetInfo()) { + case TypeAnnotation.OffsetTarget ot -> + consumer.accept(lr.labelToBci(ot.target()), ta); + case TypeAnnotation.TypeArgumentTarget tat -> + consumer.accept(lr.labelToBci(tat.target()), ta); + case TypeAnnotation.LocalVarTarget lvt -> + lvt.table().forEach(lvti -> consumer.accept(lr.labelToBci(lvti.startLabel()), ta)); + default -> {} + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassReaderImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassReaderImpl.java new file mode 100644 index 0000000000000..06be1bc864b6d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassReaderImpl.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.BootstrapMethodsAttribute; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; + +import static jdk.internal.classfile.Classfile.TAG_CLASS; +import static jdk.internal.classfile.Classfile.TAG_CONSTANTDYNAMIC; +import static jdk.internal.classfile.Classfile.TAG_DOUBLE; +import static jdk.internal.classfile.Classfile.TAG_FIELDREF; +import static jdk.internal.classfile.Classfile.TAG_FLOAT; +import static jdk.internal.classfile.Classfile.TAG_INTEGER; +import static jdk.internal.classfile.Classfile.TAG_INTERFACEMETHODREF; +import static jdk.internal.classfile.Classfile.TAG_INVOKEDYNAMIC; +import static jdk.internal.classfile.Classfile.TAG_LONG; +import static jdk.internal.classfile.Classfile.TAG_METHODHANDLE; +import static jdk.internal.classfile.Classfile.TAG_METHODREF; +import static jdk.internal.classfile.Classfile.TAG_METHODTYPE; +import static jdk.internal.classfile.Classfile.TAG_MODULE; +import static jdk.internal.classfile.Classfile.TAG_NAMEANDTYPE; +import static jdk.internal.classfile.Classfile.TAG_PACKAGE; +import static jdk.internal.classfile.Classfile.TAG_STRING; +import static jdk.internal.classfile.Classfile.TAG_UTF8; + +public final class ClassReaderImpl + implements ClassReader { + static final int CP_ITEM_START = 10; + + private final byte[] buffer; + private final int metadataStart; + private final int classfileLength; + private final Function> attributeMapper; + private final int flags; + private final int thisClassPos; + private ClassEntry thisClass; + private Optional superclass; + private final int constantPoolCount; + private final int[] cpOffset; + + final Options options; + final int interfacesPos; + final PoolEntry[] cp; + + private ClassModel containedClass; + private List bsmEntries; + private BootstrapMethodsAttribute bootstrapMethodsAttribute; + + ClassReaderImpl(byte[] classfileBytes, + Collection options) { + this.buffer = classfileBytes; + this.classfileLength = classfileBytes.length; + this.options = new Options(options); + this.attributeMapper = this.options.attributeMapper; + if (classfileLength < 4 || readInt(0) != 0xCAFEBABE) { + throw new IllegalStateException("Bad magic number"); + } + int constantPoolCount = readU2(8); + int[] cpOffset = new int[constantPoolCount]; + int p = CP_ITEM_START; + for (int i = 1; i < cpOffset.length; ++i) { + cpOffset[i] = p; + int tag = readU1(p); + ++p; + switch (tag) { + // 2 + case TAG_CLASS, TAG_METHODTYPE, TAG_MODULE, TAG_STRING, TAG_PACKAGE -> p += 2; + + // 3 + case TAG_METHODHANDLE -> p += 3; + + // 4 + case TAG_CONSTANTDYNAMIC, TAG_FIELDREF, TAG_FLOAT, TAG_INTEGER, + TAG_INTERFACEMETHODREF, TAG_INVOKEDYNAMIC, TAG_METHODREF, + TAG_NAMEANDTYPE -> p += 4; + + // 8 + case TAG_DOUBLE, TAG_LONG -> { + p += 8; + ++i; + } + case TAG_UTF8 -> p += 2 + readU2(p); + default -> throw new IllegalStateException( + "Bad tag (" + tag + ") at index (" + i + ") position (" + p + ")"); + } + } + this.metadataStart = p; + this.cpOffset = cpOffset; + this.constantPoolCount = constantPoolCount; + this.cp = new PoolEntry[constantPoolCount]; + + this.flags = readU2(p); + this.thisClassPos = p + 2; + p += 6; + this.interfacesPos = p; + } + + public Options options() { + return options; + } + + @Override + public Function> customAttributes() { + return attributeMapper; + } + + @Override + public int entryCount() { + return constantPoolCount; + } + + @Override + public int flags() { + return flags; + } + + @Override + public ClassEntry thisClassEntry() { + if (thisClass == null) { + thisClass = readClassEntry(thisClassPos); + } + return thisClass; + } + + @Override + public Optional superclassEntry() { + if (superclass == null) { + int scIndex = readU2(thisClassPos + 2); + superclass = Optional.ofNullable(scIndex == 0 ? null : (ClassEntry) entryByIndex(scIndex)); + } + return superclass; + } + + @Override + public int thisClassPos() { + return thisClassPos; + } + + @Override + public int classfileLength() { + return classfileLength; + } + + //------ Bootstrap Method Table handling + + @Override + public int bootstrapMethodCount() { + return bootstrapMethodsAttribute().bootstrapMethodsSize(); + } + + @Override + public BootstrapMethodEntryImpl bootstrapMethodEntry(int index) { + return bsmEntries().get(index); + } + + @Override + public int readU1(int p) { + return buffer[p] & 0xFF; + } + + @Override + public int readU2(int p) { + int b1 = buffer[p] & 0xFF; + int b2 = buffer[p + 1] & 0xFF; + return (b1 << 8) + b2; + } + + @Override + public int readS1(int p) { + return buffer[p]; + } + + @Override + public int readS2(int p) { + int b1 = buffer[p]; + int b2 = buffer[p + 1] & 0xFF; + return (b1 << 8) + b2; + } + + @Override + public int readInt(int p) { + int ch1 = buffer[p] & 0xFF; + int ch2 = buffer[p + 1] & 0xFF; + int ch3 = buffer[p + 2] & 0xFF; + int ch4 = buffer[p + 3] & 0xFF; + return (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4; + } + + @Override + public long readLong(int p) { + return ((long) buffer[p + 0] << 56) + ((long) (buffer[p + 1] & 255) << 48) + + ((long) (buffer[p + 2] & 255) << 40) + ((long) (buffer[p + 3] & 255) << 32) + + ((long) (buffer[p + 4] & 255) << 24) + ((buffer[p + 5] & 255) << 16) + ((buffer[p + 6] & 255) << 8) + + (buffer[p + 7] & 255); + } + + @Override + public float readFloat(int p) { + return Float.intBitsToFloat(readInt(p)); + } + + @Override + public double readDouble(int p) { + return Double.longBitsToDouble(readLong(p)); + } + + @Override + public byte[] readBytes(int p, int len) { + return Arrays.copyOfRange(buffer, p, p + len); + } + + @Override + public void copyBytesTo(BufWriter buf, int p, int len) { + buf.writeBytes(buffer, p, len); + } + + BootstrapMethodsAttribute bootstrapMethodsAttribute() { + + if (bootstrapMethodsAttribute == null) { + bootstrapMethodsAttribute + = containedClass.findAttribute(Attributes.BOOTSTRAP_METHODS) + .orElse(new UnboundAttribute.EmptyBootstrapAttribute()); + } + + return bootstrapMethodsAttribute; + } + + List bsmEntries() { + if (bsmEntries == null) { + bsmEntries = new ArrayList<>(); + BootstrapMethodsAttribute attr = bootstrapMethodsAttribute(); + List list = attr.bootstrapMethods(); + if (!list.isEmpty()) { + for (BootstrapMethodEntry bm : list) { + AbstractPoolEntry.MethodHandleEntryImpl handle = (AbstractPoolEntry.MethodHandleEntryImpl) bm.bootstrapMethod(); + List args = bm.arguments(); + int hash = BootstrapMethodEntryImpl.computeHashCode(handle, args); + bsmEntries.add(new BootstrapMethodEntryImpl(this, bsmEntries.size(), hash, handle, args)); + } + } + } + return bsmEntries; + } + + void setContainedClass(ClassModel containedClass) { + this.containedClass = containedClass; + } + + ClassModel getContainedClass() { + return containedClass; + } + + boolean writeBootstrapMethods(BufWriter buf) { + Optional a + = containedClass.findAttribute(Attributes.BOOTSTRAP_METHODS); + if (a.isEmpty()) + return false; + a.get().writeTo(buf); + return true; + } + + void writeConstantPoolEntries(BufWriter buf) { + copyBytesTo(buf, ClassReaderImpl.CP_ITEM_START, + metadataStart - ClassReaderImpl.CP_ITEM_START); + } + + // Constantpool + @Override + public PoolEntry entryByIndex(int index) { + if (index <= 0 || index >= constantPoolCount) { + throw new IndexOutOfBoundsException("Bad CP index: " + index); + } + PoolEntry info = cp[index]; + if (info == null) { + int offset = cpOffset[index]; + int tag = readU1(offset); + final int q = offset + 1; + info = switch (tag) { + case TAG_UTF8 -> new AbstractPoolEntry.Utf8EntryImpl(this, index, buffer, q + 2, readU2(q)); + case TAG_INTEGER -> new AbstractPoolEntry.IntegerEntryImpl(this, index, readInt(q)); + case TAG_FLOAT -> new AbstractPoolEntry.FloatEntryImpl(this, index, readFloat(q)); + case TAG_LONG -> new AbstractPoolEntry.LongEntryImpl(this, index, readLong(q)); + case TAG_DOUBLE -> new AbstractPoolEntry.DoubleEntryImpl(this, index, readDouble(q)); + case TAG_CLASS -> new AbstractPoolEntry.ClassEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_STRING -> new AbstractPoolEntry.StringEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_FIELDREF -> new AbstractPoolEntry.FieldRefEntryImpl(this, index, (AbstractPoolEntry.ClassEntryImpl) readClassEntry(q), + (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_METHODREF -> new AbstractPoolEntry.MethodRefEntryImpl(this, index, (AbstractPoolEntry.ClassEntryImpl) readClassEntry(q), + (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_INTERFACEMETHODREF -> new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, index, (AbstractPoolEntry.ClassEntryImpl) readClassEntry(q), + (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_NAMEANDTYPE -> new AbstractPoolEntry.NameAndTypeEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q), + (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q + 2)); + case TAG_METHODHANDLE -> new AbstractPoolEntry.MethodHandleEntryImpl(this, index, readU1(q), + (AbstractPoolEntry.AbstractMemberRefEntry) readEntry(q + 1)); + case TAG_METHODTYPE -> new AbstractPoolEntry.MethodTypeEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_CONSTANTDYNAMIC -> new AbstractPoolEntry.ConstantDynamicEntryImpl(this, index, readU2(q), (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_INVOKEDYNAMIC -> new AbstractPoolEntry.InvokeDynamicEntryImpl(this, index, readU2(q), (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_MODULE -> new AbstractPoolEntry.ModuleEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_PACKAGE -> new AbstractPoolEntry.PackageEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + default -> throw new IllegalStateException( + "Bad tag (" + tag + ") at index (" + index + ") position (" + offset + ")"); + }; + cp[index] = info; + } + return info; + } + + @Override + public AbstractPoolEntry.Utf8EntryImpl utf8EntryByIndex(int index) { + if (index <= 0 || index >= constantPoolCount) { + throw new IndexOutOfBoundsException("Bad CP UTF8 index: " + index); + } + PoolEntry info = cp[index]; + if (info == null) { + int offset = cpOffset[index]; + int tag = readU1(offset); + final int q = offset + 1; + if (tag != TAG_UTF8) throw new IllegalArgumentException("Not a UTF8 - index: " + index); + AbstractPoolEntry.Utf8EntryImpl uinfo + = new AbstractPoolEntry.Utf8EntryImpl(this, index, buffer, q + 2, readU2(q)); + cp[index] = uinfo; + return uinfo; + } + return (AbstractPoolEntry.Utf8EntryImpl) info; + } + + @Override + public int skipAttributeHolder(int offset) { + int p = offset; + int cnt = readU2(p); + p += 2; + for (int i = 0; i < cnt; ++i) { + int len = readInt(p + 2); + p += 6 + len; + } + return p; + } + + @Override + public PoolEntry readEntry(int pos) { + return entryByIndex(readU2(pos)); + } + + @Override + public PoolEntry readEntryOrNull(int pos) { + int index = readU2(pos); + if (index == 0) { + return null; + } + return entryByIndex(index); + } + + @Override + public Utf8Entry readUtf8Entry(int pos) { + int index = readU2(pos); + return utf8EntryByIndex(index); + } + + @Override + public Utf8Entry readUtf8EntryOrNull(int pos) { + int index = readU2(pos); + if (index == 0) { + return null; + } + return utf8EntryByIndex(index); + } + + @Override + public ModuleEntry readModuleEntry(int pos) { + if (readEntry(pos) instanceof ModuleEntry me) return me; + throw new IllegalArgumentException("Not a module entry at pos: " + pos); + } + + @Override + public PackageEntry readPackageEntry(int pos) { + if (readEntry(pos) instanceof PackageEntry pe) return pe; + throw new IllegalArgumentException("Not a package entry at pos: " + pos); + } + + @Override + public ClassEntry readClassEntry(int pos) { + if (readEntry(pos) instanceof ClassEntry ce) return ce; + throw new IllegalArgumentException("Not a class entry at pos: " + pos); + } + + @Override + public NameAndTypeEntry readNameAndTypeEntry(int pos) { + if (readEntry(pos) instanceof NameAndTypeEntry nate) return nate; + throw new IllegalArgumentException("Not a name and type entry at pos: " + pos); + } + + @Override + public MethodHandleEntry readMethodHandleEntry(int pos) { + if (readEntry(pos) instanceof MethodHandleEntry mhe) return mhe; + throw new IllegalArgumentException("Not a method handle entry at pos: " + pos); + } + + @Override + public boolean compare(BufWriter bufWriter, + int bufWriterOffset, + int classReaderOffset, + int length) { + return Arrays.equals(((BufWriterImpl) bufWriter).elems, + bufWriterOffset, bufWriterOffset + length, + buffer, classReaderOffset, classReaderOffset + length); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassfileVersionImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassfileVersionImpl.java new file mode 100644 index 0000000000000..e79696ad582c0 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassfileVersionImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.ClassfileVersion; + +public final class ClassfileVersionImpl + extends AbstractElement + implements ClassfileVersion { + private final int majorVersion, minorVersion; + + public ClassfileVersionImpl(int majorVersion, int minorVersion) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + } + + @Override + public int majorVersion() { + return majorVersion; + } + + @Override + public int minorVersion() { + return minorVersion; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setVersion(majorVersion, minorVersion); + } + + @Override + public String toString() { + return String.format("ClassfileVersion[majorVersion=%d, minorVersion=%d]", majorVersion, minorVersion); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java new file mode 100644 index 0000000000000..cbab475f3a1db --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.StackMapTableAttribute; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.instruction.*; + +import static jdk.internal.classfile.Classfile.*; + +public final class CodeImpl + extends BoundAttribute.BoundCodeAttribute + implements CodeModel, LabelContext { + + static final Instruction[] SINGLETON_INSTRUCTIONS = new Instruction[256]; + + static { + for (var o : Opcode.values()) { + if (o.sizeIfFixed() == 1) { + SINGLETON_INSTRUCTIONS[o.bytecode()] = switch (o.kind()) { + case ARRAY_LOAD -> ArrayLoadInstruction.of(o); + case ARRAY_STORE -> ArrayStoreInstruction.of(o); + case CONSTANT -> ConstantInstruction.ofIntrinsic(o); + case CONVERT -> ConvertInstruction.of(o); + case LOAD -> LoadInstruction.of(o, o.slot()); + case MONITOR -> MonitorInstruction.of(o); + case NOP -> NopInstruction.of(); + case OPERATOR -> OperatorInstruction.of(o); + case RETURN -> ReturnInstruction.of(o); + case STACK -> StackInstruction.of(o); + case STORE -> StoreInstruction.of(o, o.slot()); + case THROW_EXCEPTION -> ThrowInstruction.of(); + default -> throw new AssertionError("invalid opcode: " + o); + }; + } + } + } + + List exceptionTable; + List> attributes; + + // Inflated for iteration + LabelImpl[] labels; + int[] lineNumbers; + boolean inflated; + + public CodeImpl(AttributedElement enclosing, + ClassReader reader, + AttributeMapper mapper, + int payloadStart) { + super(enclosing, reader, mapper, payloadStart); + } + + // LabelContext + + @Override + public Label newLabel() { + throw new UnsupportedOperationException("CodeAttribute only supports fixed labels"); + } + + @Override + public void setLabelTarget(Label label, int bci) { + throw new UnsupportedOperationException("CodeAttribute only supports fixed labels"); + } + + @Override + public Label getLabel(int bci) { + if (labels == null) + labels = new LabelImpl[codeLength + 1]; + LabelImpl l = labels[bci]; + if (l == null) + l = labels[bci] = new LabelImpl(this, bci); + return l; + } + + @Override + public int labelToBci(Label label) { + LabelImpl lab = (LabelImpl) label; + if (lab.labelContext() != this) + throw new IllegalArgumentException(String.format("Illegal label reuse; context=%s, label=%s", + this, lab.labelContext())); + return lab.getBCI(); + } + + private void inflateMetadata() { + if (!inflated) { + if (labels == null) + labels = new LabelImpl[codeLength + 1]; + if (((ClassReaderImpl)classReader).options().processLineNumbers) + inflateLineNumbers(); + inflateJumpTargets(); + inflateTypeAnnotations(); + inflated = true; + } + } + + // CodeAttribute + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, classReader, attributePos, classReader.customAttributes()); + } + return attributes; + } + + @Override + public void writeTo(BufWriter buf) { + if (buf.canWriteDirect(classReader)) { + super.writeTo(buf); + } + else { + DirectCodeBuilder.build((MethodInfo) enclosingMethod, + new Consumer() { + @Override + public void accept(CodeBuilder cb) { + forEachElement(cb); + } + }, + (SplitConstantPool)buf.constantPool(), + null).writeTo(buf); + } + } + + // CodeModel + + @Override + public Optional parent() { + return Optional.of(enclosingMethod); + } + + @Override + public void forEachElement(Consumer consumer) { + inflateMetadata(); + boolean doLineNumbers = (lineNumbers != null); + generateCatchTargets(consumer); + if (((ClassReaderImpl)classReader).options().processDebug) + generateDebugElements(consumer); + for (int pos=codeStart; pos exceptionHandlers() { + if (exceptionTable == null) { + inflateMetadata(); + exceptionTable = new ArrayList<>(exceptionHandlerCnt); + iterateExceptionHandlers(new ExceptionHandlerAction() { + @Override + public void accept(int s, int e, int h, int c) { + ClassEntry catchTypeEntry = c == 0 + ? null + : (ClassEntry) constantPool().entryByIndex(c); + exceptionTable.add(new AbstractPseudoInstruction.ExceptionCatchImpl(getLabel(h), getLabel(s), getLabel(e), catchTypeEntry)); + } + }); + exceptionTable = Collections.unmodifiableList(exceptionTable); + } + return exceptionTable; + } + + public boolean compareCodeBytes(BufWriter buf, int offset, int len) { + return codeLength == len + && classReader.compare(buf, offset, codeStart, codeLength); + } + + private int adjustForObjectOrUninitialized(int bci) { + int vt = classReader.readU1(bci); + //inflate newTarget labels from Uninitialized VTIs + if (vt == 8) inflateLabel(classReader.readU2(bci + 1)); + return (vt == 7 || vt == 8) ? bci + 3 : bci + 1; + } + + private void inflateLabel(int bci) { + if (labels[bci] == null) + labels[bci] = new LabelImpl(this, bci); + } + + private void inflateLineNumbers() { + for (Attribute a : attributes()) { + if (a.attributeMapper() == Attributes.LINE_NUMBER_TABLE) { + BoundLineNumberTableAttribute attr = (BoundLineNumberTableAttribute) a; + if (lineNumbers == null) + lineNumbers = new int[codeLength + 1]; + + int nLn = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (nLn * 4); + for (; p < pEnd; p += 4) { + int startPc = classReader.readU2(p); + int lineNumber = classReader.readU2(p + 2); + lineNumbers[startPc] = lineNumber; + } + } + } + } + + private void inflateJumpTargets() { + Optional a = findAttribute(Attributes.STACK_MAP_TABLE); + if (a.isEmpty()) + return; + @SuppressWarnings("unchecked") + int stackMapPos = ((BoundAttribute) a.get()).payloadStart; + + int bci = -1; //compensate for offsetDelta + 1 + int nEntries = classReader.readU2(stackMapPos); + int p = stackMapPos + 2; + for (int i = 0; i < nEntries; ++i) { + int frameType = classReader.readU1(p); + int offsetDelta; + if (frameType < 64) { + offsetDelta = frameType; + ++p; + } + else if (frameType < 128) { + offsetDelta = frameType & 0x3f; + p = adjustForObjectOrUninitialized(p + 1); + } + else + switch (frameType) { + case 247 -> { + offsetDelta = classReader.readU2(p + 1); + p = adjustForObjectOrUninitialized(p + 3); + } + case 248, 249, 250, 251 -> { + offsetDelta = classReader.readU2(p + 1); + p += 3; + } + case 252, 253, 254 -> { + offsetDelta = classReader.readU2(p + 1); + int k = frameType - 251; + p += 3; + for (int c = 0; c < k; ++c) { + p = adjustForObjectOrUninitialized(p); + } + } + case 255 -> { + offsetDelta = classReader.readU2(p + 1); + p += 3; + int k = classReader.readU2(p); + p += 2; + for (int c = 0; c < k; ++c) { + p = adjustForObjectOrUninitialized(p); + } + k = classReader.readU2(p); + p += 2; + for (int c = 0; c < k; ++c) { + p = adjustForObjectOrUninitialized(p); + } + } + default -> throw new IllegalArgumentException("Bad frame type: " + frameType); + } + bci += offsetDelta + 1; + inflateLabel(bci); + } + } + + private void inflateTypeAnnotations() { + findAttribute(Attributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS).ifPresent(RuntimeVisibleTypeAnnotationsAttribute::annotations); + findAttribute(Attributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS).ifPresent(RuntimeInvisibleTypeAnnotationsAttribute::annotations); + } + + private void generateCatchTargets(Consumer consumer) { + // We attach all catch targets to bci zero, because trying to attach them + // to their range could subtly affect the order of exception processing + iterateExceptionHandlers(new ExceptionHandlerAction() { + @Override + public void accept(int s, int e, int h, int c) { + ClassEntry catchType = c == 0 + ? null + : (ClassEntry) classReader.entryByIndex(c); + consumer.accept(new AbstractPseudoInstruction.ExceptionCatchImpl(getLabel(h), getLabel(s), getLabel(e), catchType)); + } + }); + } + + private void generateDebugElements(Consumer consumer) { + for (Attribute a : attributes()) { + if (a.attributeMapper() == Attributes.CHARACTER_RANGE_TABLE) { + var attr = (BoundCharacterRangeTableAttribute) a; + int cnt = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (cnt * 14); + for (; p < pEnd; p += 14) { + var instruction = new BoundCharacterRange(this, p); + inflateLabel(instruction.startPc()); + inflateLabel(instruction.endPc() + 1); + consumer.accept(instruction); + } + } + else if (a.attributeMapper() == Attributes.LOCAL_VARIABLE_TABLE) { + var attr = (BoundLocalVariableTableAttribute) a; + int cnt = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (cnt * 10); + for (; p < pEnd; p += 10) { + BoundLocalVariable instruction = new BoundLocalVariable(this, p); + inflateLabel(instruction.startPc()); + inflateLabel(instruction.startPc() + instruction.length()); + consumer.accept(instruction); + } + } + else if (a.attributeMapper() == Attributes.LOCAL_VARIABLE_TYPE_TABLE) { + var attr = (BoundLocalVariableTypeTableAttribute) a; + int cnt = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (cnt * 10); + for (; p < pEnd; p += 10) { + BoundLocalVariableType instruction = new BoundLocalVariableType(this, p); + inflateLabel(instruction.startPc()); + inflateLabel(instruction.startPc() + instruction.length()); + consumer.accept(instruction); + } + } + else if (a.attributeMapper() == Attributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS) { + consumer.accept((BoundRuntimeVisibleTypeAnnotationsAttribute) a); + } + else if (a.attributeMapper() == Attributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS) { + consumer.accept((BoundRuntimeInvisibleTypeAnnotationsAttribute) a); + } + } + } + + public interface ExceptionHandlerAction { + void accept(int start, int end, int handler, int catchTypeIndex); + } + + public void iterateExceptionHandlers(ExceptionHandlerAction a) { + int p = exceptionHandlerPos + 2; + for (int i = 0; i < exceptionHandlerCnt; ++i) { + a.accept(classReader.readU2(p), classReader.readU2(p + 2), classReader.readU2(p + 4), classReader.readU2(p + 6)); + p += 8; + } + } + + private Instruction bcToInstruction(int bc, int pos) { + return switch (bc) { + case BIPUSH -> new AbstractInstruction.BoundArgumentConstantInstruction(Opcode.BIPUSH, CodeImpl.this, pos); + case SIPUSH -> new AbstractInstruction.BoundArgumentConstantInstruction(Opcode.SIPUSH, CodeImpl.this, pos); + case LDC -> new AbstractInstruction.BoundLoadConstantInstruction(Opcode.LDC, CodeImpl.this, pos); + case LDC_W -> new AbstractInstruction.BoundLoadConstantInstruction(Opcode.LDC_W, CodeImpl.this, pos); + case LDC2_W -> new AbstractInstruction.BoundLoadConstantInstruction(Opcode.LDC2_W, CodeImpl.this, pos); + case ILOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ILOAD, CodeImpl.this, pos); + case LLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.LLOAD, CodeImpl.this, pos); + case FLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.FLOAD, CodeImpl.this, pos); + case DLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.DLOAD, CodeImpl.this, pos); + case ALOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ALOAD, CodeImpl.this, pos); + case ISTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ISTORE, CodeImpl.this, pos); + case LSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.LSTORE, CodeImpl.this, pos); + case FSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.FSTORE, CodeImpl.this, pos); + case DSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.DSTORE, CodeImpl.this, pos); + case ASTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ASTORE, CodeImpl.this, pos); + case IINC -> new AbstractInstruction.BoundIncrementInstruction(Opcode.IINC, CodeImpl.this, pos); + case IFEQ -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFEQ, CodeImpl.this, pos); + case IFNE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNE, CodeImpl.this, pos); + case IFLT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFLT, CodeImpl.this, pos); + case IFGE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFGE, CodeImpl.this, pos); + case IFGT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFGT, CodeImpl.this, pos); + case IFLE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFLE, CodeImpl.this, pos); + case IF_ICMPEQ -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPEQ, CodeImpl.this, pos); + case IF_ICMPNE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPNE, CodeImpl.this, pos); + case IF_ICMPLT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPLT, CodeImpl.this, pos); + case IF_ICMPGE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPGE, CodeImpl.this, pos); + case IF_ICMPGT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPGT, CodeImpl.this, pos); + case IF_ICMPLE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPLE, CodeImpl.this, pos); + case IF_ACMPEQ -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ACMPEQ, CodeImpl.this, pos); + case IF_ACMPNE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ACMPNE, CodeImpl.this, pos); + case GOTO -> new AbstractInstruction.BoundBranchInstruction(Opcode.GOTO, CodeImpl.this, pos); + case TABLESWITCH -> new AbstractInstruction.BoundTableSwitchInstruction(Opcode.TABLESWITCH, CodeImpl.this, pos); + case LOOKUPSWITCH -> new AbstractInstruction.BoundLookupSwitchInstruction(Opcode.LOOKUPSWITCH, CodeImpl.this, pos); + case GETSTATIC -> new AbstractInstruction.BoundFieldInstruction(Opcode.GETSTATIC, CodeImpl.this, pos); + case PUTSTATIC -> new AbstractInstruction.BoundFieldInstruction(Opcode.PUTSTATIC, CodeImpl.this, pos); + case GETFIELD -> new AbstractInstruction.BoundFieldInstruction(Opcode.GETFIELD, CodeImpl.this, pos); + case PUTFIELD -> new AbstractInstruction.BoundFieldInstruction(Opcode.PUTFIELD, CodeImpl.this, pos); + case INVOKEVIRTUAL -> new AbstractInstruction.BoundInvokeInstruction(Opcode.INVOKEVIRTUAL, CodeImpl.this, pos); + case INVOKESPECIAL -> new AbstractInstruction.BoundInvokeInstruction(Opcode.INVOKESPECIAL, CodeImpl.this, pos); + case INVOKESTATIC -> new AbstractInstruction.BoundInvokeInstruction(Opcode.INVOKESTATIC, CodeImpl.this, pos); + case INVOKEINTERFACE -> new AbstractInstruction.BoundInvokeInterfaceInstruction(Opcode.INVOKEINTERFACE, CodeImpl.this, pos); + case INVOKEDYNAMIC -> new AbstractInstruction.BoundInvokeDynamicInstruction(Opcode.INVOKEDYNAMIC, CodeImpl.this, pos); + case NEW -> new AbstractInstruction.BoundNewObjectInstruction(CodeImpl.this, pos); + case NEWARRAY -> new AbstractInstruction.BoundNewPrimitiveArrayInstruction(Opcode.NEWARRAY, CodeImpl.this, pos); + case ANEWARRAY -> new AbstractInstruction.BoundNewReferenceArrayInstruction(Opcode.ANEWARRAY, CodeImpl.this, pos); + case CHECKCAST -> new AbstractInstruction.BoundTypeCheckInstruction(Opcode.CHECKCAST, CodeImpl.this, pos); + case INSTANCEOF -> new AbstractInstruction.BoundTypeCheckInstruction(Opcode.INSTANCEOF, CodeImpl.this, pos); + + case WIDE -> { + int bclow = classReader.readU1(pos + 1); + yield switch (bclow) { + case ILOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ILOAD_W, this, pos); + case LLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.LLOAD_W, this, pos); + case FLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.FLOAD_W, this, pos); + case DLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.DLOAD_W, this, pos); + case ALOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ALOAD_W, this, pos); + case ISTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ISTORE_W, this, pos); + case LSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.LSTORE_W, this, pos); + case FSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.FSTORE_W, this, pos); + case DSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.DSTORE_W, this, pos); + case ASTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ASTORE_W, this, pos); + case IINC -> new AbstractInstruction.BoundIncrementInstruction(Opcode.IINC_W, this, pos); + case RET -> throw new UnsupportedOperationException("RET_W instruction not supported"); + default -> throw new UnsupportedOperationException("unknown wide instruction: " + bclow); + }; + } + + case MULTIANEWARRAY -> new AbstractInstruction.BoundNewMultidimensionalArrayInstruction(Opcode.MULTIANEWARRAY, CodeImpl.this, pos); + case IFNULL -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNULL, CodeImpl.this, pos); + case IFNONNULL -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNONNULL, CodeImpl.this, pos); + case GOTO_W -> new AbstractInstruction.BoundBranchInstruction(Opcode.GOTO_W, CodeImpl.this, pos); + + case JSR -> throw new UnsupportedOperationException("JSR instruction not supported"); + case RET -> throw new UnsupportedOperationException("RET instruction not supported"); + case JSR_W -> throw new UnsupportedOperationException("JSR_W instruction not supported"); + default -> { + Instruction instr = SINGLETON_INSTRUCTIONS[bc]; + if (instr == null) + throw new UnsupportedOperationException("unknown instruction: " + bc); + yield instr; + } + }; + } + + @Override + public String toString() { + return String.format("CodeModel[id=%d]", System.identityHashCode(this)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java new file mode 100644 index 0000000000000..f53cab278d3a0 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDescs; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.FieldBuilder; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.FieldTransform; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.WritableElement; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class DirectClassBuilder + extends AbstractDirectBuilder + implements ClassBuilder { + + final ClassEntry thisClassEntry; + private final List> fields = new ArrayList<>(); + private final List> methods = new ArrayList<>(); + private ClassEntry superclassEntry; + private List interfaceEntries; + private int majorVersion; + private int minorVersion; + private int flags; + private int sizeHint; + + public DirectClassBuilder(SplitConstantPool constantPool, + ClassEntry thisClass) { + super(constantPool); + this.thisClassEntry = AbstractPoolEntry.maybeClone(constantPool, thisClass); + this.flags = Classfile.DEFAULT_CLASS_FLAGS; + this.superclassEntry = null; + this.interfaceEntries = Collections.emptyList(); + this.majorVersion = Classfile.LATEST_MAJOR_VERSION; + this.minorVersion = Classfile.LATEST_MINOR_VERSION; + } + + @Override + public ClassBuilder with(ClassElement element) { + ((AbstractElement) element).writeTo(this); + return this; + } + + @Override + public ClassBuilder withField(Utf8Entry name, + Utf8Entry descriptor, + Consumer handler) { + return withField(new DirectFieldBuilder(constantPool, name, descriptor, null) + .run(handler)); + } + + @Override + public ClassBuilder transformField(FieldModel field, FieldTransform transform) { + DirectFieldBuilder builder = new DirectFieldBuilder(constantPool, field.fieldName(), + field.fieldType(), field); + builder.transform(field, transform); + return withField(builder); + } + + @Override + public ClassBuilder withMethod(Utf8Entry name, + Utf8Entry descriptor, + int flags, + Consumer handler) { + return withMethod(new DirectMethodBuilder(constantPool, name, descriptor, flags, null) + .run(handler)); + } + + @Override + public ClassBuilder transformMethod(MethodModel method, MethodTransform transform) { + DirectMethodBuilder builder = new DirectMethodBuilder(constantPool, method.methodName(), + method.methodType(), + method.flags().flagsMask(), + method); + builder.transform(method, transform); + return withMethod(builder); + } + + // internal / for use by elements + + public ClassBuilder withField(WritableElement field) { + fields.add(field); + return this; + } + + public ClassBuilder withMethod(WritableElement method) { + methods.add(method); + return this; + } + + void setSuperclass(ClassEntry superclassEntry) { + this.superclassEntry = superclassEntry; + } + + void setInterfaces(List interfaces) { + this.interfaceEntries = interfaces; + } + + void setVersion(int major, int minor) { + this.majorVersion = major; + this.minorVersion = minor; + } + + void setFlags(int flags) { + this.flags = flags; + } + + void setSizeHint(int sizeHint) { + this.sizeHint = sizeHint; + } + + + public byte[] build() { + + // The logic of this is very carefully ordered. We want to avoid + // repeated buffer copyings, so we accumulate lists of writers which + // all get written later into the same buffer. But, writing can often + // trigger CP / BSM insertions, so we cannot run the CP writer or + // BSM writers until everything else is written. + + // Do this early because it might trigger CP activity + ClassEntry superclass = superclassEntry; + if (superclass != null) + superclass = AbstractPoolEntry.maybeClone(constantPool, superclass); + else if ((flags & Classfile.ACC_MODULE) == 0 && !"java/lang/Object".equals(thisClassEntry.asInternalName())) + superclass = constantPool.classEntry(ConstantDescs.CD_Object); + List ies = new ArrayList<>(interfaceEntries.size()); + for (ClassEntry ce : interfaceEntries) + ies.add(AbstractPoolEntry.maybeClone(constantPool, ce)); + + // We maintain two writers, and then we join them at the end + int size = sizeHint == 0 ? 256 : sizeHint; + BufWriter head = new BufWriterImpl(constantPool, size); + BufWriterImpl tail = new BufWriterImpl(constantPool, size); + tail.setThisClass(thisClassEntry); + + // The tail consists of fields and methods, and attributes + // This should trigger all the CP/BSM mutation + tail.writeList(fields); + tail.writeList(methods); + int attributesOffset = tail.size(); + attributes.writeTo(tail); + + // Now we have to append the BSM, if there is one + boolean written = constantPool.writeBootstrapMethods(tail); + if (written) { + // Update attributes count + tail.patchInt(attributesOffset, 2, attributes.size() + 1); + } + + // Now we can make the head + head.writeInt(Classfile.MAGIC_NUMBER); + head.writeU2(minorVersion); + head.writeU2(majorVersion); + constantPool.writeTo(head); + head.writeU2(flags); + head.writeIndex(thisClassEntry); + head.writeIndexOrZero(superclass); + head.writeListIndices(ies); + + // Join head and tail into an exact-size buffer + byte[] result = new byte[head.size() + tail.size()]; + head.copyTo(result, 0); + tail.copyTo(result, head.size()); + return result; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java new file mode 100644 index 0000000000000..8d282347f63e8 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.SwitchCase; +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.attribute.LineNumberTableAttribute; +import jdk.internal.classfile.attribute.StackMapTableAttribute; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.DoubleEntry; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.LongEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.instruction.CharacterRange; +import jdk.internal.classfile.instruction.ExceptionCatch; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; + +import static jdk.internal.classfile.Opcode.GOTO; +import static jdk.internal.classfile.Opcode.GOTO_W; +import static jdk.internal.classfile.Opcode.IINC; +import static jdk.internal.classfile.Opcode.IINC_W; +import static jdk.internal.classfile.Opcode.LDC2_W; +import static jdk.internal.classfile.Opcode.LDC_W; + +public final class DirectCodeBuilder + extends AbstractDirectBuilder + implements TerminalCodeBuilder, LabelContext { + private final List characterRanges = new ArrayList<>(); + private final List handlers = new ArrayList<>(); + private final List localVariables = new ArrayList<>(); + private final List localVariableTypes = new ArrayList<>(); + private final boolean transformFwdJumps, transformBackJumps; + private final Label startLabel, endLabel; + private final MethodInfo methodInfo; + final BufWriter bytecodesBufWriter; + private CodeAttribute mruParent; + private int[] mruParentTable; + private Map parentMap; + private DedupLineNumberTableAttribute lineNumberWriter; + private int topLocal; + + List deferredLabels; + + /* Locals management + lazily computed maxLocal = -1 + first time: derive count from methodType descriptor (for new methods) & ACC_STATIC, + or model maxLocals (for transformation) + block builders inherit parent count + allocLocal(TypeKind) bumps by nSlots + */ + + public static Attribute build(MethodInfo methodInfo, + Consumer handler, + SplitConstantPool constantPool, + CodeModel original) { + DirectCodeBuilder cb; + try { + handler.accept(cb = new DirectCodeBuilder(methodInfo, constantPool, original, false)); + cb.buildContent(); + } catch (LabelOverflowException loe) { + if (constantPool.options().fixJumps) { + handler.accept(cb = new DirectCodeBuilder(methodInfo, constantPool, original, true)); + cb.buildContent(); + } + else + throw loe; + } + return cb.content; + } + + private DirectCodeBuilder(MethodInfo methodInfo, + SplitConstantPool constantPool, + CodeModel original, + boolean transformFwdJumps) { + super(constantPool); + setOriginal(original); + this.methodInfo = methodInfo; + this.transformFwdJumps = transformFwdJumps; + this.transformBackJumps = constantPool.options().fixJumps; + bytecodesBufWriter = (original instanceof CodeImpl cai) ? new BufWriterImpl(constantPool, cai.codeLength()) + : new BufWriterImpl(constantPool); + this.startLabel = new LabelImpl(this, 0); + this.endLabel = new LabelImpl(this, -1); + this.topLocal = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodType().stringValue()); + if (original != null) + this.topLocal = Math.max(this.topLocal, original.maxLocals()); + } + + @Override + public CodeBuilder with(CodeElement element) { + ((AbstractElement) element).writeTo(this); + return this; + } + + @Override + public Label newLabel() { + return new LabelImpl(this, -1); + } + + @Override + public Label startLabel() { + return startLabel; + } + + @Override + public Label endLabel() { + return endLabel; + } + + @Override + public int receiverSlot() { + return methodInfo.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return methodInfo.parameterSlot(paramNo); + } + + public int curTopLocal() { + return topLocal; + } + + @Override + public int allocateLocal(TypeKind typeKind) { + int retVal = topLocal; + topLocal += typeKind.slotSize(); + return retVal; + } + + public int curPc() { + return bytecodesBufWriter.size(); + } + + public MethodInfo methodInfo() { + return methodInfo; + } + + private Attribute content = null; + + private void writeExceptionHandlers(BufWriter buf) { + int pos = buf.size(); + int handlersSize = handlers.size(); + buf.writeU2(handlersSize); + for (AbstractPseudoInstruction.ExceptionCatchImpl h : handlers) { + int startPc = labelToBci(h.tryStart()); + int endPc = labelToBci(h.tryEnd()); + int handlerPc = labelToBci(h.handler()); + if (startPc == -1 || endPc == -1 || handlerPc == -1) { + if (constantPool.options().filterDeadLabels) { + handlersSize--; + } else { + throw new IllegalStateException("Unbound label in exception handler"); + } + } else { + buf.writeU2(startPc); + buf.writeU2(endPc); + buf.writeU2(handlerPc); + buf.writeIndexOrZero(h.catchTypeEntry()); + handlersSize++; + } + } + if (handlersSize < handlers.size()) + buf.patchInt(pos, 2, handlersSize); + } + + private void buildContent() { + if (content != null) return; + setLabelTarget(endLabel); + + // Backfill branches for which Label didn't have position yet + processDeferredLabels(); + + if (constantPool.options().processDebug) { + if (!characterRanges.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.CHARACTER_RANGE_TABLE) { + + @Override + public void writeBody(BufWriter b) { + int pos = b.size(); + int crSize = characterRanges.size(); + b.writeU2(crSize); + for (CharacterRange cr : characterRanges) { + var start = labelToBci(cr.startScope()); + var end = labelToBci(cr.endScope()); + if (start == -1 || end == -1) { + if (constantPool.options().filterDeadLabels) { + crSize--; + } else { + throw new IllegalStateException("Unbound label in character range"); + } + } else { + b.writeU2(start); + b.writeU2(end - 1); + b.writeInt(cr.characterRangeStart()); + b.writeInt(cr.characterRangeEnd()); + b.writeU2(cr.flags()); + } + } + if (crSize < characterRanges.size()) + b.patchInt(pos, 2, crSize); + } + }; + attributes.withAttribute(a); + } + + if (!localVariables.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.LOCAL_VARIABLE_TABLE) { + @Override + public void writeBody(BufWriter b) { + int pos = b.size(); + int lvSize = localVariables.size(); + b.writeU2(lvSize); + for (LocalVariable l : localVariables) { + if (!l.writeTo(b)) { + if (constantPool.options().filterDeadLabels) { + lvSize--; + } else { + throw new IllegalStateException("Unbound label in local variable type"); + } + } + } + if (lvSize < localVariables.size()) + b.patchInt(pos, 2, lvSize); + } + }; + attributes.withAttribute(a); + } + + if (!localVariableTypes.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.LOCAL_VARIABLE_TYPE_TABLE) { + @Override + public void writeBody(BufWriter b) { + int pos = b.size(); + int lvtSize = localVariableTypes.size(); + b.writeU2(localVariableTypes.size()); + for (LocalVariableType l : localVariableTypes) { + if (!l.writeTo(b)) { + if (constantPool.options().filterDeadLabels) { + lvtSize--; + } else { + throw new IllegalStateException("Unbound label in local variable type"); + } + } + } + if (lvtSize < localVariableTypes.size()) + b.patchInt(pos, 2, lvtSize); + } + }; + attributes.withAttribute(a); + } + } + + if (lineNumberWriter != null) { + attributes.withAttribute(lineNumberWriter); + } + + content = new UnboundAttribute.AdHocAttribute<>(Attributes.CODE) { + @Override + public void writeBody(BufWriter b) { + BufWriterImpl buf = (BufWriterImpl) b; + buf.setLabelContext(DirectCodeBuilder.this); + + int codeLength = curPc(); + int maxStack, maxLocals; + Attribute stackMapAttr; + boolean canReuseStackmaps = codeAndExceptionsMatch(codeLength); + + if (!constantPool.options().generateStackmaps) { + maxStack = maxLocals = 255; + stackMapAttr = null; + } + else if (canReuseStackmaps) { + maxLocals = original.maxLocals(); + maxStack = original.maxStack(); + stackMapAttr = original.findAttribute(Attributes.STACK_MAP_TABLE).orElse(null); + } + else { + //new instance of generator immediately calculates maxStack, maxLocals, all frames, + // patches dead bytecode blocks and removes them from exception table + StackMapGenerator gen = new StackMapGenerator(DirectCodeBuilder.this, + buf.thisClass().asSymbol(), + methodInfo.methodName().stringValue(), + MethodTypeDesc.ofDescriptor(methodInfo.methodType().stringValue()), + (methodInfo.methodFlags() & Classfile.ACC_STATIC) != 0, + bytecodesBufWriter.asByteBuffer().slice(0, codeLength), + constantPool, + handlers); + maxStack = gen.maxStack(); + maxLocals = gen.maxLocals(); + stackMapAttr = gen.stackMapTableAttribute(); + } + attributes.withAttribute(stackMapAttr); + + buf.writeU2(maxStack); + buf.writeU2(maxLocals); + buf.writeInt(codeLength); + buf.writeBytes(bytecodesBufWriter); + writeExceptionHandlers(b); + attributes.writeTo(b); + buf.setLabelContext(null); + } + }; + } + + private static class DedupLineNumberTableAttribute extends UnboundAttribute.AdHocAttribute { + private final BufWriterImpl buf; + private int lastPc, lastLine, writtenLine; + + public DedupLineNumberTableAttribute(ConstantPoolBuilder constantPool) { + super(Attributes.LINE_NUMBER_TABLE); + buf = new BufWriterImpl(constantPool); + lastPc = -1; + writtenLine = -1; + } + + private void push() { + //subsequent identical line numbers are skipped + if (lastPc >= 0 && lastLine != writtenLine) { + buf.writeU2(lastPc); + buf.writeU2(lastLine); + writtenLine = lastLine; + } + } + + //writes are expected ordered by pc in ascending sequence + public void writeLineNumber(int pc, int lineNo) { + //for each pc only the latest line number is written + if (lastPc != pc && lastLine != lineNo) { + push(); + lastPc = pc; + } + lastLine = lineNo; + } + + @Override + public void writeBody(BufWriter b) { + throw new UnsupportedOperationException(); + } + + @Override + public void writeTo(BufWriter b) { + b.writeIndex(b.constantPool().utf8Entry(Attributes.NAME_LINE_NUMBER_TABLE)); + push(); + b.writeInt(buf.size() + 2); + b.writeU2(buf.size() / 4); + b.writeBytes(buf); + } + } + + private boolean codeAndExceptionsMatch(int codeLength) { + boolean codeAttributesMatch; + if (original instanceof CodeImpl cai && canWriteDirect(cai.constantPool())) { + codeAttributesMatch = cai.codeLength == curPc() + && cai.compareCodeBytes(bytecodesBufWriter, 0, codeLength); + if (codeAttributesMatch) { + BufWriter bw = new BufWriterImpl(constantPool); + writeExceptionHandlers(bw); + codeAttributesMatch = cai.classReader.compare(bw, 0, cai.exceptionHandlerPos, bw.size()); + } + } + else + codeAttributesMatch = false; + return codeAttributesMatch; + } + + // Writing support + + private record DeferredLabel(int labelPc, int size, int instructionPc, Label label) { } + + private void writeLabelOffset(int nBytes, int instructionPc, Label label) { + int targetBci = labelToBci(label); + if (targetBci == -1) { + int pc = curPc(); + bytecodesBufWriter.writeIntBytes(nBytes, 0); + if (deferredLabels == null) + deferredLabels = new ArrayList<>(); + deferredLabels.add(new DeferredLabel(pc, nBytes, instructionPc, label)); + } + else { + int branchOffset = targetBci - instructionPc; + if (nBytes == 2 && (short)branchOffset != branchOffset) throw new LabelOverflowException(); + bytecodesBufWriter.writeIntBytes(nBytes, branchOffset); + } + } + + private void processDeferredLabels() { + if (deferredLabels != null) { + for (DeferredLabel dl : deferredLabels) { + int branchOffset = labelToBci(dl.label) - dl.instructionPc; + if (dl.size == 2 && (short)branchOffset != branchOffset) throw new LabelOverflowException(); + bytecodesBufWriter.patchInt(dl.labelPc, dl.size, branchOffset); + } + } + } + + // Instruction writing + + public void writeBytecode(Opcode opcode) { + if (opcode.isWide()) + bytecodesBufWriter.writeU1(Classfile.WIDE); + bytecodesBufWriter.writeU1(opcode.bytecode() & 0xFF); + } + + public void writeLoad(Opcode opcode, int localVar) { + writeBytecode(opcode); + switch (opcode.sizeIfFixed()) { + case 1 -> { } + case 2 -> bytecodesBufWriter.writeU1(localVar); + case 4 -> bytecodesBufWriter.writeU2(localVar); + default -> throw new IllegalArgumentException("Unexpected instruction size: " + opcode); + } + } + + public void writeStore(Opcode opcode, int localVar) { + writeBytecode(opcode); + switch (opcode.sizeIfFixed()) { + case 1 -> { } + case 2 -> bytecodesBufWriter.writeU1(localVar); + case 4 -> bytecodesBufWriter.writeU2(localVar); + default -> throw new IllegalArgumentException("Unexpected instruction size: " + opcode); + } + } + + public void writeIncrement(int slot, int val) { + Opcode opcode = (slot < 256 && val < 128 && val > -127) + ? IINC + : IINC_W; + writeBytecode(opcode); + if (opcode.isWide()) { + bytecodesBufWriter.writeU2(slot); + bytecodesBufWriter.writeU2(val); + } else { + bytecodesBufWriter.writeU1(slot); + bytecodesBufWriter.writeU1(val); + } + } + + public void writeBranch(Opcode op, Label target) { + int instructionPc = curPc(); + int targetBci = labelToBci(target); + //transform short-opcode forward jumps if enforced, and backward jumps if enabled and overflowing + if (op.sizeIfFixed() == 3 && (targetBci == -1 + ? transformFwdJumps + : (transformBackJumps + && targetBci - instructionPc < Short.MIN_VALUE))) { + if (op == GOTO) { + writeBytecode(GOTO_W); + writeLabelOffset(4, instructionPc, target); + } else { + writeBytecode(BytecodeHelpers.reverseBranchOpcode(op)); + Label bypassJump = newLabel(); + writeLabelOffset(2, instructionPc, bypassJump); + writeBytecode(GOTO_W); + writeLabelOffset(4, instructionPc + 3, target); + labelBinding(bypassJump); + } + } else { + writeBytecode(op); + writeLabelOffset(op.sizeIfFixed() == 3 ? 2 : 4, instructionPc, target); + } + } + + public void writeLookupSwitch(Label defaultTarget, List cases) { + int instructionPc = curPc(); + writeBytecode(Opcode.LOOKUPSWITCH); + int pad = 4 - (curPc() % 4); + if (pad != 4) + bytecodesBufWriter.writeIntBytes(pad, 0); + writeLabelOffset(4, instructionPc, defaultTarget); + bytecodesBufWriter.writeInt(cases.size()); + cases = new ArrayList<>(cases); + cases.sort(new Comparator() { + @Override + public int compare(SwitchCase c1, SwitchCase c2) { + return Integer.compare(c1.caseValue(), c2.caseValue()); + } + }); + for (var c : cases) { + bytecodesBufWriter.writeInt(c.caseValue()); + writeLabelOffset(4, instructionPc, c.target()); + } + } + + public void writeTableSwitch(int low, int high, Label defaultTarget, List cases) { + int instructionPc = curPc(); + writeBytecode(Opcode.TABLESWITCH); + int pad = 4 - (curPc() % 4); + if (pad != 4) + bytecodesBufWriter.writeIntBytes(pad, 0); + writeLabelOffset(4, instructionPc, defaultTarget); + bytecodesBufWriter.writeInt(low); + bytecodesBufWriter.writeInt(high); + var caseMap = new HashMap(cases.size()); + for (var c : cases) { + caseMap.put(c.caseValue(), c.target()); + } + for (long l = low; l<=high; l++) { + writeLabelOffset(4, instructionPc, caseMap.getOrDefault((int)l, defaultTarget)); + } + } + + public void writeFieldAccess(Opcode opcode, FieldRefEntry ref) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(ref); + } + + public void writeInvokeNormal(Opcode opcode, MemberRefEntry ref) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(ref); + } + + public void writeInvokeInterface(Opcode opcode, + InterfaceMethodRefEntry ref, + int count) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(ref); + bytecodesBufWriter.writeU1(count); + bytecodesBufWriter.writeU1(0); + } + + public void writeInvokeDynamic(InvokeDynamicEntry ref) { + writeBytecode(Opcode.INVOKEDYNAMIC); + bytecodesBufWriter.writeIndex(ref); + bytecodesBufWriter.writeU2(0); + } + + public void writeNewObject(ClassEntry type) { + writeBytecode(Opcode.NEW); + bytecodesBufWriter.writeIndex(type); + } + + public void writeNewPrimitiveArray(int newArrayCode) { + writeBytecode(Opcode.NEWARRAY); + bytecodesBufWriter.writeU1(newArrayCode); + } + + public void writeNewReferenceArray(ClassEntry type) { + writeBytecode(Opcode.ANEWARRAY); + bytecodesBufWriter.writeIndex(type); + } + + public void writeNewMultidimensionalArray(int dimensions, ClassEntry type) { + writeBytecode(Opcode.MULTIANEWARRAY); + bytecodesBufWriter.writeIndex(type); + bytecodesBufWriter.writeU1(dimensions); + } + + public void writeTypeCheck(Opcode opcode, ClassEntry type) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(type); + } + + public void writeArgumentConstant(Opcode opcode, int value) { + writeBytecode(opcode); + if (opcode.sizeIfFixed() == 3) { + bytecodesBufWriter.writeU2(value); + } else { + bytecodesBufWriter.writeU1(value); + } + } + + public void writeLoadConstant(Opcode opcode, LoadableConstantEntry value) { + // Make sure Long and Double have LDC2_W and + // rewrite to _W if index is > 256 + int index = AbstractPoolEntry.maybeClone(constantPool, value).index(); + Opcode op = opcode; + if (value instanceof LongEntry || value instanceof DoubleEntry) { + op = LDC2_W; + } else if (index >= 256) + op = LDC_W; + + writeBytecode(op); + if (op.sizeIfFixed() == 3) { + bytecodesBufWriter.writeU2(index); + } else { + bytecodesBufWriter.writeU1(index); + } + } + + @Override + public Label getLabel(int bci) { + throw new UnsupportedOperationException("Lookup by BCI not supported by CodeBuilder"); + } + + @Override + public int labelToBci(Label label) { + LabelImpl lab = (LabelImpl) label; + LabelContext context = lab.labelContext(); + if (context == this) { + return lab.getBCI(); + } + else if (context == mruParent) { + return mruParentTable[lab.getBCI()] - 1; + } + else if (context instanceof CodeAttribute parent) { + if (parentMap == null) + parentMap = new IdentityHashMap<>(); + //critical JDK bootstrap path, cannot use lambda here + int[] table = parentMap.computeIfAbsent(parent, new Function() { + @Override + public int[] apply(CodeAttribute x) { + return new int[parent.codeLength() + 1]; + } + }); + + mruParent = parent; + mruParentTable = table; + return mruParentTable[lab.getBCI()] - 1; + } + else if (context instanceof BufferedCodeBuilder) { + // Hijack the label + return lab.getBCI(); + } + else { + throw new IllegalStateException(String.format("Unexpected label context %s in =%s", context, this)); + } + } + + public void setLineNumber(int lineNo) { + if (lineNumberWriter == null) + lineNumberWriter = new DedupLineNumberTableAttribute(constantPool); + lineNumberWriter.writeLineNumber(curPc(), lineNo); + } + + public void setLabelTarget(Label label) { + setLabelTarget(label, curPc()); + } + + @Override + public void setLabelTarget(Label label, int bci) { + LabelImpl lab = (LabelImpl) label; + LabelContext context = lab.labelContext(); + + if (context == this) { + if (lab.getBCI() != -1) + throw new IllegalStateException("Setting label target for already-set label"); + lab.setBCI(bci); + } + else if (context == mruParent) { + mruParentTable[lab.getBCI()] = bci + 1; + } + else if (context instanceof CodeAttribute parent) { + if (parentMap == null) + parentMap = new IdentityHashMap<>(); + int[] table = parentMap.computeIfAbsent(parent, new Function() { + @Override + public int[] apply(CodeAttribute x) { + return new int[parent.codeLength() + 1]; + } + }); + + mruParent = parent; + mruParentTable = table; + mruParentTable[lab.getBCI()] = bci + 1; + } + else if (context instanceof BufferedCodeBuilder) { + // Hijack the label + lab.setBCI(bci); + } + else { + throw new IllegalStateException(String.format("Unexpected label context %s in =%s", context, this)); + } + } + + public void addCharacterRange(CharacterRange element) { + characterRanges.add(element); + } + + public void addHandler(ExceptionCatch element) { + AbstractPseudoInstruction.ExceptionCatchImpl el = (AbstractPseudoInstruction.ExceptionCatchImpl) element; + ClassEntry type = el.catchTypeEntry(); + if (type != null && !constantPool.canWriteDirect(type.constantPool())) + el = new AbstractPseudoInstruction.ExceptionCatchImpl(element.handler(), element.tryStart(), element.tryEnd(), AbstractPoolEntry.maybeClone(constantPool, type)); + handlers.add(el); + } + + public void addLocalVariable(LocalVariable element) { + localVariables.add(element); + } + + public void addLocalVariableType(LocalVariableType element) { + localVariableTypes.add(element); + } + + @Override + public String toString() { + return String.format("CodeBuilder[id=%d]", System.identityHashCode(this)); + } + + //ToDo: consolidate and open all exceptions + private static final class LabelOverflowException extends IllegalStateException { + + private static final long serialVersionUID = 1L; + + public LabelOverflowException() { + super("Label target offset overflow"); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java new file mode 100644 index 0000000000000..9edcdc9c0b6c4 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.util.function.Consumer; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.FieldBuilder; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.WritableElement; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class DirectFieldBuilder + extends AbstractDirectBuilder + implements TerminalFieldBuilder, WritableElement { + private final Utf8Entry name; + private final Utf8Entry desc; + private int flags; + + public DirectFieldBuilder(SplitConstantPool constantPool, + Utf8Entry name, + Utf8Entry type, + FieldModel original) { + super(constantPool); + setOriginal(original); + this.name = name; + this.desc = type; + this.flags = 0; + } + + @Override + public FieldBuilder with(FieldElement element) { + ((AbstractElement) element).writeTo(this); + return this; + } + + public DirectFieldBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + void setFlags(int flags) { + this.flags = flags; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU2(flags); + buf.writeIndex(name); + buf.writeIndex(desc); + attributes.writeTo(buf); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectMethodBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectMethodBuilder.java new file mode 100644 index 0000000000000..4e10e31929932 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectMethodBuilder.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.util.function.Consumer; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.WritableElement; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class DirectMethodBuilder + extends AbstractDirectBuilder + implements TerminalMethodBuilder, WritableElement, MethodInfo { + + final Utf8Entry name; + final Utf8Entry desc; + int flags; + int[] parameterSlots; + + public DirectMethodBuilder(SplitConstantPool constantPool, + Utf8Entry nameInfo, + Utf8Entry typeInfo, + int flags, + MethodModel original) { + super(constantPool); + setOriginal(original); + this.name = nameInfo; + this.desc = typeInfo; + this.flags = flags; + } + + void setFlags(int flags) { + boolean wasStatic = (this.flags & Classfile.ACC_STATIC) != 0; + boolean isStatic = (flags & Classfile.ACC_STATIC) != 0; + if (wasStatic != isStatic) + throw new IllegalArgumentException("Cannot change ACC_STATIC flag of method"); + this.flags = flags; + } + + @Override + public Utf8Entry methodName() { + return name; + } + + @Override + public Utf8Entry methodType() { + return desc; + } + + @Override + public int methodFlags() { + return flags; + } + + @Override + public int parameterSlot(int paramNo) { + if (parameterSlots == null) + parameterSlots = Util.parseParameterSlots(methodFlags(), methodType().stringValue()); + return parameterSlots[paramNo]; + } + + @Override + public BufferedCodeBuilder bufferedCodeBuilder(CodeModel original) { + return new BufferedCodeBuilder(this, constantPool, original); + } + + @Override + public MethodBuilder with(MethodElement element) { + ((AbstractElement) element).writeTo(this); + return this; + } + + private MethodBuilder withCode(CodeModel original, + Consumer handler) { + var cb = DirectCodeBuilder.build(this, handler, constantPool, original); + writeAttribute(cb); + return this; + } + + @Override + public MethodBuilder withCode(Consumer handler) { + return withCode(null, handler); + } + + @Override + public MethodBuilder transformCode(CodeModel code, CodeTransform transform) { + return withCode(code, new Consumer<>() { + @Override + public void accept(CodeBuilder builder) { + builder.transform(code, transform); + } + }); + } + + public DirectMethodBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + @Override + public void writeTo(BufWriter b) { + BufWriterImpl buf = (BufWriterImpl) b; + buf.writeU2(flags); + buf.writeIndex(name); + buf.writeIndex(desc); + attributes.writeTo(buf); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/EntryMap.java b/src/java.base/share/classes/jdk/internal/classfile/impl/EntryMap.java new file mode 100644 index 0000000000000..bb54618ab312f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/EntryMap.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +/** + * An open-chain multimap used to map nonzero hashes to indexes (of either CP + * elements or BSM entries). Code transformed from public domain implementation + * (http://java-performance.info/). + * + * The internal data structure is an array of 2N int elements, where the first + * element is the hash and the second is the mapped index. To look something up + * in the map, provide a hash value and an index to map it to, and invoke + * firstToken(hash). This returns an opaque token that can be provided to + * nextToken(hash, token) to get the next candidate, or to getElementByToken(token) + * or getIndexByToken to get the mapped element or index. + */ +public abstract class EntryMap { + public static final int NO_VALUE = -1; + + /** + * Keys and values + */ + private int[] data; + + /** + * Fill factor, must be between (0 and 1) + */ + private final float fillFactor; + /** + * We will resize a map once it reaches this size + */ + private int resizeThreshold; + /** + * Current map size + */ + private int size; + + /** + * Mask to calculate the original position + */ + private int mask1; + private int mask2; + + public EntryMap(int size, float fillFactor) { + if (fillFactor <= 0 || fillFactor >= 1) + throw new IllegalArgumentException("FillFactor must be in (0, 1)"); + if (size <= 0) + throw new IllegalArgumentException("Size must be positive!"); + + int capacity = arraySize(size, fillFactor); + this.fillFactor = fillFactor; + this.resizeThreshold = (int) (capacity * fillFactor); + this.mask1 = capacity - 1; + this.mask2 = capacity * 2 - 1; + data = new int[capacity * 2]; + } + + protected abstract T fetchElement(int index); + + public int firstToken(int hash) { + if (hash == 0) + throw new IllegalArgumentException("hash must be nonzero"); + + int ix = (hash & mask1) << 1; + int k = data[ix]; + + if (k == 0) + return NO_VALUE; //end of chain already + else if (k == hash) + return ix; + else + return nextToken(hash, ix); + } + + public int nextToken(int hash, int token) { + int ix = token; + while (true) { + ix = (ix + 2) & mask2; // next index + int k = data[ix]; + if (k == 0) + return NO_VALUE; + else if (k == hash) + return ix; + } + } + + public int getIndexByToken(int token) { + return data[token + 1]; + } + + public T getElementByToken(int token) { + return fetchElement(data[token + 1]); + } + + public void put(int hash, int index) { + if (hash == 0) + throw new IllegalArgumentException("hash must be nonzero"); + + int ptr = (hash & mask1) << 1; + int k = data[ptr]; + if (k == 0) { + data[ptr] = hash; + data[ptr + 1] = index; + if (size >= resizeThreshold) + rehash(data.length * 2); //size is set inside + else + ++size; + return; + } + else if (k == hash && data[ptr + 1] == index) { + return; + } + + while (true) { + ptr = (ptr + 2) & mask2; // next index + k = data[ptr]; + if (k == 0) { + data[ptr] = hash; + data[ptr + 1] = index; + if (size >= resizeThreshold) + rehash(data.length * 2); //size is set inside + else + ++size; + return; + } + else if (k == hash && data[ptr + 1] == index) { + return; + } + } + } + + public int size() { + return size; + } + + private void rehash(final int newCapacity) { + resizeThreshold = (int) (newCapacity / 2 * fillFactor); + mask1 = newCapacity / 2 - 1; + mask2 = newCapacity - 1; + + final int oldCapacity = data.length; + final int[] oldData = data; + + data = new int[newCapacity]; + size = 0; + + for (int i = 0; i < oldCapacity; i += 2) { + final int oldHash = oldData[i]; + if (oldHash != 0) + put(oldHash, oldData[i + 1]); + } + } + + public static long nextPowerOfTwo( long x ) { + x = -1 >>> Long.numberOfLeadingZeros(x - 1); + return x + 1; + } + + public static int arraySize( final int expected, final float f ) { + final long s = Math.max( 2, nextPowerOfTwo( (long)Math.ceil( expected / f ) ) ); + if ( s > (1 << 30) ) + throw new IllegalArgumentException("Too large (" + expected + + " expected elements with load factor " + f + ")" ); + return (int)s; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java new file mode 100644 index 0000000000000..3ad9e7df61b29 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public final class FieldImpl + extends AbstractElement + implements FieldModel { + + private final ClassReader reader; + private final int startPos, endPos, attributesPos; + private List> attributes; + + public FieldImpl(ClassReader reader, int startPos, int endPos, int attributesPos) { + this.reader = reader; + this.startPos = startPos; + this.endPos = endPos; + this.attributesPos = attributesPos; + } + + @Override + public AccessFlags flags() { + return AccessFlags.ofField(reader.readU2(startPos)); + } + + @Override + public Optional parent() { + if (reader instanceof ClassReaderImpl cri) + return Optional.of(cri.getContainedClass()); + else + return Optional.empty(); + } + + @Override + public Utf8Entry fieldName() { + return reader.readUtf8Entry(startPos + 2); + } + + @Override + public Utf8Entry fieldType() { + return reader.readUtf8Entry(startPos + 4); + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } + + @Override + public void writeTo(BufWriter buf) { + if (buf.canWriteDirect(reader)) { + reader.copyBytesTo(buf, startPos, endPos - startPos); + } + else { + buf.writeU2(flags().flagsMask()); + buf.writeIndex(fieldName()); + buf.writeIndex(fieldType()); + buf.writeList(attributes()); + } + } + + // FieldModel + + @Override + public void writeTo(DirectClassBuilder builder) { + if (builder.canWriteDirect(reader)) { + builder.withField(this); + } + else { + builder.withField(fieldName(), fieldType(), new Consumer<>() { + @Override + public void accept(FieldBuilder fb) { + FieldImpl.this.forEachElement(fb); + } + }); + } + } + + @Override + public void forEachElement(Consumer consumer) { + consumer.accept(flags()); + for (Attribute attr : attributes()) { + if (attr instanceof FieldElement e) + consumer.accept(e); + } + } + + @Override + public String toString() { + return String.format("FieldModel[fieldName=%s, fieldType=%s, flags=%d]", + fieldName().stringValue(), fieldType().stringValue(), flags().flagsMask()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/InterfacesImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/InterfacesImpl.java new file mode 100644 index 0000000000000..e5b2a8fc663b5 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/InterfacesImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.stream.Collectors; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Interfaces; + +public final class InterfacesImpl + extends AbstractElement + implements Interfaces { + private final List interfaces; + + public InterfacesImpl(List interfaces) { + this.interfaces = List.copyOf(interfaces); + } + + @Override + public List interfaces() { + return interfaces; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setInterfaces(interfaces); + } + + @Override + public String toString() { + return String.format("Interfaces[interfaces=%s]", interfaces.stream() + .map(iface -> iface.name().stringValue()) + .collect(Collectors.joining(", "))); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java b/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java new file mode 100644 index 0000000000000..3dd4b288233fc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/LabelContext.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.Label; + +public sealed interface LabelContext + permits BufferedCodeBuilder, CodeImpl, DirectCodeBuilder { + Label newLabel(); + Label getLabel(int bci); + void setLabelTarget(Label label, int bci); + int labelToBci(Label label); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/LabelImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/LabelImpl.java new file mode 100644 index 0000000000000..c64ae1ff39b4c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/LabelImpl.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Objects; + +import jdk.internal.classfile.Label; +import jdk.internal.classfile.instruction.LabelTarget; + +/** + * Labels are created with a parent context, which is either a code attribute + * or a code builder. A label originating in a code attribute context may be + * reused in a code builder context, but only labels from a single code + * attribute may be reused by a single code builder. Mappings to and from + * BCI are the responsibility of the context in which it is used; a single + * word of mutable state is provided, for the exclusive use of the owning + * context. + * + * In practice, this means that labels created in a code attribute can simply + * store the BCI in the state on creation, and labels created in in a code + * builder can store the BCI in the state when the label is eventually set; if + * a code attribute label is reused in a builder, the original BCI can be used + * as an index into an array. + */ +public final class LabelImpl + extends AbstractElement + implements Label, LabelTarget { + + private final LabelContext labelContext; + private int bci; + + public LabelImpl(LabelContext labelContext, int bci) { + this.labelContext = Objects.requireNonNull(labelContext); + this.bci = bci; + } + + public LabelContext labelContext() { + return labelContext; + } + + public int getBCI() { + return bci; + } + + public void setBCI(int bci) { + this.bci = bci; + } + + @Override + public Label label() { + return this; + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.setLabelTarget(this); + } + + @Override + public String toString() { + return String.format("Label[context=%s, bci=%d]", labelContext, bci); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/LineNumberImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/LineNumberImpl.java new file mode 100644 index 0000000000000..604a179ec404e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/LineNumberImpl.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.instruction.LineNumber; + +public final class LineNumberImpl + extends AbstractElement + implements LineNumber { + private static final int INTERN_LIMIT = 1000; + private static final LineNumber[] internCache = new LineNumber[INTERN_LIMIT]; + static { + for (int i=0; i> attributes; + private int[] parameterSlots; + + public MethodImpl(ClassReader reader, int startPos, int endPos, int attrStart) { + this.reader = reader; + this.startPos = startPos; + this.endPos = endPos; + this.attributesPos = attrStart; + } + + @Override + public AccessFlags flags() { + return AccessFlags.ofMethod(reader.readU2(startPos)); + } + + @Override + public Optional parent() { + if (reader instanceof ClassReaderImpl cri) + return Optional.of(cri.getContainedClass()); + else + return Optional.empty(); + } + + @Override + public Utf8Entry methodName() { + return reader.readUtf8Entry(startPos + 2); + } + + @Override + public Utf8Entry methodType() { + return reader.readUtf8Entry(startPos + 4); + } + + @Override + public int methodFlags() { + return reader.readU2(startPos); + } + + @Override + public int parameterSlot(int paramNo) { + if (parameterSlots == null) + parameterSlots = Util.parseParameterSlots(methodFlags(), methodType().stringValue()); + return parameterSlots[paramNo]; + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } + + @Override + public void writeTo(BufWriter b) { + BufWriterImpl buf = (BufWriterImpl) b; + if (buf.canWriteDirect(reader)) { + reader.copyBytesTo(buf, startPos, endPos - startPos); + } + else { + buf.writeU2(flags().flagsMask()); + buf.writeIndex(methodName()); + buf.writeIndex(methodType()); + buf.writeList(attributes()); + } + } + + // MethodModel + + @Override + public Optional code() { + return findAttribute(Attributes.CODE).map(a -> (CodeModel) a); + } + + @Override + public void forEachElement(Consumer consumer) { + consumer.accept(flags()); + for (Attribute attr : attributes()) { + if (attr instanceof MethodElement e) + consumer.accept(e); + } + } + + @Override + public void writeTo(DirectClassBuilder builder) { + if (builder.canWriteDirect(reader)) { + builder.withMethod(this); + } + else { + builder.withMethod(methodName(), methodType(), methodFlags(), + new Consumer<>() { + @Override + public void accept(MethodBuilder mb) { + MethodImpl.this.forEachElement(mb); + } + }); + } + } + + @Override + public String toString() { + return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]", + methodName().stringValue(), methodType().stringValue(), flags().flagsMask()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/MethodInfo.java b/src/java.base/share/classes/jdk/internal/classfile/impl/MethodInfo.java new file mode 100644 index 0000000000000..f7229ed01824c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/MethodInfo.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.constantpool.Utf8Entry; + +import static jdk.internal.classfile.Classfile.ACC_STATIC; + +public interface MethodInfo { + Utf8Entry methodName(); + Utf8Entry methodType(); + int methodFlags(); + + default int receiverSlot() { + if ((methodFlags() & ACC_STATIC) != 0) + throw new IllegalStateException("not an instance method"); + return 0; + } + + int parameterSlot(int paramNo); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.java new file mode 100644 index 0000000000000..057c5f9d90717 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.attribute.ModuleAttribute.ModuleAttributeBuilder; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; + +import java.lang.constant.ClassDesc; +import java.util.*; + +public final class ModuleAttributeBuilderImpl + implements ModuleAttributeBuilder { + + private ModuleEntry moduleEntry; + private Utf8Entry moduleVersion; + private int moduleFlags; + + private final Set requires = new LinkedHashSet<>(); + private final Set exports = new LinkedHashSet<>(); + private final Set opens = new LinkedHashSet<>(); + private final Set uses = new LinkedHashSet<>(); + private final Set provides = new LinkedHashSet<>(); + + public ModuleAttributeBuilderImpl(ModuleDesc moduleName) { + this.moduleEntry = TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(moduleName.moduleName())); + this.moduleFlags = 0; + } + + @Override + public ModuleAttribute build() { + return new UnboundAttribute.UnboundModuleAttribute(moduleEntry, moduleFlags, moduleVersion, + requires, exports, opens, uses, provides); + } + + @Override + public ModuleAttributeBuilder moduleName(ModuleDesc moduleName) { + Objects.requireNonNull(moduleName); + moduleEntry = TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(moduleName.moduleName())); + return this; + } + + @Override + public ModuleAttributeBuilder moduleFlags(int flags) { + this.moduleFlags = flags; + return this; + } + + @Override + public ModuleAttributeBuilder moduleVersion(String version) { + moduleVersion = version == null ? null : TemporaryConstantPool.INSTANCE.utf8Entry(version); + return this; + } + + @Override + public ModuleAttributeBuilder requires(ModuleDesc module, int flags, String version) { + Objects.requireNonNull(module); + return requires(ModuleRequireInfo.of(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(module.moduleName())), flags, version == null ? null : TemporaryConstantPool.INSTANCE.utf8Entry(version))); + } + + @Override + public ModuleAttributeBuilder requires(ModuleRequireInfo requires) { + Objects.requireNonNull(requires); + this.requires.add(requires); + return this; + } + + @Override + public ModuleAttributeBuilder exports(PackageDesc pkge, int flags, ModuleDesc... exportsToModules) { + Objects.requireNonNull(pkge); + var exportsTo = new ArrayList(exportsToModules.length); + for (var e : exportsToModules) + exportsTo.add(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(e.moduleName()))); + return exports(ModuleExportInfo.of(TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(pkge.packageInternalName())), flags, exportsTo)); + } + + @Override + public ModuleAttributeBuilder exports(ModuleExportInfo exports) { + Objects.requireNonNull(exports); + this.exports.add(exports); + return this; + } + + @Override + public ModuleAttributeBuilder opens(PackageDesc pkge, int flags, ModuleDesc... opensToModules) { + Objects.requireNonNull(pkge); + var opensTo = new ArrayList(opensToModules.length); + for (var e : opensToModules) + opensTo.add(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(e.moduleName()))); + return opens(ModuleOpenInfo.of(TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(pkge.packageInternalName())), flags, opensTo)); + } + + @Override + public ModuleAttributeBuilder opens(ModuleOpenInfo opens) { + Objects.requireNonNull(opens); + this.opens.add(opens); + return this; + } + + @Override + public ModuleAttributeBuilder uses(ClassDesc service) { + Objects.requireNonNull(service); + return uses(TemporaryConstantPool.INSTANCE.classEntry(service)); + } + + @Override + public ModuleAttributeBuilder uses(ClassEntry uses) { + Objects.requireNonNull(uses); + this.uses.add(uses); + return this; + } + + @Override + public ModuleAttributeBuilder provides(ClassDesc service, ClassDesc... implClasses) { + Objects.requireNonNull(service); + var impls = new ArrayList(implClasses.length); + for (var seq : implClasses) + impls.add(TemporaryConstantPool.INSTANCE.classEntry(seq)); + return provides(ModuleProvideInfo.of(TemporaryConstantPool.INSTANCE.classEntry(service), impls)); + } + + @Override + public ModuleAttributeBuilder provides(ModuleProvideInfo provides) { + Objects.requireNonNull(provides); + this.provides.add(provides); + return this; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ModuleDescImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ModuleDescImpl.java new file mode 100644 index 0000000000000..eefbe926ebbca --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ModuleDescImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.java.lang.constant.ModuleDesc; + +public record ModuleDescImpl(String moduleName) implements ModuleDesc { + + /** + * Validates the correctness of a module name. In particular checks for the presence of + * invalid characters in the name. + * + * {@jvms 4.2.3} Module and Package Names + * + * @param name the module name + * @return the module name passed if valid + * @throws IllegalArgumentException if the module name is invalid + */ + public static String validateModuleName(String name) { + for (int i=name.length() - 1; i >= 0; i--) { + char ch = name.charAt(i); + if ((ch >= '\u0000' && ch <= '\u001F') + || ((ch == '\\' || ch == ':' || ch =='@') && (i == 0 || name.charAt(--i) != '\\'))) + throw new IllegalArgumentException("Invalid module name: " + name); + } + return name; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/NonterminalCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/NonterminalCodeBuilder.java new file mode 100644 index 0000000000000..cf98950460dc2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/NonterminalCodeBuilder.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; + +public abstract sealed class NonterminalCodeBuilder implements CodeBuilder + permits ChainedCodeBuilder, BlockCodeBuilderImpl { + protected final TerminalCodeBuilder terminal; + protected final CodeBuilder parent; + + public NonterminalCodeBuilder(CodeBuilder parent) { + this.parent = parent; + this.terminal = switch (parent) { + case NonterminalCodeBuilder cb -> cb.terminal; + case TerminalCodeBuilder cb -> cb; + }; + } + + @Override + public int receiverSlot() { + return terminal.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return terminal.parameterSlot(paramNo); + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + + @Override + public Optional original() { + return terminal.original(); + } + + @Override + public Label newLabel() { + return terminal.newLabel(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java new file mode 100644 index 0000000000000..d9bf3fd05c7cc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Collection; +import java.util.function.Function; + +import jdk.internal.classfile.AttributeMapper; +import jdk.internal.classfile.ClassHierarchyResolver; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.Utf8Entry; + +import static jdk.internal.classfile.ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER; + +public class Options { + + public enum Key { + GENERATE_STACK_MAPS, PROCESS_DEBUG, PROCESS_LINE_NUMBERS, PROCESS_UNKNOWN_ATTRIBUTES, + CP_SHARING, FIX_SHORT_JUMPS, PATCH_DEAD_CODE, HIERARCHY_RESOLVER, ATTRIBUTE_MAPPER, + FILTER_DEAD_LABELS; + } + + public record OptionValue(Key key, Object value) implements Classfile.Option { } + + public Boolean generateStackmaps = true; + public Boolean processDebug = true; + public Boolean processLineNumbers = true; + public Boolean processUnknownAttributes = true; + public Boolean cpSharing = true; + public Boolean fixJumps = true; + public Boolean patchCode = true; + public Boolean filterDeadLabels = false; + public ClassHierarchyResolver classHierarchyResolver = DEFAULT_CLASS_HIERARCHY_RESOLVER; + public Function> attributeMapper = new Function<>() { + @Override + public AttributeMapper apply(Utf8Entry k) { + return null; + } + }; + + @SuppressWarnings("unchecked") + public Options(Collection options) { + for (var o : options) { + var ov = ((OptionValue)o); + var v = ov.value(); + switch (ov.key()) { + case GENERATE_STACK_MAPS -> generateStackmaps = (Boolean) v; + case PROCESS_DEBUG -> processDebug = (Boolean) v; + case PROCESS_LINE_NUMBERS -> processLineNumbers = (Boolean) v; + case PROCESS_UNKNOWN_ATTRIBUTES -> processUnknownAttributes = (Boolean) v; + case CP_SHARING -> cpSharing = (Boolean) v; + case FIX_SHORT_JUMPS -> fixJumps = (Boolean) v; + case PATCH_DEAD_CODE -> patchCode = (Boolean) v; + case HIERARCHY_RESOLVER -> classHierarchyResolver = (ClassHierarchyResolver) v; + case ATTRIBUTE_MAPPER -> attributeMapper = (Function>) v; + case FILTER_DEAD_LABELS -> filterDeadLabels = (Boolean) v; + } + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/PackageDescImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/PackageDescImpl.java new file mode 100644 index 0000000000000..336cd50af697a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/PackageDescImpl.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.java.lang.constant.PackageDesc; + +public record PackageDescImpl(String packageInternalName) implements PackageDesc { + + /** + * Validates the correctness of a binary package name. In particular checks for the presence of + * invalid characters in the name. + * + * @param name the package name + * @return the package name passed if valid + * @throws IllegalArgumentException if the package name is invalid + */ + public static String validateBinaryPackageName(String name) { + for (int i=0; i= endBci; + } + + public int getShort(int bci) { + return bytecode.getShort(bci); + } + + public int dest() { + return bci + getShort(bci + 1); + } + + public int getInt(int bci) { + return bytecode.getInt(bci); + } + + public int destW() { + return bci + getInt(bci + 1); + } + + public int getIndexU1() { + return bytecode.get(bci + 1) & 0xff; + } + + public int getU1(int bci) { + return bytecode.get(bci) & 0xff; + } + + public int rawNext(int jumpTo) { + this.nextBci = jumpTo; + return rawNext(); + } + + public int rawNext() { + bci = nextBci; + int code = bytecode.get(bci) & 0xff; + int len = LENGTHS[code] & 0xf; + if (len > 0 && (bci <= endBci - len)) { + isWide = false; + nextBci += len; + if (nextBci <= bci) { + code = ILLEGAL; + } + rawCode = code; + return code; + } else { + len = switch (bytecode.get(bci) & 0xff) { + case WIDE -> { + if (bci + 1 >= endBci) { + yield -1; + } + yield LENGTHS[bytecode.get(bci + 1) & 0xff] >> 4; + } + case TABLESWITCH -> { + int aligned_bci = align(bci + 1); + if (aligned_bci + 3 * 4 >= endBci) { + yield -1; + } + int lo = bytecode.getInt(aligned_bci + 1 * 4); + int hi = bytecode.getInt(aligned_bci + 2 * 4); + int l = aligned_bci - bci + (3 + hi - lo + 1) * 4; + if (l > 0) yield l; else yield -1; + } + case LOOKUPSWITCH -> { + int aligned_bci = align(bci + 1); + if (aligned_bci + 2 * 4 >= endBci) { + yield -1; + } + int npairs = bytecode.getInt(aligned_bci + 4); + int l = aligned_bci - bci + (2 + 2 * npairs) * 4; + if (l > 0) yield l; else yield -1; + } + default -> + 0; + }; + if (len <= 0 || (bci > endBci - len) || (bci - len >= nextBci)) { + code = ILLEGAL; + } else { + nextBci += len; + isWide = false; + if (code == WIDE) { + if (bci + 1 >= endBci) { + code = ILLEGAL; + } else { + code = bytecode.get(bci + 1) & 0xff; + isWide = true; + } + } + } + rawCode = code; + return code; + } + } + + public int getIndex() { + return (isWide) ? getIndexU2Raw(bci + 2) : getIndexU1(); + } + + public int getIndexU2() { + return getIndexU2Raw(bci + 1); + } + + public int getIndexU2Raw(int bci) { + return bytecode.getShort(bci) & 0xffff; + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SignaturesImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SignaturesImpl.java new file mode 100644 index 0000000000000..8a6b3ea88728e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SignaturesImpl.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Collections; +import jdk.internal.classfile.ClassSignature; +import jdk.internal.classfile.MethodSignature; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.Signature.*; + +public final class SignaturesImpl { + + public SignaturesImpl() { + } + + private String sig; + private int sigp; + + public ClassSignature parseClassSignature(String signature) { + this.sig = signature; + sigp = 0; + List typeParamTypes = parseParamTypes(); + RefTypeSig superclass = referenceTypeSig(); + ArrayList superinterfaces = null; + while (sigp < sig.length()) { + if (superinterfaces == null) + superinterfaces = new ArrayList<>(); + superinterfaces.add(referenceTypeSig()); + } + return new ClassSignatureImpl(typeParamTypes, superclass, null2Empty(superinterfaces)); + } + + public MethodSignature parseMethodSignature(String signature) { + this.sig = signature; + sigp = 0; + List typeParamTypes = parseParamTypes(); + assert sig.charAt(sigp) == '('; + sigp++; + ArrayList paramTypes = null; + while (sig.charAt(sigp) != ')') { + if (paramTypes == null) + paramTypes = new ArrayList<>(); + paramTypes.add(typeSig()); + } + sigp++; + Signature returnType = typeSig(); + ArrayList throwsTypes = null; + while (sigp < sig.length() && sig.charAt(sigp) == '^') { + sigp++; + if (throwsTypes == null) + throwsTypes = new ArrayList<>(); + var t = typeSig(); + if (t instanceof ThrowableSig ts) + throwsTypes.add(ts); + else + throw new IllegalStateException("not a valid type signature: " + sig); + } + return new MethodSignatureImpl(typeParamTypes, null2Empty(throwsTypes), returnType, null2Empty(paramTypes)); + } + + public Signature parseSignature(String signature) { + this.sig = signature; + sigp = 0; + return typeSig(); + } + + private List parseParamTypes() { + ArrayList typeParamTypes = null; + if (sig.charAt(sigp) == '<') { + sigp++; + typeParamTypes = new ArrayList<>(); + while (sig.charAt(sigp) != '>') { + int sep = sig.indexOf(":", sigp); + String name = sig.substring(sigp, sep); + RefTypeSig classBound = null; + ArrayList interfaceBounds = null; + sigp = sep + 1; + if (sig.charAt(sigp) != ':') + classBound = referenceTypeSig(); + while (sig.charAt(sigp) == ':') { + sigp++; + if (interfaceBounds == null) + interfaceBounds = new ArrayList<>(); + interfaceBounds.add(referenceTypeSig()); + } + typeParamTypes.add(new TypeParamImpl(name, Optional.ofNullable(classBound), null2Empty(interfaceBounds))); + } + sigp++; + } + return null2Empty(typeParamTypes); + } + + private Signature typeSig() { + char c = sig.charAt(sigp++); + switch (c) { + case 'B','C','D','F','I','J','V','S','Z': return Signature.BaseTypeSig.of(c); + default: + sigp--; + return referenceTypeSig(); + } + } + + private RefTypeSig referenceTypeSig() { + char c = sig.charAt(sigp++); + switch (c) { + case 'L': + StringBuilder sb = new StringBuilder(); + ArrayList argTypes = null; + Signature.ClassTypeSig t = null; + char sigch ; + do { + switch (sigch = sig.charAt(sigp++)) { + case '<' -> { + argTypes = new ArrayList<>(); + while (sig.charAt(sigp) != '>') + argTypes.add(typeArg()); + sigp++; + } + case '.',';' -> { + t = new ClassTypeSigImpl(Optional.ofNullable(t), sb.toString(), null2Empty(argTypes)); + sb.setLength(0); + argTypes = null; + } + default -> sb.append(sigch); + } + } while (sigch != ';'); + return t; + case 'T': + int sep = sig.indexOf(';', sigp); + var ty = Signature.TypeVarSig.of(sig.substring(sigp, sep)); + sigp = sep + 1; + return ty; + case '[': return ArrayTypeSig.of(typeSig()); + } + throw new IllegalStateException("not a valid type signature: " + sig); + } + + private TypeArg typeArg() { + char c = sig.charAt(sigp++); + switch (c) { + case '*': return TypeArg.unbounded(); + case '+': return TypeArg.extendsOf(referenceTypeSig()); + case '-': return TypeArg.superOf(referenceTypeSig()); + default: + sigp--; + return TypeArg.of(referenceTypeSig()); + } + } + + public static record BaseTypeSigImpl(char baseType) implements Signature.BaseTypeSig { + + @Override + public String signatureString() { + return "" + baseType; + } + } + + public static record TypeVarSigImpl(String identifier) implements Signature.TypeVarSig { + + @Override + public String signatureString() { + return "T" + identifier + ';'; + } + } + + public static record ArrayTypeSigImpl(int arrayDepth, Signature elemType) implements Signature.ArrayTypeSig { + + @Override + public Signature componentSignature() { + return arrayDepth > 1 ? new ArrayTypeSigImpl(arrayDepth - 1, elemType) : elemType; + } + + @Override + public String signatureString() { + return "[".repeat(arrayDepth) + elemType.signatureString(); + } + } + + public static record ClassTypeSigImpl(Optional outerType, String className, List typeArgs) + implements Signature.ClassTypeSig { + + @Override + public String signatureString() { + String prefix = "L"; + if (outerType.isPresent()) { + prefix = outerType.get().signatureString(); + assert prefix.charAt(prefix.length() - 1) == ';'; + prefix = prefix.substring(0, prefix.length() - 1) + '.'; + } + String suffix = ";"; + if (!typeArgs.isEmpty()) { + var sb = new StringBuilder(); + sb.append('<'); + for (var ta : typeArgs) + sb.append(((TypeArgImpl)ta).signatureString()); + suffix = sb.append(">;").toString(); + } + return prefix + className + suffix; + } + } + + public static record TypeArgImpl(WildcardIndicator wildcardIndicator, Optional boundType) implements Signature.TypeArg { + + public String signatureString() { + return switch (wildcardIndicator) { + case DEFAULT -> boundType.get().signatureString(); + case EXTENDS -> "+" + boundType.get().signatureString(); + case SUPER -> "-" + boundType.get().signatureString(); + case UNBOUNDED -> "*"; + }; + } + } + + public static record TypeParamImpl(String identifier, Optional classBound, List interfaceBounds) + implements TypeParam { + } + + private static StringBuilder printTypeParameters(List typeParameters) { + var sb = new StringBuilder(); + if (typeParameters != null && !typeParameters.isEmpty()) { + sb.append('<'); + for (var tp : typeParameters) { + sb.append(tp.identifier()).append(':'); + if (tp.classBound().isPresent()) + sb.append(tp.classBound().get().signatureString()); + if (tp.interfaceBounds() != null) for (var is : tp.interfaceBounds()) + sb.append(':').append(is.signatureString()); + } + sb.append('>'); + } + return sb; + } + + public static record ClassSignatureImpl(List typeParameters, RefTypeSig superclassSignature, + List superinterfaceSignatures) implements ClassSignature { + + @Override + public String signatureString() { + var sb = printTypeParameters(typeParameters); + sb.append(superclassSignature.signatureString()); + if (superinterfaceSignatures != null) for (var in : superinterfaceSignatures) + sb.append(in.signatureString()); + return sb.toString(); + } + } + + public static record MethodSignatureImpl( + List typeParameters, + List throwableSignatures, + Signature result, + List arguments) implements MethodSignature { + + @Override + public String signatureString() { + var sb = printTypeParameters(typeParameters); + sb.append('('); + for (var a : arguments) + sb.append(a.signatureString()); + sb.append(')').append(result.signatureString()); + if (!throwableSignatures.isEmpty()) + for (var t : throwableSignatures) + sb.append('^').append(t.signatureString()); + return sb.toString(); + } + } + + private static List null2Empty(ArrayList l) { + return l == null ? List.of() : Collections.unmodifiableList(l); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java new file mode 100644 index 0000000000000..ec517a95a1829 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.ClassReader; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.attribute.BootstrapMethodsAttribute; +import jdk.internal.classfile.constantpool.DoubleEntry; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.FloatEntry; +import jdk.internal.classfile.constantpool.IntegerEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.LongEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.MethodRefEntry; +import jdk.internal.classfile.constantpool.MethodTypeEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.StringEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; + +import static jdk.internal.classfile.Classfile.TAG_CLASS; +import static jdk.internal.classfile.Classfile.TAG_CONSTANTDYNAMIC; +import static jdk.internal.classfile.Classfile.TAG_DOUBLE; +import static jdk.internal.classfile.Classfile.TAG_FIELDREF; +import static jdk.internal.classfile.Classfile.TAG_FLOAT; +import static jdk.internal.classfile.Classfile.TAG_INTEGER; +import static jdk.internal.classfile.Classfile.TAG_INTERFACEMETHODREF; +import static jdk.internal.classfile.Classfile.TAG_INVOKEDYNAMIC; +import static jdk.internal.classfile.Classfile.TAG_LONG; +import static jdk.internal.classfile.Classfile.TAG_METHODHANDLE; +import static jdk.internal.classfile.Classfile.TAG_METHODREF; +import static jdk.internal.classfile.Classfile.TAG_METHODTYPE; +import static jdk.internal.classfile.Classfile.TAG_MODULE; +import static jdk.internal.classfile.Classfile.TAG_NAMEANDTYPE; +import static jdk.internal.classfile.Classfile.TAG_PACKAGE; +import static jdk.internal.classfile.Classfile.TAG_STRING; + +public final class SplitConstantPool implements ConstantPoolBuilder { + + private final ClassReaderImpl parent; + private final int parentSize, parentBsmSize; + final Options options; + + private int size, bsmSize; + private PoolEntry[] myEntries; + private BootstrapMethodEntryImpl[] myBsmEntries; + private boolean doneFullScan; + private EntryMap map; + private EntryMap bsmMap; + + public SplitConstantPool() { + this(new Options(Collections.emptyList())); + } + + public SplitConstantPool(Options options) { + this.size = 1; + this.bsmSize = 0; + this.myEntries = new PoolEntry[1024]; + this.myBsmEntries = new BootstrapMethodEntryImpl[8]; + this.parent = null; + this.parentSize = 0; + this.parentBsmSize = 0; + this.options = options; + } + + public SplitConstantPool(ClassReader parent) { + this.options = ((ClassReaderImpl) parent).options; + this.parent = (ClassReaderImpl) parent; + this.parentSize = parent.entryCount(); + this.parentBsmSize = parent.bootstrapMethodCount(); + this.size = parentSize; + this.bsmSize = parentBsmSize; + this.myEntries = new PoolEntry[8]; + this.myBsmEntries = new BootstrapMethodEntryImpl[8]; + } + + @Override + public int entryCount() { + return size; + } + + @Override + public int bootstrapMethodCount() { + return bsmSize; + } + + @Override + public PoolEntry entryByIndex(int index) { + return (index < parentSize) + ? parent.entryByIndex(index) + : myEntries[index - parentSize]; + } + + @Override + public BootstrapMethodEntryImpl bootstrapMethodEntry(int index) { + return (index < parentBsmSize) + ? parent.bootstrapMethodEntry(index) + : myBsmEntries[index - parentBsmSize]; + } + + public Options options() { + return options; + } + + @Override + public boolean canWriteDirect(ConstantPool other) { + return this == other || parent == other; + } + + @Override + public boolean writeBootstrapMethods(BufWriter buf) { + if (bsmSize == 0) + return false; + int pos = buf.size(); + if (parent != null && parentBsmSize != 0) { + parent.writeBootstrapMethods(buf); + for (int i = parentBsmSize; i < bsmSize; i++) + bootstrapMethodEntry(i).writeTo(buf); + int attrLen = buf.size() - pos; + buf.patchInt(pos + 2, 4, attrLen - 6); + buf.patchInt(pos + 6, 2, bsmSize); + } + else { + Attribute a + = new UnboundAttribute.AdHocAttribute<>(Attributes.BOOTSTRAP_METHODS) { + + @Override + public void writeBody(BufWriter b) { + buf.writeU2(bsmSize); + for (int i = 0; i < bsmSize; i++) + bootstrapMethodEntry(i).writeTo(buf); + } + }; + a.writeTo(buf); + } + return true; + } + + @Override + public void writeTo(BufWriter buf) { + int writeFrom = 1; + buf.writeU2(entryCount()); + if (parent != null && buf.constantPool().canWriteDirect(this)) { + parent.writeConstantPoolEntries(buf); + writeFrom = parent.entryCount(); + } + for (int i = writeFrom; i < entryCount(); ) { + PoolEntry info = entryByIndex(i); + info.writeTo(buf); + i += info.width(); + } + } + + private EntryMap map() { + if (map == null) { + map = new EntryMap<>(Math.max(size, 1024), .75f) { + @Override + protected PoolEntry fetchElement(int index) { + return entryByIndex(index); + } + }; + // Doing a full scan here yields fall-off-the-cliff performance results, + // especially if we only need a few entries that are already + // inflated (such as attribute names). + // So we inflate the map with whatever we've got from the parent, and + // later, if we miss, we do a one-time full inflation before creating + // a new entry. + for (int i=1; i bsmMap() { + if (bsmMap == null) { + bsmMap = new EntryMap<>(Math.max(bsmSize, 16), .75f) { + @Override + protected BootstrapMethodEntryImpl fetchElement(int index) { + return bootstrapMethodEntry(index); + } + }; + for (int i=0; i E internalAdd(E cpi) { + return internalAdd(cpi, cpi.hashCode()); + } + + private E internalAdd(E cpi, int hash) { + int newIndex = size-parentSize; + if (newIndex + 2 > myEntries.length) { + myEntries = Arrays.copyOf(myEntries, 2 * newIndex, PoolEntry[].class); + } + myEntries[newIndex] = cpi; + size += cpi.width(); + map().put(hash, cpi.index()); + return cpi; + } + + private BootstrapMethodEntryImpl internalAdd(BootstrapMethodEntryImpl bsm, int hash) { + int newIndex = bsmSize-parentBsmSize; + if (newIndex + 2 > myBsmEntries.length) { + myBsmEntries = Arrays.copyOf(myBsmEntries, 2 * newIndex, BootstrapMethodEntryImpl[].class); + } + myBsmEntries[newIndex] = bsm; + bsmSize += 1; + bsmMap().put(hash, bsm.index); + return bsm; + } + + private PoolEntry findPrimitiveEntry(int tag, T val) { + int hash = AbstractPoolEntry.hash1(tag, val.hashCode()); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == tag + && e instanceof AbstractPoolEntry.PrimitiveEntry ce + && ce.value().equals(val)) + return e; + } + if (!doneFullScan) { + fullScan(); + return findPrimitiveEntry(tag, val); + } + return null; + } + + private AbstractPoolEntry findEntry(int tag, T ref1) { + // invariant: canWriteDirect(ref1.constantPool()) + int hash = AbstractPoolEntry.hash1(tag, ref1.index()); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == tag + && e instanceof AbstractPoolEntry.AbstractRefEntry re + && re.ref1 == ref1) + return re; + } + if (!doneFullScan) { + fullScan(); + return findEntry(tag, ref1); + } + return null; + } + + private + AbstractPoolEntry findEntry(int tag, T ref1, U ref2) { + // invariant: canWriteDirect(ref1.constantPool()), canWriteDirect(ref2.constantPool()) + int hash = AbstractPoolEntry.hash2(tag, ref1.index(), ref2.index()); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == tag + && e instanceof AbstractPoolEntry.AbstractRefsEntry re + && re.ref1 == ref1 + && re.ref2 == ref2) { + return re; + } + } + if (!doneFullScan) { + fullScan(); + return findEntry(tag, ref1, ref2); + } + return null; + } + + private AbstractPoolEntry.Utf8EntryImpl tryFindUtf8(int hash, String target) { + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; + token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == Classfile.TAG_UTF8 + && e instanceof AbstractPoolEntry.Utf8EntryImpl ce + && ce.hashCode() == hash + && target.equals(ce.stringValue())) + return ce; + } + if (!doneFullScan) { + fullScan(); + return tryFindUtf8(hash, target); + } + return null; + } + + private AbstractPoolEntry.Utf8EntryImpl tryFindUtf8(int hash, AbstractPoolEntry.Utf8EntryImpl target) { + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == Classfile.TAG_UTF8 + && e instanceof AbstractPoolEntry.Utf8EntryImpl ce + && target.equalsUtf8(ce)) + return ce; + } + if (!doneFullScan) { + fullScan(); + return tryFindUtf8(hash, target); + } + return null; + } + + @Override + public AbstractPoolEntry.Utf8EntryImpl utf8Entry(String s) { + var ce = tryFindUtf8(AbstractPoolEntry.hashString(s.hashCode()), s); + return ce == null ? internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, s)) : ce; + } + + AbstractPoolEntry.Utf8EntryImpl maybeCloneUtf8Entry(Utf8Entry entry) { + AbstractPoolEntry.Utf8EntryImpl e = (AbstractPoolEntry.Utf8EntryImpl) entry; + if (e.constantPool == this || e.constantPool == parent) + return e; + AbstractPoolEntry.Utf8EntryImpl ce = tryFindUtf8(e.hashCode(), e); + return ce == null ? internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, e)) : ce; + } + + @Override + public AbstractPoolEntry.ClassEntryImpl classEntry(Utf8Entry nameEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + var e = (AbstractPoolEntry.ClassEntryImpl) findEntry(TAG_CLASS, ne); + return e == null ? internalAdd(new AbstractPoolEntry.ClassEntryImpl(this, size, ne)) : e; + } + + @Override + public PackageEntry packageEntry(Utf8Entry nameEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + var e = (AbstractPoolEntry.PackageEntryImpl) findEntry(TAG_PACKAGE, ne); + return e == null ? internalAdd(new AbstractPoolEntry.PackageEntryImpl(this, size, ne)) : e; + } + + @Override + public ModuleEntry moduleEntry(Utf8Entry nameEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + var e = (AbstractPoolEntry.ModuleEntryImpl) findEntry(TAG_MODULE, ne); + return e == null ? internalAdd(new AbstractPoolEntry.ModuleEntryImpl(this, size, ne)) : e; + } + + @Override + public AbstractPoolEntry.NameAndTypeEntryImpl nameAndTypeEntry(Utf8Entry nameEntry, Utf8Entry typeEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + AbstractPoolEntry.Utf8EntryImpl te = maybeCloneUtf8Entry(typeEntry); + var e = (AbstractPoolEntry.NameAndTypeEntryImpl) findEntry(TAG_NAMEANDTYPE, ne, te); + return e == null ? internalAdd(new AbstractPoolEntry.NameAndTypeEntryImpl(this, size, ne, te)) : e; + } + + @Override + public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner; + AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + var e = (AbstractPoolEntry.FieldRefEntryImpl) findEntry(TAG_FIELDREF, oe, ne); + return e == null ? internalAdd(new AbstractPoolEntry.FieldRefEntryImpl(this, size, oe, ne)) : e; + } + + @Override + public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner; + AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + var e = (AbstractPoolEntry.MethodRefEntryImpl) findEntry(TAG_METHODREF, oe, ne); + return e == null ? internalAdd(new AbstractPoolEntry.MethodRefEntryImpl(this, size, oe, ne)) : e; + } + + @Override + public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner; + AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + var e = (AbstractPoolEntry.InterfaceMethodRefEntryImpl) findEntry(TAG_INTERFACEMETHODREF, oe, ne); + return e == null ? internalAdd(new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, size, oe, ne)) : e; + } + + @Override + public MethodTypeEntry methodTypeEntry(MethodTypeDesc descriptor) { + return methodTypeEntry(utf8Entry(descriptor.descriptorString())); + } + + @Override + public MethodTypeEntry methodTypeEntry(Utf8Entry descriptor) { + AbstractPoolEntry.Utf8EntryImpl de = maybeCloneUtf8Entry(descriptor); + var e = (AbstractPoolEntry.MethodTypeEntryImpl) findEntry(TAG_METHODTYPE, de); + return e == null ? internalAdd(new AbstractPoolEntry.MethodTypeEntryImpl(this, size, de)) : e; + } + + @Override + public MethodHandleEntry methodHandleEntry(int refKind, MemberRefEntry reference) { + if (!canWriteDirect(reference.constantPool())) { + reference = switch (reference.tag()) { + case TAG_FIELDREF -> fieldRefEntry(reference.owner(), reference.nameAndType()); + case TAG_METHODREF -> methodRefEntry(reference.owner(), reference.nameAndType()); + case TAG_INTERFACEMETHODREF -> interfaceMethodRefEntry(reference.owner(), reference.nameAndType()); + default -> throw new IllegalStateException(String.format("Bad tag %d", reference.tag())); + }; + } + + int hash = AbstractPoolEntry.hash2(TAG_METHODHANDLE, refKind, reference.index()); + EntryMap map1 = map(); + for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) { + PoolEntry e = map1.getElementByToken(token); + if (e.tag() == TAG_METHODHANDLE + && e instanceof AbstractPoolEntry.MethodHandleEntryImpl ce + && ce.kind() == refKind && ce.reference() == reference) + return ce; + } + if (!doneFullScan) { + fullScan(); + return methodHandleEntry(refKind, reference); + } + return internalAdd(new AbstractPoolEntry.MethodHandleEntryImpl(this, size, + hash, refKind, (AbstractPoolEntry.AbstractMemberRefEntry) reference), hash); + } + + @Override + public InvokeDynamicEntry invokeDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, + NameAndTypeEntry nameAndType) { + if (!canWriteDirect(bootstrapMethodEntry.constantPool())) + bootstrapMethodEntry = bsmEntry(bootstrapMethodEntry.bootstrapMethod(), + bootstrapMethodEntry.arguments()); + if (!canWriteDirect(nameAndType.constantPool())) + nameAndType = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + int hash = AbstractPoolEntry.hash2(TAG_INVOKEDYNAMIC, + bootstrapMethodEntry.bsmIndex(), nameAndType.index()); + EntryMap map1 = map(); + for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) { + PoolEntry e = map1.getElementByToken(token); + if (e.tag() == TAG_INVOKEDYNAMIC + && e instanceof AbstractPoolEntry.InvokeDynamicEntryImpl ce + && ce.bootstrap() == bootstrapMethodEntry && ce.nameAndType() == nameAndType) + return ce; + } + if (!doneFullScan) { + fullScan(); + return invokeDynamicEntry(bootstrapMethodEntry, nameAndType); + } + + AbstractPoolEntry.InvokeDynamicEntryImpl ce = + new AbstractPoolEntry.InvokeDynamicEntryImpl(this, size, hash, + (BootstrapMethodEntryImpl) bootstrapMethodEntry, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + internalAdd(ce, hash); + return ce; + } + + @Override + public ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, + NameAndTypeEntry nameAndType) { + if (!canWriteDirect(bootstrapMethodEntry.constantPool())) + bootstrapMethodEntry = bsmEntry(bootstrapMethodEntry.bootstrapMethod(), + bootstrapMethodEntry.arguments()); + if (!canWriteDirect(nameAndType.constantPool())) + nameAndType = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + int hash = AbstractPoolEntry.hash2(TAG_CONSTANTDYNAMIC, + bootstrapMethodEntry.bsmIndex(), nameAndType.index()); + EntryMap map1 = map(); + for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) { + PoolEntry e = map1.getElementByToken(token); + if (e.tag() == TAG_CONSTANTDYNAMIC + && e instanceof AbstractPoolEntry.ConstantDynamicEntryImpl ce + && ce.bootstrap() == bootstrapMethodEntry && ce.nameAndType() == nameAndType) + return ce; + } + if (!doneFullScan) { + fullScan(); + return constantDynamicEntry(bootstrapMethodEntry, nameAndType); + } + + AbstractPoolEntry.ConstantDynamicEntryImpl ce = + new AbstractPoolEntry.ConstantDynamicEntryImpl(this, size, hash, + (BootstrapMethodEntryImpl) bootstrapMethodEntry, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + internalAdd(ce, hash); + return ce; + } + + @Override + public IntegerEntry intEntry(int value) { + var e = (IntegerEntry) findPrimitiveEntry(TAG_INTEGER, value); + return e == null ? internalAdd(new AbstractPoolEntry.IntegerEntryImpl(this, size, value)) : e; + } + + @Override + public FloatEntry floatEntry(float value) { + var e = (FloatEntry) findPrimitiveEntry(TAG_FLOAT, value); + return e == null ? internalAdd(new AbstractPoolEntry.FloatEntryImpl(this, size, value)) : e; + } + + @Override + public LongEntry longEntry(long value) { + var e = (LongEntry) findPrimitiveEntry(TAG_LONG, value); + return e == null ? internalAdd(new AbstractPoolEntry.LongEntryImpl(this, size, value)) : e; + } + + @Override + public DoubleEntry doubleEntry(double value) { + var e = (DoubleEntry) findPrimitiveEntry(TAG_DOUBLE, value); + return e == null ? internalAdd(new AbstractPoolEntry.DoubleEntryImpl(this, size, value)) : e; + } + + @Override + public StringEntry stringEntry(Utf8Entry utf8) { + AbstractPoolEntry.Utf8EntryImpl ue = maybeCloneUtf8Entry(utf8); + var e = (AbstractPoolEntry.StringEntryImpl) findEntry(TAG_STRING, ue); + return e == null ? internalAdd(new AbstractPoolEntry.StringEntryImpl(this, size, ue)) : e; + } + + @Override + public BootstrapMethodEntry bsmEntry(MethodHandleEntry methodReference, + List arguments) { + if (!canWriteDirect(methodReference.constantPool())) + methodReference = methodHandleEntry(methodReference.kind(), methodReference.reference()); + for (LoadableConstantEntry a : arguments) { + if (!canWriteDirect(a.constantPool())) { + // copy args list + LoadableConstantEntry[] arr = arguments.toArray(new LoadableConstantEntry[0]); + for (int i = 0; i < arr.length; i++) + arr[i] = AbstractPoolEntry.maybeClone(this, arr[i]); + arguments = List.of(arr); + + break; + } + } + AbstractPoolEntry.MethodHandleEntryImpl mre = (AbstractPoolEntry.MethodHandleEntryImpl) methodReference; + int hash = BootstrapMethodEntryImpl.computeHashCode(mre, arguments); + EntryMap map = bsmMap(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + BootstrapMethodEntryImpl e = map.getElementByToken(token); + if (e.bootstrapMethod() == mre && e.arguments().equals(arguments)) { + return e; + } + } + BootstrapMethodEntryImpl ne = new BootstrapMethodEntryImpl(this, bsmSize, hash, mre, arguments); + return internalAdd(ne, hash); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java new file mode 100644 index 0000000000000..bd4ef3d131de2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.AccessFlag; +import java.util.List; +import java.util.TreeMap; +import jdk.internal.classfile.BufWriter; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.attribute.StackMapFrameInfo; +import jdk.internal.classfile.attribute.StackMapFrameInfo.*; +import jdk.internal.classfile.ClassReader; + +import static jdk.internal.classfile.Classfile.*; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.MethodModel; + +public class StackMapDecoder { + + private static final int + SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, + SAME_EXTENDED = 251; + + private final ClassReader classReader; + private final int pos; + private final LabelContext ctx; + private final List initFrameLocals; + private int p; + + StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List initFrameLocals) { + this.classReader = classReader; + this.pos = pos; + this.ctx = ctx; + this.initFrameLocals = initFrameLocals; + } + + static List initFrameLocals(MethodModel method) { + return initFrameLocals(method.parent().orElseThrow().thisClass(), + method.methodName().stringValue(), + method.methodType().stringValue(), + method.flags().has(AccessFlag.STATIC)); + } + + public static List initFrameLocals(ClassEntry thisClass, String methodName, String methodType, boolean isStatic) { + var mdesc = MethodTypeDesc.ofDescriptor(methodType); + VerificationTypeInfo vtis[]; + int i = 0; + if (!isStatic) { + vtis = new VerificationTypeInfo[mdesc.parameterCount() + 1]; + if ("".equals(methodName) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) { + vtis[i++] = SimpleVerificationTypeInfo.ITEM_UNINITIALIZED_THIS; + } else { + vtis[i++] = new StackMapDecoder.ObjectVerificationTypeInfoImpl(thisClass); + } + } else { + vtis = new VerificationTypeInfo[mdesc.parameterCount()]; + } + for(var arg : mdesc.parameterList()) { + vtis[i++] = switch (arg.descriptorString()) { + case "I", "S", "C" ,"B", "Z" -> SimpleVerificationTypeInfo.ITEM_INTEGER; + case "J" -> SimpleVerificationTypeInfo.ITEM_LONG; + case "F" -> SimpleVerificationTypeInfo.ITEM_FLOAT; + case "D" -> SimpleVerificationTypeInfo.ITEM_DOUBLE; + case "V" -> throw new IllegalArgumentException("Illegal method argument type: " + arg); + default -> new StackMapDecoder.ObjectVerificationTypeInfoImpl(TemporaryConstantPool.INSTANCE.classEntry(arg)); + }; + } + return List.of(vtis); + } + + public static void writeFrames(BufWriter b, List entries) { + var buf = (BufWriterImpl)b; + var dcb = (DirectCodeBuilder)buf.labelContext(); + var mi = dcb.methodInfo(); + var prevLocals = StackMapDecoder.initFrameLocals(buf.thisClass(), + mi.methodName().stringValue(), + mi.methodType().stringValue(), + (mi.methodFlags() & ACC_STATIC) != 0); + int prevOffset = -1; + var map = new TreeMap(); + //sort by resolved label offsets first to allow unordered entries + for (var fr : entries) { + map.put(dcb.labelToBci(fr.target()), fr); + } + b.writeU2(map.size()); + for (var me : map.entrySet()) { + int offset = me.getKey(); + var fr = me.getValue(); + writeFrame(buf, offset - prevOffset - 1, prevLocals, fr); + prevOffset = offset; + prevLocals = fr.locals(); + } + } + + private static void writeFrame(BufWriterImpl out, int offsetDelta, List prevLocals, StackMapFrameInfo fr) { + if (offsetDelta < 0) throw new IllegalArgumentException("Invalid stack map frames order"); + if (fr.stack().isEmpty()) { + int commonLocalsSize = Math.min(prevLocals.size(), fr.locals().size()); + int diffLocalsSize = fr.locals().size() - prevLocals.size(); + if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(fr.locals(), prevLocals, commonLocalsSize)) { + if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame + out.writeU1(offsetDelta); + } else { //chop, same extended or append frame + out.writeU1(251 + diffLocalsSize); + out.writeU2(offsetDelta); + for (int i=commonLocalsSize; i l1, List l2, int compareSize) { + for (int i = 0; i < compareSize; i++) { + if (!l1.get(i).equals(l2.get(i))) return false; + } + return true; + } + + private static void writeTypeInfo(BufWriterImpl bw, VerificationTypeInfo vti) { + bw.writeU1(vti.tag()); + switch (vti) { + case SimpleVerificationTypeInfo svti -> + {} + case ObjectVerificationTypeInfo ovti -> + bw.writeIndex(ovti.className()); + case UninitializedVerificationTypeInfo uvti -> + bw.writeU2(bw.labelContext().labelToBci(uvti.newTarget())); + } + } + + List entries() { + p = pos; + List locals = initFrameLocals, stack = List.of(); + int bci = -1; + var entries = new StackMapFrameInfo[u2()]; + for (int ei = 0; ei < entries.length; ei++) { + int frameType = classReader.readU1(p++); + if (frameType < 64) { + bci += frameType + 1; + stack = List.of(); + } else if (frameType < 128) { + bci += frameType - 63; + stack = List.of(readVerificationTypeInfo()); + } else { + if (frameType < SAME_LOCALS_1_STACK_ITEM_EXTENDED) + throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType); + bci += u2() + 1; + if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + stack = List.of(readVerificationTypeInfo()); + } else if (frameType < SAME_EXTENDED) { + locals = locals.subList(0, locals.size() + frameType - SAME_EXTENDED); + stack = List.of(); + } else if (frameType == SAME_EXTENDED) { + stack = List.of(); + } else if (frameType < SAME_EXTENDED + 4) { + int actSize = locals.size(); + var newLocals = locals.toArray(new VerificationTypeInfo[actSize + frameType - SAME_EXTENDED]); + for (int i = actSize; i < newLocals.length; i++) + newLocals[i] = readVerificationTypeInfo(); + locals = List.of(newLocals); + stack = List.of(); + } else { + var newLocals = new VerificationTypeInfo[u2()]; + for (int i=0; i SimpleVerificationTypeInfo.ITEM_TOP; + case VT_INTEGER -> SimpleVerificationTypeInfo.ITEM_INTEGER; + case VT_FLOAT -> SimpleVerificationTypeInfo.ITEM_FLOAT; + case VT_DOUBLE -> SimpleVerificationTypeInfo.ITEM_DOUBLE; + case VT_LONG -> SimpleVerificationTypeInfo.ITEM_LONG; + case VT_NULL -> SimpleVerificationTypeInfo.ITEM_NULL; + case VT_UNINITIALIZED_THIS -> SimpleVerificationTypeInfo.ITEM_UNINITIALIZED_THIS; + case VT_OBJECT -> new ObjectVerificationTypeInfoImpl((ClassEntry)classReader.entryByIndex(u2())); + case VT_UNINITIALIZED -> new UninitializedVerificationTypeInfoImpl(ctx.getLabel(u2())); + default -> throw new IllegalArgumentException("Invalid verification type tag: " + tag); + }; + } + + public static record ObjectVerificationTypeInfoImpl( + ClassEntry className) implements ObjectVerificationTypeInfo { + + @Override + public int tag() { return VT_OBJECT; } + + @Override + public String toString() { + return className.asInternalName(); + } + } + + public static record UninitializedVerificationTypeInfoImpl(Label newTarget) implements UninitializedVerificationTypeInfo { + + @Override + public int tag() { return VT_UNINITIALIZED; } + + @Override + public String toString() { + return "UNINIT(" + newTarget +")"; + } + } + + private int u2() { + int v = classReader.readU2(p); + p += 2; + return v; + } + + public static record StackMapFrameImpl(int frameType, + Label target, + List locals, + List stack) + implements StackMapFrameInfo { + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java new file mode 100644 index 0000000000000..9466ef069e555 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java @@ -0,0 +1,1424 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import jdk.internal.classfile.Attribute; + +import static jdk.internal.classfile.Classfile.*; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.attribute.StackMapTableAttribute; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.components.ClassPrinter; +import jdk.internal.classfile.attribute.CodeAttribute; + +/** + * StackMapGenerator is responsible for stack map frames generation. + *

+ * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process. + *

+ * The {@linkplain #generate() frames computation} consists of following steps: + *

    + *
  1. {@linkplain #detectFrameOffsets() Detection} of mandatory stack map frames offsets:
      + *
    • Mandatory stack map frame offsets include all jump and switch instructions targets, + * offsets immediately following {@linkplain #noControlFlow(int) "no control flow"} + * and all exception table handlers. + *
    • Detection is performed in a single fast pass through the bytecode, + * with no auxiliary structures construction nor further instructions processing. + *
    + *
  2. Generator loop {@linkplain #processMethod() processing bytecode instructions}:
      + *
    • Generator loop simulates sequence instructions {@linkplain #processBlock(RawBytecodeHelper) processing effect on the actual stack and locals}. + *
    • All mandatory {@linkplain Frame frames} detected in the step #1 are {@linkplain Frame#checkAssignableTo(Frame) retro-filled} + * (or {@linkplain Frame#merge(Type, Type[], int, Frame) reverse-merged} in subsequent processing) + * with the actual stack and locals for all matching jump, switch and exception handler targets. + *
    • All frames modified by reverse merges are marked as {@linkplain Frame#dirty dirty} for further processing. + *
    • Code blocks with not yet known entry frame content are skipped and related frames are also marked as dirty. + *
    • Generator loop process is repeated until all mandatory frames are cleared or until an error state is reached. + *
    • Generator loop always passes all instructions at least once to calculate {@linkplain #maxStack max stack} + * and {@linkplain #maxLocals max locals} code attributes. + *
    • More than one pass is usually not necessary, except for more complex bytecode sequences.
      + * (Note: experimental measurements showed that more than 99% of the cases required only single pass to clear all frames, + * less than 1% of the cases required second pass and remaining 0,01% of the cases required third pass to clear all frames.). + *
    + *
  3. Dead code patching to pass class loading verification:
      + *
    • Dead code blocks are indicated by frames remaining without content after leaving the Generator loop. + *
    • Each dead code block is filled with NOP instructions, terminated with + * ATHROW instruction, and removed from exception handlers table. + *
    • Dead code block entry frame is set to java.lang.Throwable single stack item and no locals. + *
    + *
+ *

+ * {@linkplain Frame#merge(Type, Type[], int, Frame) Reverse-merge} of the stack map frames + * may in some situations require to determine {@linkplain ClassHierarchyImpl class hierarchy} relations. + *

+ * Reverse-merge of individual {@linkplain Type types} is performed when a target frame has already been retro-filled + * and it is necessary to adjust its existing stack entries and locals to also match actual stack map frame conditions. + * Following tables describe how new target stack entry or local type is calculated, based on the actual frame stack entry or local ("from") + * and actual value of the target stack entry or local ("to"). + * + * + * + *
Reverse-merge of general type categories
to \ fromTOPPRIMITIVEUNINITIALIZEDREFERENCE + *
TOPTOPTOPTOPTOP + *
PRIMITIVETOPReverse-merge of primitive typesTOPTOP + *
UNINITIALIZEDTOPTOPIs NEW offset matching ? UNINITIALIZED : TOPTOP + *
REFERENCETOPTOPTOPReverse-merge of reference types + *
+ *

+ * + * + *
Reverse-merge of primitive types
to \ fromSHORTBYTEBOOLEANLONGDOUBLEFLOATINTEGER + *
SHORTSHORTTOPTOPTOPTOPTOPSHORT + *
BYTETOPBYTETOPTOPTOPTOPBYTE + *
BOOLEANTOPTOPBOOLEANTOPTOPTOPBOOLEAN + *
LONGTOPTOPTOPLONGTOPTOPTOP + *
DOUBLETOPTOPTOPTOPDOUBLETOPTOP + *
FLOATTOPTOPTOPTOPTOPFLOATTOP + *
INTEGERTOPTOPTOPTOPTOPTOPINTEGER + *
+ *

+ * + * + *
Reverse merge of reference types
to \ fromNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACE*OBJECT** + *
NULLNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACEOBJECT + *
j.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object + *
j.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Objectj.l.Cloneablej.l.Cloneable + *
j.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.l.Objectj.i.Serializablej.i.Serializable + *
ARRAYARRAYj.l.Objectj.l.Objectj.l.ObjectReverse merge of arraysj.l.Objectj.l.Object + *
INTERFACE*INTERFACEj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object + *
OBJECT**OBJECTj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.ObjectResolved common ancestor + *
*any interface reference except for j.l.Cloneable and j.i.Serializable
**any object reference except for j.l.Object + *
+ *

+ * Array types are reverse-merged as reference to array type constructed from reverse-merged components. + * Reference to j.l.Object is an alternate result when construction of the array type is not possible (when reverse-merge of components returned TOP or other non-reference and non-primitive type). + *

+ * Custom class hierarchy resolver has been implemented as a part of the library to avoid heavy class loading + * and to allow stack maps generation even for code with incomplete dependency classpath. + * However stack maps generated with {@linkplain ClassHierarchyImpl#resolve(java.lang.constant.ClassDesc) warnings of unresolved dependencies} may later fail to verify during class loading process. + *

+ * Focus of the whole algorithm is on high performance and low memory footprint:

    + *
  • It does not produce, collect nor visit any complex intermediate structures + * (beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}). + *
  • It works with only minimal mandatory stack map frames. + *
  • It does not spend time on any non-essential verifications. + *
+ *

+ * In case of an exception during the Generator loop there is just minimal information available in the exception message. + *

+ * To determine root cause of the exception it is recommended to enable debug logging of the Generator in one of the two modes + * using following java.lang.System properties:

+ *
-Djdk.internal.classfile.impl.StackMapGenerator.DEBUG=true + *
Activates debug logging with basic information + generated stack map frames in case of success. + * It also re-runs with enabled full trace logging in case of an error or exception. + *
-Djdk.internal.classfile.impl.StackMapGenerator.TRACE=true + *
Activates full detailed tracing of the generator process for all invocations. + *
+ */ + +public final class StackMapGenerator { + + private static final String OBJECT_INITIALIZER_NAME = ""; + private static final int FLAG_THIS_UNINIT = 0x01; + private static final int FRAME_DEFAULT_CAPACITY = 10; + private static final int T_BOOLEAN = 4, T_LONG = 11; + + private static final int ITEM_TOP = 0, + ITEM_INTEGER = 1, + ITEM_FLOAT = 2, + ITEM_DOUBLE = 3, + ITEM_LONG = 4, + ITEM_NULL = 5, + ITEM_UNINITIALIZED_THIS = 6, + ITEM_OBJECT = 7, + ITEM_UNINITIALIZED = 8, + ITEM_BOOLEAN = 9, + ITEM_BYTE = 10, + ITEM_SHORT = 11, + ITEM_CHAR = 12, + ITEM_LONG_2ND = 13, + ITEM_DOUBLE_2ND = 14; + + private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null, + Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE, + Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE}; + + private final Type thisType; + private final String methodName; + private final MethodTypeDesc methodDesc; + private final ByteBuffer bytecode; + private final SplitConstantPool cp; + private final boolean isStatic; + private final LabelContext labelContext; + private final List exceptionTable; + private final ClassHierarchyImpl classHierarchy; + private final boolean patchDeadCode; + private List frames; + private final Frame currentFrame; + private int maxStack, maxLocals; + + /** + * Primary constructor of the Generator class. + * New Generator instance must be created for each individual class/method. + * Instance contains only immutable results, all the calculations are processed during instance construction. + * + * @param labelContext LableContext instance used to resolve or patch ExceptionHandler + * labels to bytecode offsets (or vice versa) + * @param thisClass class to generate stack maps for + * @param methodName method name to generate stack maps for + * @param methodDesc method descriptor to generate stack maps for + * @param isStatic information whether the method is static + * @param bytecode R/W ByteBuffer wrapping method bytecode, the content is altered in case Generator detects and patches dead code + * @param cp R/W ConstantPoolBuilder instance used to resolve all involved CP entries and also generate new entries referenced from the generted stack maps + * @param handlers R/W ExceptionHandler list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers + * and also to be altered when dead code is detected and must be excluded from exception handlers + */ + public StackMapGenerator(LabelContext labelContext, + ClassDesc thisClass, + String methodName, + MethodTypeDesc methodDesc, + boolean isStatic, + ByteBuffer bytecode, + SplitConstantPool cp, + List handlers) { + this.thisType = Type.referenceType(thisClass); + this.methodName = methodName; + this.methodDesc = methodDesc; + this.isStatic = isStatic; + this.bytecode = bytecode; + this.cp = cp; + this.labelContext = labelContext; + this.exceptionTable = handlers; + this.classHierarchy = new ClassHierarchyImpl(cp.options().classHierarchyResolver); + this.patchDeadCode = cp.options().patchCode; + this.currentFrame = new Frame(classHierarchy); + generate(); + } + + /** + * Calculated maximum number of the locals required + * @return maximum number of the locals required + */ + public int maxLocals() { + return maxLocals; + } + + /** + * Calculated maximum stack size required + * @return maximum stack size required + */ + public int maxStack() { + return maxStack; + } + + private int getFrameIndexFromOffset(int offset) { + int i = 0; + for (; i < frames.size(); i++) { + if (frames.get(i).offset == offset) { + return i; + } + } + return i; + } + + private void checkJumpTarget(Frame frame, int target) { + int index = getFrameIndexFromOffset(target); + frame.checkAssignableTo(frames.get(index)); + } + + private int exMin, exMax; + + private boolean isAnyFrameDirty() { + for (var f : frames) { + if (f.dirty) return true; + } + return false; + } + + private void generate() { + exMin = bytecode.capacity(); + exMax = -1; + for (var exhandler : exceptionTable) { + int start_pc = labelContext.labelToBci(exhandler.tryStart()); + int end_pc = labelContext.labelToBci(exhandler.tryEnd()); + if (start_pc < exMin) exMin = start_pc; + if (end_pc > exMax) exMax = end_pc; + } + BitSet frameOffsets = detectFrameOffsets(); + int framesCount = frameOffsets.cardinality(); + frames = new ArrayList<>(framesCount); + int offset = -1; + for (int i = 0; i < framesCount; i++) { + offset = frameOffsets.nextSetBit(offset + 1); + frames.add(new Frame(offset, classHierarchy)); + } + do { + processMethod(); + } while (isAnyFrameDirty()); + maxLocals = currentFrame.frameMaxLocals; + maxStack = currentFrame.frameMaxStack; + + //dead code patching + for (int i = 0; i < framesCount; i++) { + var frame = frames.get(i); + if (frame.flags == -1) { + if (!patchDeadCode) generatorError("Unable to generate stack map frame for dead code", frame.offset); + //patch frame + frame.pushStack(Type.THROWABLE_TYPE); + if (maxStack < 1) maxStack = 1; + int blockSize = (i < framesCount - 1 ? frames.get(i + 1).offset : bytecode.limit()) - frame.offset; + //patch bytecode + bytecode.position(frame.offset); + for (int n=1; n= handlerEnd || rangeEnd <= handlerStart) { + //out of range + continue; + } + if (rangeStart <= handlerStart) { + if (rangeEnd >= handlerEnd) { + //complete removal + it.remove(); + } else { + //cut from left + Label newStart = labelContext.newLabel(); + labelContext.setLabelTarget(newStart, rangeEnd); + it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType())); + } + } else if (rangeEnd >= handlerEnd) { + //cut from right + Label newEnd = labelContext.newLabel(); + labelContext.setLabelTarget(newEnd, rangeStart); + it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); + } else { + //split + Label newStart = labelContext.newLabel(); + labelContext.setLabelTarget(newStart, rangeEnd); + Label newEnd = labelContext.newLabel(); + labelContext.setLabelTarget(newEnd, rangeStart); + it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); + it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType())); + } + } + } + + /** + * Getter of the generated StackMapTableAttribute or null if stack map is empty + * @return StackMapTableAttribute or null if stack map is empty + */ + public Attribute stackMapTableAttribute() { + return frames.isEmpty() ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.STACK_MAP_TABLE) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(frames.size()); + Frame prevFrame = new Frame(classHierarchy); + prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); + prevFrame.trimAndCompress(); + for (var fr : frames) { + fr.trimAndCompress(); + fr.writeTo(b, prevFrame, cp); + prevFrame = fr; + } + } + }; + } + + private static Type cpIndexToType(int index, ConstantPoolBuilder cp) { + return Type.referenceType(((ClassEntry)cp.entryByIndex(index)).asSymbol()); + } + + private static boolean isDoubleSlot(ClassDesc desc) { + return CD_double.equals(desc) || CD_long.equals(desc); + } + + private void processMethod() { + currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); + currentFrame.stackSize = 0; + currentFrame.flags = 0; + currentFrame.offset = -1; + int stackmapIndex = 0; + RawBytecodeHelper bcs = new RawBytecodeHelper(bytecode); + boolean ncf = false; + while (!bcs.isLastBytecode()) { + bcs.rawNext(); + currentFrame.offset = bcs.bci; + if (stackmapIndex < frames.size()) { + int thisOffset = frames.get(stackmapIndex).offset; + if (ncf && thisOffset > bcs.bci) { + generatorError("Expecting a stack map frame"); + } + if (thisOffset == bcs.bci) { + if (!ncf) { + currentFrame.checkAssignableTo(frames.get(stackmapIndex)); + } + Frame nextFrame = frames.get(stackmapIndex++); + while (!nextFrame.dirty) { //skip unmatched frames + if (stackmapIndex == frames.size()) return; //skip the rest of this round + nextFrame = frames.get(stackmapIndex++); + } + bcs.rawNext(nextFrame.offset); //skip code up-to the next frame + currentFrame.offset = bcs.bci; + currentFrame.copyFrom(nextFrame); + nextFrame.dirty = false; + } else if (thisOffset < bcs.bci) { + throw new ClassFormatError(String.format("Bad stack map offset %d", thisOffset)); + } + } else if (ncf) { + generatorError("Expecting a stack map frame"); + } + ncf = processBlock(bcs); + } + } + + private boolean processBlock(RawBytecodeHelper bcs) { + int opcode = bcs.rawCode; + boolean ncf = false; + boolean this_uninit = false; + boolean verified_exc_handlers = false; + int bci = bcs.bci; + Type type1, type2, type3, type4; + if (RawBytecodeHelper.isStoreIntoLocal(opcode) && bci >= exMin && bci < exMax) { + processExceptionHandlerTargets(bci, this_uninit); + verified_exc_handlers = true; + } + switch (opcode) { + case Classfile.NOP -> {} + case Classfile.RETURN -> { + ncf = true; + } + case Classfile.ACONST_NULL -> + currentFrame.pushStack(Type.NULL_TYPE); + case Classfile.ICONST_M1, Classfile.ICONST_0, Classfile.ICONST_1, Classfile.ICONST_2, Classfile.ICONST_3, Classfile.ICONST_4, Classfile.ICONST_5, Classfile.SIPUSH, Classfile.BIPUSH -> + currentFrame.pushStack(Type.INTEGER_TYPE); + case Classfile.LCONST_0, Classfile.LCONST_1 -> + currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.FCONST_0, Classfile.FCONST_1, Classfile.FCONST_2 -> + currentFrame.pushStack(Type.FLOAT_TYPE); + case Classfile.DCONST_0, Classfile.DCONST_1 -> + currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.LDC -> + processLdc(bcs.getIndexU1()); + case Classfile.LDC_W, Classfile.LDC2_W -> + processLdc(bcs.getIndexU2()); + case Classfile.ILOAD -> + currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.INTEGER_TYPE); + case Classfile.ILOAD_0, Classfile.ILOAD_1, Classfile.ILOAD_2, Classfile.ILOAD_3 -> + currentFrame.checkLocal(opcode - Classfile.ILOAD_0).pushStack(Type.INTEGER_TYPE); + case Classfile.LLOAD -> + currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.LLOAD_0, Classfile.LLOAD_1, Classfile.LLOAD_2, Classfile.LLOAD_3 -> + currentFrame.checkLocal(opcode - Classfile.LLOAD_0 + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.FLOAD -> + currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.FLOAT_TYPE); + case Classfile.FLOAD_0, Classfile.FLOAD_1, Classfile.FLOAD_2, Classfile.FLOAD_3 -> + currentFrame.checkLocal(opcode - Classfile.FLOAD_0).pushStack(Type.FLOAT_TYPE); + case Classfile.DLOAD -> + currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.DLOAD_0, Classfile.DLOAD_1, Classfile.DLOAD_2, Classfile.DLOAD_3 -> + currentFrame.checkLocal(opcode - Classfile.DLOAD_0 + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.ALOAD -> + currentFrame.pushStack(currentFrame.getLocal(bcs.getIndex())); + case Classfile.ALOAD_0, Classfile.ALOAD_1, Classfile.ALOAD_2, Classfile.ALOAD_3 -> + currentFrame.pushStack(currentFrame.getLocal(opcode - Classfile.ALOAD_0)); + case Classfile.IALOAD, Classfile.BALOAD, Classfile.CALOAD, Classfile.SALOAD -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case Classfile.LALOAD -> + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.FALOAD -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case Classfile.DALOAD -> + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.AALOAD -> + currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()) == Type.NULL_TYPE ? Type.NULL_TYPE : type1.getComponent()); + case Classfile.ISTORE -> + currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.INTEGER_TYPE); + case Classfile.ISTORE_0, Classfile.ISTORE_1, Classfile.ISTORE_2, Classfile.ISTORE_3 -> + currentFrame.decStack(1).setLocal(opcode - Classfile.ISTORE_0, Type.INTEGER_TYPE); + case Classfile.LSTORE -> + currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.LSTORE_0, Classfile.LSTORE_1, Classfile.LSTORE_2, Classfile.LSTORE_3 -> + currentFrame.decStack(2).setLocal2(opcode - Classfile.LSTORE_0, Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.FSTORE -> + currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.FLOAT_TYPE); + case Classfile.FSTORE_0, Classfile.FSTORE_1, Classfile.FSTORE_2, Classfile.FSTORE_3 -> + currentFrame.decStack(1).setLocal(opcode - Classfile.FSTORE_0, Type.FLOAT_TYPE); + case Classfile.DSTORE -> + currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.DSTORE_0, Classfile.DSTORE_1, Classfile.DSTORE_2, Classfile.DSTORE_3 -> + currentFrame.decStack(2).setLocal2(opcode - Classfile.DSTORE_0, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.ASTORE -> + currentFrame.setLocal(bcs.getIndex(), currentFrame.popStack()); + case Classfile.ASTORE_0, Classfile.ASTORE_1, Classfile.ASTORE_2, Classfile.ASTORE_3 -> + currentFrame.setLocal(opcode - Classfile.ASTORE_0, currentFrame.popStack()); + case Classfile.LASTORE, Classfile.DASTORE -> + currentFrame.decStack(4); + case Classfile.IASTORE, Classfile.BASTORE, Classfile.CASTORE, Classfile.SASTORE, Classfile.FASTORE, Classfile.AASTORE -> + currentFrame.decStack(3); + case Classfile.POP, Classfile.MONITORENTER, Classfile.MONITOREXIT -> + currentFrame.decStack(1); + case Classfile.POP2 -> + currentFrame.decStack(2); + case Classfile.DUP -> + currentFrame.pushStack(type1 = currentFrame.popStack()).pushStack(type1); + case Classfile.DUP_X1 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + currentFrame.pushStack(type1).pushStack(type2).pushStack(type1); + } + case Classfile.DUP_X2 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + type3 = currentFrame.popStack(); + currentFrame.pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); + } + case Classfile.DUP2 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + currentFrame.pushStack(type2).pushStack(type1).pushStack(type2).pushStack(type1); + } + case Classfile.DUP2_X1 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + type3 = currentFrame.popStack(); + currentFrame.pushStack(type2).pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); + } + case Classfile.DUP2_X2 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + type3 = currentFrame.popStack(); + type4 = currentFrame.popStack(); + currentFrame.pushStack(type2).pushStack(type1).pushStack(type4).pushStack(type3).pushStack(type2).pushStack(type1); + } + case Classfile.SWAP -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + currentFrame.pushStack(type1); + currentFrame.pushStack(type2); + } + case Classfile.IADD, Classfile.ISUB, Classfile.IMUL, Classfile.IDIV, Classfile.IREM, Classfile.ISHL, Classfile.ISHR, Classfile.IUSHR, Classfile.IOR, Classfile.IXOR, Classfile.IAND -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case Classfile.INEG, Classfile.ARRAYLENGTH, Classfile.INSTANCEOF -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case Classfile.LADD, Classfile.LSUB, Classfile.LMUL, Classfile.LDIV, Classfile.LREM, Classfile.LAND, Classfile.LOR, Classfile.LXOR -> + currentFrame.decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.LNEG -> + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.LSHL, Classfile.LSHR, Classfile.LUSHR -> + currentFrame.decStack(3).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.FADD, Classfile.FSUB, Classfile.FMUL, Classfile.FDIV, Classfile.FREM -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case Classfile.FNEG -> + currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); + case Classfile.DADD, Classfile.DSUB, Classfile.DMUL, Classfile.DDIV, Classfile.DREM -> + currentFrame.decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.DNEG -> + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.IINC -> + currentFrame.checkLocal(bcs.getIndex()); + case Classfile.I2L -> + currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.L2I -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case Classfile.I2F -> + currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); + case Classfile.I2D -> + currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.L2F -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case Classfile.L2D -> + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.F2I -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case Classfile.F2L -> + currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.F2D -> + currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.D2L -> + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.D2F -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case Classfile.I2B, Classfile.I2C, Classfile.I2S -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case Classfile.LCMP, Classfile.DCMPL, Classfile.DCMPG -> + currentFrame.decStack(4).pushStack(Type.INTEGER_TYPE); + case Classfile.FCMPL, Classfile.FCMPG, Classfile.D2I -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case Classfile.IF_ICMPEQ, Classfile.IF_ICMPNE, Classfile.IF_ICMPLT, Classfile.IF_ICMPGE, Classfile.IF_ICMPGT, Classfile.IF_ICMPLE, Classfile.IF_ACMPEQ, Classfile.IF_ACMPNE -> + checkJumpTarget(currentFrame.decStack(2), bcs.dest()); + case Classfile.IFEQ, Classfile.IFNE, Classfile.IFLT, Classfile.IFGE, Classfile.IFGT, Classfile.IFLE, Classfile.IFNULL, Classfile.IFNONNULL -> + checkJumpTarget(currentFrame.decStack(1), bcs.dest()); + case Classfile.GOTO -> { + checkJumpTarget(currentFrame, bcs.dest()); + ncf = true; + } + case Classfile.GOTO_W -> { + checkJumpTarget(currentFrame, bcs.destW()); + ncf = true; + } + case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> { + processSwitch(bcs); + ncf = true; + } + case Classfile.LRETURN, Classfile.DRETURN -> { + currentFrame.decStack(2); + ncf = true; + } + case Classfile.IRETURN, Classfile.FRETURN, Classfile.ARETURN, Classfile.ATHROW -> { + currentFrame.decStack(1); + ncf = true; + } + case Classfile.GETSTATIC, Classfile.PUTSTATIC, Classfile.GETFIELD, Classfile.PUTFIELD -> + processFieldInstructions(bcs); + case Classfile.INVOKEVIRTUAL, Classfile.INVOKESPECIAL, Classfile.INVOKESTATIC, Classfile.INVOKEINTERFACE, Classfile.INVOKEDYNAMIC -> + this_uninit = processInvokeInstructions(bcs, (bci >= exMin && bci < exMax), this_uninit); + case Classfile.NEW -> + currentFrame.pushStack(Type.uninitializedType(bci)); + case Classfile.NEWARRAY -> + currentFrame.decStack(1).pushStack(getNewarrayType(bcs.getIndex())); + case Classfile.ANEWARRAY -> + processAnewarray(bcs.getIndexU2()); + case Classfile.CHECKCAST -> + currentFrame.decStack(1).pushStack(cpIndexToType(bcs.getIndexU2(), cp)); + case Classfile.MULTIANEWARRAY -> { + type1 = cpIndexToType(bcs.getIndexU2(), cp); + int dim = bcs.getU1(bcs.bci + 3); + for (int i = 0; i < dim; i++) { + currentFrame.popStack(); + } + currentFrame.pushStack(type1); + } + default -> + generatorError(String.format("Bad instruction: %02x", opcode)); + } + if (!verified_exc_handlers && bci >= exMin && bci < exMax) { + processExceptionHandlerTargets(bci, this_uninit); + } + return ncf; + } + + private void processExceptionHandlerTargets(int bci, boolean this_uninit) { + for (var exhandler : exceptionTable) { + if (bci >= labelContext.labelToBci(exhandler.tryStart()) && bci < labelContext.labelToBci(exhandler.tryEnd())) { + int flags = currentFrame.flags; + if (this_uninit) flags |= FLAG_THIS_UNINIT; + Frame newFrame = currentFrame.frameInExceptionHandler(flags); + var catchType = exhandler.catchType(); + newFrame.pushStack(catchType.isPresent() ? cpIndexToType(catchType.get().index(), cp) : Type.THROWABLE_TYPE); + int handler = labelContext.labelToBci(exhandler.handler()); + if (handler != -1) checkJumpTarget(newFrame, handler); + } + } + } + + private void processLdc(int index) { + switch (cp.entryByIndex(index).tag()) { + case TAG_UTF8 -> + currentFrame.pushStack(Type.OBJECT_TYPE); + case TAG_STRING -> + currentFrame.pushStack(Type.STRING_TYPE); + case TAG_CLASS -> + currentFrame.pushStack(Type.CLASS_TYPE); + case TAG_INTEGER -> + currentFrame.pushStack(Type.INTEGER_TYPE); + case TAG_FLOAT -> + currentFrame.pushStack(Type.FLOAT_TYPE); + case TAG_DOUBLE -> + currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case TAG_LONG -> + currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case TAG_METHODHANDLE -> + currentFrame.pushStack(Type.METHOD_HANDLE_TYPE); + case TAG_METHODTYPE -> + currentFrame.pushStack(Type.METHOD_TYPE); + case TAG_CONSTANTDYNAMIC -> + currentFrame.pushStack(((ConstantDynamicEntry)cp.entryByIndex(index)).asSymbol().constantType()); + default -> + generatorError("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag())); + } + } + + private void processSwitch(RawBytecodeHelper bcs) { + int bci = bcs.bci; + int alignedBci = RawBytecodeHelper.align(bci + 1); + int defaultOfset = bcs.getInt(alignedBci); + int keys, delta; + currentFrame.popStack(); + if (bcs.rawCode == Classfile.TABLESWITCH) { + int low = bcs.getInt(alignedBci + 4); + int high = bcs.getInt(alignedBci + 2 * 4); + if (low > high) { + generatorError("low must be less than or equal to high in tableswitch"); + } + keys = high - low + 1; + if (keys < 0) { + generatorError("too many keys in tableswitch"); + } + delta = 1; + } else { + keys = bcs.getInt(alignedBci + 4); + if (keys < 0) { + generatorError("number of keys in lookupswitch less than 0"); + } + delta = 2; + for (int i = 0; i < (keys - 1); i++) { + int this_key = bcs.getInt(alignedBci + (2 + 2 * i) * 4); + int next_key = bcs.getInt(alignedBci + (2 + 2 * i + 2) * 4); + if (this_key >= next_key) { + generatorError("Bad lookupswitch instruction"); + } + } + } + int target = bci + defaultOfset; + checkJumpTarget(currentFrame, target); + for (int i = 0; i < keys; i++) { + alignedBci = RawBytecodeHelper.align(bcs.bci + 1); + target = bci + bcs.getInt(alignedBci + (3 + i * delta) * 4); + checkJumpTarget(currentFrame, target); + } + } + + private void processFieldInstructions(RawBytecodeHelper bcs) { + var desc = ClassDesc.ofDescriptor(((MemberRefEntry)cp.entryByIndex(bcs.getIndexU2())).nameAndType().type().stringValue()); + switch (bcs.rawCode) { + case Classfile.GETSTATIC -> + currentFrame.pushStack(desc); + case Classfile.PUTSTATIC -> { + currentFrame.popStack(); + if (isDoubleSlot(desc)) currentFrame.popStack(); + } + case Classfile.GETFIELD -> { + currentFrame.popStack(); + currentFrame.pushStack(desc); + } + case Classfile.PUTFIELD -> { + currentFrame.popStack(); + currentFrame.popStack(); + if (isDoubleSlot(desc)) currentFrame.popStack(); + } + default -> throw new AssertionError("Should not reach here"); + } + } + + private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) { + int index = bcs.getIndexU2(); + int opcode = bcs.rawCode; + var cpe = cp.entryByIndex(index); + var nameAndType = opcode == Classfile.INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); + String invokeMethodName = nameAndType.name().stringValue(); + + var mDesc = nameAndType.type().stringValue(); + //faster counting of method descriptor argument slots instead of full parsing + int nargs = 0, pos = 0, descLen = mDesc.length(); + if (descLen < 3 || mDesc.charAt(0) != '(') + throw new IllegalArgumentException("Bad method descriptor: " + mDesc); + char ch; + while (++pos < descLen && (ch = mDesc.charAt(pos)) != ')') { + switch (ch) { + case '[' -> { + nargs++; + while (++pos < descLen && mDesc.charAt(pos) == '['); + if (mDesc.charAt(pos) == 'L') + while (++pos < descLen && mDesc.charAt(pos) != ';'); + } + case 'D', 'J' -> nargs += 2; + case 'B', 'C', 'F', 'I', 'S', 'Z' -> nargs++; + case 'L' -> { + nargs++; + while (++pos < descLen && mDesc.charAt(pos) != ';'); + } + default -> + throw new IllegalArgumentException("Bad method descriptor: " + mDesc); + } + } + if (++pos >= descLen) + throw new IllegalArgumentException("Bad method descriptor: " + mDesc); + + int bci = bcs.bci; + currentFrame.decStack(nargs); + if (opcode != Classfile.INVOKESTATIC && opcode != Classfile.INVOKEDYNAMIC) { + if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { + Type type = currentFrame.popStack(); + if (type == Type.UNITIALIZED_THIS_TYPE) { + if (inTryBlock) { + processExceptionHandlerTargets(bci, true); + } + currentFrame.initializeObject(type, thisType); + thisUninit = true; + } else if (type.tag == ITEM_UNINITIALIZED) { + int new_offset = type.bci; + int new_class_index = bcs.getIndexU2Raw(new_offset + 1); + Type new_class_type = cpIndexToType(new_class_index, cp); + if (inTryBlock) { + processExceptionHandlerTargets(bci, thisUninit); + } + currentFrame.initializeObject(type, new_class_type); + } else { + generatorError("Bad operand type when invoking "); + } + } else { + currentFrame.popStack(); + } + } + currentFrame.pushStack(ClassDesc.ofDescriptor(mDesc.substring(pos))); + return thisUninit; + } + + private Type getNewarrayType(int index) { + if (index < T_BOOLEAN || index > T_LONG) generatorError("Illegal newarray instruction type %d".formatted(index)); + return ARRAY_FROM_BASIC_TYPE[index]; + } + + private void processAnewarray(int index) { + currentFrame.popStack(); + currentFrame.pushStack(cpIndexToType(index, cp).toArray()); + } + + /** + * Throws java.lang.VerifyError with given error message + * @param msg error message + */ + private void generatorError(String msg) { + generatorError(msg, currentFrame.offset); + } + + /** + * Throws java.lang.VerifyError with given error message + * @param msg error message + * @param offset bytecode offset where the error occured + */ + private void generatorError(String msg, int offset) { + var sb = new StringBuilder("%s at bytecode offset %d of method %s(%s)".formatted( + msg, + offset, + methodName, + methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")))); + //try to attach debug info about corrupted bytecode to the message + try { + cp.options.generateStackmaps = false; + var clb = new DirectClassBuilder(cp, cp.classEntry(ClassDesc.of("FakeClass"))); + clb.withMethod(methodName, methodDesc, isStatic ? ACC_STATIC : 0, mb -> + ((DirectMethodBuilder)mb).writeAttribute(new UnboundAttribute.AdHocAttribute(Attributes.CODE) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(-1);//max stack + b.writeU2(-1);//max locals + b.writeInt(bytecode.limit()); + b.writeBytes(bytecode.array(), 0, bytecode.limit()); + b.writeU2(0);//exception handlers + b.writeU2(0);//attributes + } + })); + ClassPrinter.toYaml(Classfile.parse(clb.build()).methods().get(0).code().get(), ClassPrinter.Verbosity.TRACE_ALL, sb::append); + } catch (Error | Exception suppresed) { + //fallback to bytecode hex dump + bytecode.rewind(); + while (bytecode.position() < bytecode.limit()) { + sb.append("%n%04x:".formatted(bytecode.position())); + for (int i = 0; i < 16 && bytecode.position() < bytecode.limit(); i++) { + sb.append(" %02x".formatted(bytecode.get())); + } + } + var err = new VerifyError(sb.toString()); + err.addSuppressed(suppresed); + throw err; + } + throw new IllegalStateException(sb.toString()); + } + + /** + * Performs detection of mandatory stack map frames offsets + * in a single bytecode traversing pass + * @return java.lang.BitSet of detected frames offsets + */ + private BitSet detectFrameOffsets() { + var offsets = new BitSet() { + @Override + public void set(int i) { + if (i < 0 || i >= bytecode.capacity()) throw new IllegalArgumentException(); + super.set(i); + } + }; + RawBytecodeHelper bcs = new RawBytecodeHelper(bytecode); + boolean no_control_flow = false; + int opcode, bci = 0; + while (!bcs.isLastBytecode()) try { + opcode = bcs.rawNext(); + bci = bcs.bci; + if (no_control_flow) { + offsets.set(bci); + } + no_control_flow = switch (opcode) { + case Classfile.GOTO -> { + offsets.set(bcs.dest()); + yield true; + } + case Classfile.GOTO_W -> { + offsets.set(bcs.destW()); + yield true; + } + case Classfile.IF_ICMPEQ, Classfile.IF_ICMPNE, Classfile.IF_ICMPLT, Classfile.IF_ICMPGE, + Classfile.IF_ICMPGT, Classfile.IF_ICMPLE, Classfile.IFEQ, Classfile.IFNE, + Classfile.IFLT, Classfile.IFGE, Classfile.IFGT, Classfile.IFLE, Classfile.IF_ACMPEQ, + Classfile.IF_ACMPNE , Classfile.IFNULL , Classfile.IFNONNULL -> { + offsets.set(bcs.dest()); + yield false; + } + case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> { + int aligned_bci = RawBytecodeHelper.align(bci + 1); + int default_ofset = bcs.getInt(aligned_bci); + int keys, delta; + if (bcs.rawCode == Classfile.TABLESWITCH) { + int low = bcs.getInt(aligned_bci + 4); + int high = bcs.getInt(aligned_bci + 2 * 4); + keys = high - low + 1; + delta = 1; + } else { + keys = bcs.getInt(aligned_bci + 4); + delta = 2; + } + offsets.set(bci + default_ofset); + for (int i = 0; i < keys; i++) { + offsets.set(bci + bcs.getInt(aligned_bci + (3 + i * delta) * 4)); + } + yield true; + } + case Classfile.IRETURN, Classfile.LRETURN, Classfile.FRETURN, Classfile.DRETURN, + Classfile.ARETURN, Classfile.RETURN, Classfile.ATHROW -> true; + default -> false; + }; + } catch (IllegalArgumentException iae) { + generatorError("Detected branch target out of bytecode range", bci); + } + for (var exhandler : exceptionTable) try { + offsets.set(labelContext.labelToBci(exhandler.handler())); + } catch (IllegalArgumentException iae) { + if (!cp.options().filterDeadLabels) + generatorError("Detected exception handler out of bytecode range"); + } + return offsets; + } + + private final class Frame { + + int offset; + int localsSize, stackSize; + int flags; + int frameMaxStack = 0, frameMaxLocals = 0; + boolean dirty = false; + + private final ClassHierarchyImpl classHierarchy; + + private Type[] locals, stack; + + Frame(ClassHierarchyImpl classHierarchy) { + this(-1, 0, 0, 0, null, null, classHierarchy); + } + + Frame(int offset, ClassHierarchyImpl classHierarchy) { + this(offset, -1, 0, 0, null, null, classHierarchy); + } + + Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) { + this.offset = offset; + this.localsSize = locals_size; + this.stackSize = stack_size; + this.flags = flags; + this.locals = locals; + this.stack = stack; + this.classHierarchy = classHierarchy; + } + + @Override + public String toString() { + return (dirty ? "frame* @" : "frame @") + offset + " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) + " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)); + } + + Frame pushStack(ClassDesc desc) { + return switch (desc.descriptorString()) { + case "J" -> + pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case "D" -> + pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case "I", "Z", "B", "C", "S" -> + pushStack(Type.INTEGER_TYPE); + case "F" -> + pushStack(Type.FLOAT_TYPE); + case "V" -> + this; + default -> + pushStack(Type.referenceType(desc)); + }; + } + + Frame pushStack(Type type) { + checkStack(stackSize); + stack[stackSize++] = type; + return this; + } + + Frame pushStack(Type type1, Type type2) { + checkStack(stackSize + 1); + stack[stackSize++] = type1; + stack[stackSize++] = type2; + return this; + } + + Type popStack() { + if (stackSize < 1) generatorError("Operand stack underflow"); + return stack[--stackSize]; + } + + Frame decStack(int size) { + stackSize -= size; + if (stackSize < 0) generatorError("Operand stack underflow"); + return this; + } + + Frame frameInExceptionHandler(int flags) { + return new Frame(offset, flags, localsSize, 0, locals, new Type[] {Type.TOP_TYPE}, classHierarchy); + } + + void initializeObject(Type old_object, Type new_object) { + int i; + for (i = 0; i < localsSize; i++) { + if (locals[i].equals(old_object)) { + locals[i] = new_object; + } + } + for (i = 0; i < stackSize; i++) { + if (stack[i].equals(old_object)) { + stack[i] = new_object; + } + } + if (old_object == Type.UNITIALIZED_THIS_TYPE) { + flags = 0; + } + } + + Frame checkLocal(int index) { + if (index >= frameMaxLocals) frameMaxLocals = index + 1; + if (locals == null) { + locals = new Type[index + FRAME_DEFAULT_CAPACITY]; + Arrays.fill(locals, Type.TOP_TYPE); + } else if (index >= locals.length) { + int current = locals.length; + locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY); + Arrays.fill(locals, current, locals.length, Type.TOP_TYPE); + } + return this; + } + + private void checkStack(int index) { + if (index >= frameMaxStack) frameMaxStack = index + 1; + if (stack == null) { + stack = new Type[index + FRAME_DEFAULT_CAPACITY]; + Arrays.fill(stack, Type.TOP_TYPE); + } else if (index >= stack.length) { + int current = stack.length; + stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY); + Arrays.fill(stack, current, stack.length, Type.TOP_TYPE); + } + } + + private void setLocalRawInternal(int index, Type type) { + checkLocal(index); + locals[index] = type; + } + + void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { + localsSize = 0; + if (!isStatic) { + localsSize++; + if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) { + setLocal(0, Type.UNITIALIZED_THIS_TYPE); + flags |= FLAG_THIS_UNINIT; + } else { + setLocalRawInternal(0, thisKlass); + } + } + for (int i = 0; i < methodDesc.parameterCount(); i++) { + var desc = methodDesc.parameterType(i); + if (desc.isClassOrInterface() || desc.isArray()) { + setLocalRawInternal(localsSize++, Type.referenceType(desc)); + } else switch (desc.descriptorString()) { + case "J" -> { + setLocalRawInternal(localsSize++, Type.LONG_TYPE); + setLocalRawInternal(localsSize++, Type.LONG2_TYPE); + } + case "D" -> { + setLocalRawInternal(localsSize++, Type.DOUBLE_TYPE); + setLocalRawInternal(localsSize++, Type.DOUBLE2_TYPE); + } + case "I", "Z", "B", "C", "S" -> + setLocalRawInternal(localsSize++, Type.INTEGER_TYPE); + case "F" -> + setLocalRawInternal(localsSize++, Type.FLOAT_TYPE); + default -> throw new AssertionError("Should not reach here"); + } + } + } + + void copyFrom(Frame src) { + if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE); + localsSize = src.localsSize; + checkLocal(src.localsSize - 1); + if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize); + if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE); + stackSize = src.stackSize; + checkStack(src.stackSize - 1); + if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize); + flags = src.flags; + } + + void checkAssignableTo(Frame target) { + if (target.flags == -1) { + target.locals = locals == null ? null : Arrays.copyOf(locals, localsSize); + target.localsSize = localsSize; + target.stack = stack == null ? null : Arrays.copyOf(stack, stackSize); + target.stackSize = stackSize; + target.flags = flags; + target.dirty = true; + } else { + if (target.localsSize > localsSize) { + target.localsSize = localsSize; + target.dirty = true; + } + for (int i = 0; i < target.localsSize; i++) { + merge(locals[i], target.locals, i, target); + } + for (int i = 0; i < target.stackSize; i++) { + merge(stack[i], target.stack, i, target); + } + } + } + + private Type getLocalRawInternal(int index) { + checkLocal(index); + return locals[index]; + } + + Type getLocal(int index) { + Type ret = getLocalRawInternal(index); + if (index >= localsSize) { + localsSize = index + 1; + } + return ret; + } + + void setLocal(int index, Type type) { + Type old = getLocalRawInternal(index); + if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { + setLocalRawInternal(index + 1, Type.TOP_TYPE); + } + if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { + setLocalRawInternal(index - 1, Type.TOP_TYPE); + } + setLocalRawInternal(index, type); + if (index >= localsSize) { + localsSize = index + 1; + } + } + + void setLocal2(int index, Type type1, Type type2) { + Type old = getLocalRawInternal(index + 1); + if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { + setLocalRawInternal(index + 2, Type.TOP_TYPE); + } + old = getLocalRawInternal(index); + if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { + setLocalRawInternal(index - 1, Type.TOP_TYPE); + } + setLocalRawInternal(index, type1); + setLocalRawInternal(index + 1, type2); + if (index >= localsSize - 1) { + localsSize = index + 2; + } + } + + private void merge(Type me, Type[] toTypes, int i, Frame target) { + var to = toTypes[i]; + var newTo = to.mergeFrom(me, classHierarchy); + if (to != newTo && !to.equals(newTo)) { + toTypes[i] = newTo; + target.dirty = true; + } + } + + private static int trimAndCompress(Type[] types, int count) { + while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--; + int compressed = 0; + for (int i = 0; i < count; i++) { + if (!types[i].isCategory2_2nd()) { + types[compressed++] = types[i]; + } + } + return compressed; + } + + void trimAndCompress() { + localsSize = trimAndCompress(locals, localsSize); + stackSize = trimAndCompress(stack, stackSize); + } + + private static boolean equals(Type[] l1, Type[] l2, int commonSize) { + if (l1 == null || l2 == null) return commonSize == 0; + return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize); + } + + void writeTo(BufWriter out, Frame prevFrame, ConstantPoolBuilder cp) { + int offsetDelta = offset - prevFrame.offset - 1; + if (stackSize == 0) { + int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize; + int diffLocalsSize = localsSize - prevFrame.localsSize; + if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) { + if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame + out.writeU1(offsetDelta); + } else { //chop, same extended or append frame + out.writeU1(251 + diffLocalsSize); + out.writeU2(offsetDelta); + for (int i=commonLocalsSize; i + from == INTEGER_TYPE ? this : TOP_TYPE; + default -> + isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; + }; + } + } + + Type mergeComponentFrom(Type from, ClassHierarchyImpl context) { + if (this == TOP_TYPE || this == from || equals(from)) { + return this; + } else { + return switch (tag) { + case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT -> + TOP_TYPE; + default -> + isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; + }; + } + } + + private static final ClassDesc CD_Cloneable = ClassDesc.of("java.lang.Cloneable"); + private static final ClassDesc CD_Serializable = ClassDesc.of("java.io.Serializable"); + + private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { + if (from == NULL_TYPE) { + return this; + } else if (this == NULL_TYPE) { + return from; + } else if (sym.equals(from.sym)) { + return this; + } else if (isObject()) { + if (CD_Object.equals(sym)) { + return this; + } + if (context.isInterface(sym)) { + if (!from.isArray() || CD_Cloneable.equals(sym) || CD_Serializable.equals(sym)) { + return this; + } + } else if (from.isObject()) { + var anc = context.commonAncestor(sym, from.sym); + return anc == null ? this : Type.referenceType(anc); + } + } else if (isArray() && from.isArray()) { + Type compThis = getComponent(); + Type compFrom = from.getComponent(); + if (compThis != TOP_TYPE && compFrom != TOP_TYPE) { + return compThis.mergeComponentFrom(compFrom, context).toArray(); + } + } + return OBJECT_TYPE; + } + + Type toArray() { + return switch (tag) { + case ITEM_BOOLEAN -> BOOLEAN_ARRAY_TYPE; + case ITEM_BYTE -> BYTE_ARRAY_TYPE; + case ITEM_CHAR -> CHAR_ARRAY_TYPE; + case ITEM_SHORT -> SHORT_ARRAY_TYPE; + case ITEM_INTEGER -> INT_ARRAY_TYPE; + case ITEM_LONG -> LONG_ARRAY_TYPE; + case ITEM_FLOAT -> FLOAT_ARRAY_TYPE; + case ITEM_DOUBLE -> DOUBLE_ARRAY_TYPE; + case ITEM_OBJECT -> Type.referenceType(sym.arrayType()); + default -> OBJECT_TYPE; + }; + } + + Type getComponent() { + if (sym.isArray()) { + var comp = sym.componentType(); + if (comp.isPrimitive()) { + return switch (comp.descriptorString().charAt(0)) { + case 'Z' -> Type.BOOLEAN_TYPE; + case 'B' -> Type.BYTE_TYPE; + case 'C' -> Type.CHAR_TYPE; + case 'S' -> Type.SHORT_TYPE; + case 'I' -> Type.INTEGER_TYPE; + case 'J' -> Type.LONG_TYPE; + case 'F' -> Type.FLOAT_TYPE; + case 'D' -> Type.DOUBLE_TYPE; + default -> Type.TOP_TYPE; + }; + } + return Type.referenceType(comp); + } + return Type.TOP_TYPE; + } + + void writeTo(BufWriter bw, ConstantPoolBuilder cp) { + bw.writeU1(tag); + switch (tag) { + case ITEM_OBJECT -> + bw.writeU2(cp.classEntry(sym).index()); + case ITEM_UNINITIALIZED -> + bw.writeU2(bci); + } + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SuperclassImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SuperclassImpl.java new file mode 100644 index 0000000000000..8724b2ddf2ffb --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SuperclassImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Superclass; + +import static java.util.Objects.requireNonNull; + +public final class SuperclassImpl + extends AbstractElement + implements Superclass { + private final ClassEntry superclassEntry; + + public SuperclassImpl(ClassEntry superclassEntry) { + requireNonNull(superclassEntry); + this.superclassEntry = superclassEntry; + } + + @Override + public ClassEntry superclassEntry() { + return superclassEntry; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setSuperclass(superclassEntry); + } + + @Override + public String toString() { + return String.format("Superclass[superclassEntry=%s]", superclassEntry.name().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TargetInfoImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TargetInfoImpl.java new file mode 100644 index 0000000000000..57c4fb4bf3eeb --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TargetInfoImpl.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.Objects; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.TypeAnnotation.*; +import static jdk.internal.classfile.Classfile.*; + +public final class TargetInfoImpl { + + private TargetInfoImpl() { + } + + private static TargetType checkValid(TargetType targetType, int rangeFrom, int rangeTo) { + Objects.requireNonNull(targetType); + if (targetType.targetTypeValue() < rangeFrom || targetType.targetTypeValue() > rangeTo) + throw new IllegalArgumentException("Wrong target type specified " + targetType); + return targetType; + } + + public record TypeParameterTargetImpl(TargetType targetType, int typeParameterIndex) + implements TypeParameterTarget { + + public TypeParameterTargetImpl(TargetType targetType, int typeParameterIndex) { + this.targetType = checkValid(targetType, TAT_CLASS_TYPE_PARAMETER, TAT_METHOD_TYPE_PARAMETER); + this.typeParameterIndex = typeParameterIndex; + } + } + + public record SupertypeTargetImpl(int supertypeIndex) implements SupertypeTarget { + @Override + public TargetType targetType() { + return TargetType.CLASS_EXTENDS; + } + } + + public record TypeParameterBoundTargetImpl(TargetType targetType, int typeParameterIndex, int boundIndex) + implements TypeParameterBoundTarget { + + public TypeParameterBoundTargetImpl(TargetType targetType, int typeParameterIndex, int boundIndex) { + this.targetType = checkValid(targetType, TAT_CLASS_TYPE_PARAMETER_BOUND, TAT_METHOD_TYPE_PARAMETER_BOUND); + this.typeParameterIndex = typeParameterIndex; + this.boundIndex = boundIndex; + } + } + + public record EmptyTargetImpl(TargetType targetType) implements EmptyTarget { + + public EmptyTargetImpl(TargetType targetType) { + this.targetType = checkValid(targetType, TAT_FIELD, TAT_METHOD_RECEIVER); + } + } + + public record FormalParameterTargetImpl(int formalParameterIndex) implements FormalParameterTarget { + @Override + public TargetType targetType() { + return TargetType.METHOD_FORMAL_PARAMETER; + } + } + + public record ThrowsTargetImpl(int throwsTargetIndex) implements ThrowsTarget { + @Override + public TargetType targetType() { + return TargetType.THROWS; + } + } + + public record LocalVarTargetImpl(TargetType targetType, List table) + implements LocalVarTarget { + + public LocalVarTargetImpl(TargetType targetType, List table) { + this.targetType = checkValid(targetType, TAT_LOCAL_VARIABLE, TAT_RESOURCE_VARIABLE); + this.table = List.copyOf(table); + } + @Override + public int size() { + return 2 + 6 * table.size(); + } + } + + public record LocalVarTargetInfoImpl(Label startLabel, Label endLabel, int index) + implements LocalVarTargetInfo { + } + + public record CatchTargetImpl(int exceptionTableIndex) implements CatchTarget { + @Override + public TargetType targetType() { + return TargetType.EXCEPTION_PARAMETER; + } + } + + public record OffsetTargetImpl(TargetType targetType, Label target) implements OffsetTarget { + + public OffsetTargetImpl(TargetType targetType, Label target) { + this.targetType = checkValid(targetType, TAT_INSTANCEOF, TAT_METHOD_REFERENCE); + this.target = target; + } + } + + public record TypeArgumentTargetImpl(TargetType targetType, Label target, int typeArgumentIndex) + implements TypeArgumentTarget { + + public TypeArgumentTargetImpl(TargetType targetType, Label target, int typeArgumentIndex) { + this.targetType = checkValid(targetType, TAT_CAST, TAT_METHOD_REFERENCE_TYPE_ARGUMENT); + this.target = target; + this.typeArgumentIndex = typeArgumentIndex; + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TemporaryConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TemporaryConstantPool.java new file mode 100644 index 0000000000000..231d8b3718411 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TemporaryConstantPool.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.DoubleEntry; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.FloatEntry; +import jdk.internal.classfile.constantpool.IntegerEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.LongEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.MethodRefEntry; +import jdk.internal.classfile.constantpool.MethodTypeEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.StringEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; + +import java.lang.constant.MethodTypeDesc; +import java.util.List; + +public final class TemporaryConstantPool implements ConstantPoolBuilder { + + private TemporaryConstantPool() {} + + public static final TemporaryConstantPool INSTANCE = new TemporaryConstantPool(); + + @Override + public Utf8Entry utf8Entry(String s) { + return new AbstractPoolEntry.Utf8EntryImpl(this, -1, s); + } + + @Override + public IntegerEntry intEntry(int value) { + return new AbstractPoolEntry.IntegerEntryImpl(this, -1, value); + } + + @Override + public FloatEntry floatEntry(float value) { + return new AbstractPoolEntry.FloatEntryImpl(this, -1, value); + } + + @Override + public LongEntry longEntry(long value) { + return new AbstractPoolEntry.LongEntryImpl(this, -1, value); + } + + @Override + public DoubleEntry doubleEntry(double value) { + return new AbstractPoolEntry.DoubleEntryImpl(this, -1, value); + } + + @Override + public ClassEntry classEntry(Utf8Entry name) { + return new AbstractPoolEntry.ClassEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) name); + } + + @Override + public PackageEntry packageEntry(Utf8Entry name) { + return new AbstractPoolEntry.PackageEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) name); + } + + @Override + public ModuleEntry moduleEntry(Utf8Entry name) { + return new AbstractPoolEntry.ModuleEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) name); + } + + @Override + public NameAndTypeEntry nameAndTypeEntry(Utf8Entry nameEntry, Utf8Entry typeEntry) { + return new AbstractPoolEntry.NameAndTypeEntryImpl(this, -3, + (AbstractPoolEntry.Utf8EntryImpl) nameEntry, + (AbstractPoolEntry.Utf8EntryImpl) typeEntry); + } + + @Override + public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new AbstractPoolEntry.FieldRefEntryImpl(this, -3, + (AbstractPoolEntry.ClassEntryImpl) owner, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + } + + @Override + public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new AbstractPoolEntry.MethodRefEntryImpl(this, -3, + (AbstractPoolEntry.ClassEntryImpl) owner, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + } + + @Override + public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, -3, + (AbstractPoolEntry.ClassEntryImpl) owner, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + } + + @Override + public MethodTypeEntry methodTypeEntry(MethodTypeDesc descriptor) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodTypeEntry methodTypeEntry(Utf8Entry descriptor) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodHandleEntry methodHandleEntry(int refKind, MemberRefEntry reference) { + throw new UnsupportedOperationException(); + } + + @Override + public InvokeDynamicEntry invokeDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, NameAndTypeEntry nameAndType) { + throw new UnsupportedOperationException(); + } + + @Override + public ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, NameAndTypeEntry nameAndType) { + throw new UnsupportedOperationException(); + } + + @Override + public StringEntry stringEntry(Utf8Entry utf8) { + return new AbstractPoolEntry.StringEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) utf8); + } + + @Override + public BootstrapMethodEntry bsmEntry(MethodHandleEntry methodReference, List arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public PoolEntry entryByIndex(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int entryCount() { + throw new UnsupportedOperationException(); + } + + @Override + public BootstrapMethodEntry bootstrapMethodEntry(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int bootstrapMethodCount() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canWriteDirect(ConstantPool constantPool) { + return false; + } + + @Override + public boolean writeBootstrapMethods(BufWriter buf) { + throw new UnsupportedOperationException(); + } + + @Override + public void writeTo(BufWriter buf) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalCodeBuilder.java new file mode 100644 index 0000000000000..df1adbd7dec10 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalCodeBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.CodeBuilder; + +public sealed interface TerminalCodeBuilder extends CodeBuilder + permits DirectCodeBuilder, BufferedCodeBuilder, TransformingCodeBuilder { + +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalFieldBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalFieldBuilder.java new file mode 100644 index 0000000000000..6cbba5e93d2f1 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalFieldBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.FieldBuilder; + +public sealed interface TerminalFieldBuilder + extends FieldBuilder + permits DirectFieldBuilder, BufferedFieldBuilder { +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalMethodBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalMethodBuilder.java new file mode 100644 index 0000000000000..8170d2c60567e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TerminalMethodBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.MethodBuilder; + +public sealed interface TerminalMethodBuilder + extends MethodBuilder + permits BufferedMethodBuilder, DirectMethodBuilder { + BufferedCodeBuilder bufferedCodeBuilder(CodeModel original); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java new file mode 100644 index 0000000000000..0ada64f29b55f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.ClassfileElement; +import jdk.internal.classfile.ClassfileTransform; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.FieldBuilder; +import jdk.internal.classfile.FieldElement; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.FieldTransform; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.MethodTransform; + +public class TransformImpl { + // ClassTransform + + private TransformImpl() { + } + + private static Runnable chainRunnable(Runnable a, Runnable b) { + return () -> { a.run(); b.run(); }; + } + + private static final Runnable NOTHING = () -> { }; + + interface UnresolvedClassTransform extends ClassTransform { + @Override + default void accept(ClassBuilder builder, ClassElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(ClassBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(ClassBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ResolvedTransformImpl(Consumer consumer, + Runnable endHandler, + Runnable startHandler) + implements ClassfileTransform.ResolvedTransform { + + public ResolvedTransformImpl(Consumer consumer) { + this(consumer, NOTHING, NOTHING); + } + } + + public record ChainedClassTransform(ClassTransform t, + ClassTransform next) + implements UnresolvedClassTransform { + @Override + public ResolvedTransformImpl resolve(ClassBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + ClassBuilder chainedBuilder = new ChainedClassBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierClassTransform(Supplier supplier) + implements UnresolvedClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return supplier.get().resolve(builder); + } + } + + public record ClassMethodTransform(MethodTransform transform, + Predicate filter) + implements UnresolvedClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return new ResolvedTransformImpl<>(ce -> { + if (ce instanceof MethodModel mm && filter.test(mm)) + builder.transformMethod(mm, transform); + else + builder.with(ce); + }); + } + + @Override + public ClassTransform andThen(ClassTransform next) { + if (next instanceof ClassMethodTransform cmt) + return new ClassMethodTransform(transform.andThen(cmt.transform), + mm -> filter.test(mm) && cmt.filter.test(mm)); + else + return UnresolvedClassTransform.super.andThen(next); + } + } + + public record ClassFieldTransform(FieldTransform transform, + Predicate filter) + implements UnresolvedClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return new ResolvedTransformImpl<>(ce -> { + if (ce instanceof FieldModel fm && filter.test(fm)) + builder.transformField(fm, transform); + else + builder.with(ce); + }); + } + + @Override + public ClassTransform andThen(ClassTransform next) { + if (next instanceof ClassFieldTransform cft) + return new ClassFieldTransform(transform.andThen(cft.transform), + mm -> filter.test(mm) && cft.filter.test(mm)); + else + return UnresolvedClassTransform.super.andThen(next); + } + } + + // MethodTransform + + interface UnresolvedMethodTransform extends MethodTransform { + @Override + default void accept(MethodBuilder builder, MethodElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(MethodBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(MethodBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ChainedMethodTransform(MethodTransform t, + MethodTransform next) + implements TransformImpl.UnresolvedMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + MethodBuilder chainedBuilder = new ChainedMethodBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierMethodTransform(Supplier supplier) + implements TransformImpl.UnresolvedMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + return supplier.get().resolve(builder); + } + } + + public record MethodCodeTransform(CodeTransform xform) + implements TransformImpl.UnresolvedMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + return new ResolvedTransformImpl<>(me -> { + if (me instanceof CodeModel cm) { + builder.transformCode(cm, xform); + } + else { + builder.with(me); + } + }, NOTHING, NOTHING); + } + + @Override + public MethodTransform andThen(MethodTransform next) { + return (next instanceof TransformImpl.MethodCodeTransform mct) + ? new TransformImpl.MethodCodeTransform(xform.andThen(mct.xform)) + : UnresolvedMethodTransform.super.andThen(next); + + } + } + + // FieldTransform + + interface UnresolvedFieldTransform extends FieldTransform { + @Override + default void accept(FieldBuilder builder, FieldElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(FieldBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(FieldBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ChainedFieldTransform(FieldTransform t, FieldTransform next) + implements UnresolvedFieldTransform { + @Override + public ResolvedTransform resolve(FieldBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + FieldBuilder chainedBuilder = new ChainedFieldBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierFieldTransform(Supplier supplier) + implements UnresolvedFieldTransform { + @Override + public ResolvedTransform resolve(FieldBuilder builder) { + return supplier.get().resolve(builder); + } + } + + // CodeTransform + + interface UnresolvedCodeTransform extends CodeTransform { + @Override + default void accept(CodeBuilder builder, CodeElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(CodeBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(CodeBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ChainedCodeTransform(CodeTransform t, CodeTransform next) + implements UnresolvedCodeTransform { + @Override + public ResolvedTransform resolve(CodeBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + CodeBuilder chainedBuilder = new ChainedCodeBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierCodeTransform(Supplier supplier) + implements UnresolvedCodeTransform { + @Override + public ResolvedTransform resolve(CodeBuilder builder) { + return supplier.get().resolve(builder); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformingCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformingCodeBuilder.java new file mode 100644 index 0000000000000..7fcba5c12608d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformingCodeBuilder.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeModel; +import java.util.Optional; +import java.util.function.Consumer; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; + +public final class TransformingCodeBuilder implements TerminalCodeBuilder { + + final CodeBuilder delegate; + final Consumer consumer; + + public TransformingCodeBuilder(CodeBuilder delegate, Consumer consumer) { + this.delegate = delegate; + this.consumer = consumer; + } + + @Override + public CodeBuilder with(CodeElement e) { + consumer.accept(e); + return this; + } + + @Override + public Optional original() { + return delegate.original(); + } + + @Override + public Label newLabel() { + return delegate.newLabel(); + } + + @Override + public Label startLabel() { + return delegate.startLabel(); + } + + @Override + public Label endLabel() { + return delegate.endLabel(); + } + + @Override + public int receiverSlot() { + return delegate.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return delegate.parameterSlot(paramNo); + } + + @Override + public int allocateLocal(TypeKind typeKind) { + return delegate.allocateLocal(typeKind); + } + + @Override + public ConstantPoolBuilder constantPool() { + return delegate.constantPool(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java new file mode 100644 index 0000000000000..de81acc9f0db5 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java @@ -0,0 +1,946 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.AnnotationElement; +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.AttributeMapper; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.TypeAnnotation; +import jdk.internal.classfile.attribute.AnnotationDefaultAttribute; +import jdk.internal.classfile.attribute.BootstrapMethodsAttribute; +import jdk.internal.classfile.attribute.CharacterRangeInfo; +import jdk.internal.classfile.attribute.CharacterRangeTableAttribute; +import jdk.internal.classfile.attribute.CompilationIDAttribute; +import jdk.internal.classfile.attribute.ConstantValueAttribute; +import jdk.internal.classfile.attribute.DeprecatedAttribute; +import jdk.internal.classfile.attribute.EnclosingMethodAttribute; +import jdk.internal.classfile.attribute.ExceptionsAttribute; +import jdk.internal.classfile.attribute.InnerClassInfo; +import jdk.internal.classfile.attribute.InnerClassesAttribute; +import jdk.internal.classfile.attribute.LineNumberInfo; +import jdk.internal.classfile.attribute.LineNumberTableAttribute; +import jdk.internal.classfile.attribute.LocalVariableInfo; +import jdk.internal.classfile.attribute.LocalVariableTableAttribute; +import jdk.internal.classfile.attribute.LocalVariableTypeInfo; +import jdk.internal.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.internal.classfile.attribute.MethodParameterInfo; +import jdk.internal.classfile.attribute.MethodParametersAttribute; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleExportInfo; +import jdk.internal.classfile.attribute.ModuleHashInfo; +import jdk.internal.classfile.attribute.ModuleHashesAttribute; +import jdk.internal.classfile.attribute.ModuleMainClassAttribute; +import jdk.internal.classfile.attribute.ModuleOpenInfo; +import jdk.internal.classfile.attribute.ModulePackagesAttribute; +import jdk.internal.classfile.attribute.ModuleProvideInfo; +import jdk.internal.classfile.attribute.ModuleRequireInfo; +import jdk.internal.classfile.attribute.ModuleResolutionAttribute; +import jdk.internal.classfile.attribute.ModuleTargetAttribute; +import jdk.internal.classfile.attribute.NestHostAttribute; +import jdk.internal.classfile.attribute.NestMembersAttribute; +import jdk.internal.classfile.attribute.PermittedSubclassesAttribute; +import jdk.internal.classfile.attribute.RecordAttribute; +import jdk.internal.classfile.attribute.RecordComponentInfo; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.classfile.attribute.SourceIDAttribute; +import jdk.internal.classfile.attribute.StackMapTableAttribute; +import jdk.internal.classfile.attribute.StackMapFrameInfo; +import jdk.internal.classfile.attribute.SyntheticAttribute; +import jdk.internal.classfile.constantpool.ConstantValueEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; + +public abstract sealed class UnboundAttribute> + extends AbstractElement + implements Attribute { + protected final AttributeMapper mapper; + + public UnboundAttribute(AttributeMapper mapper) { + this.mapper = mapper; + } + + @Override + public AttributeMapper attributeMapper() { + return mapper; + } + + @Override + public String attributeName() { + return mapper.name(); + } + + @Override + @SuppressWarnings("unchecked") + public void writeTo(BufWriter buf) { + mapper.writeAttribute(buf, (T) this); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public String toString() { + return String.format("Attribute[name=%s]", mapper.name()); + } + public static final class UnboundConstantValueAttribute + extends UnboundAttribute + implements ConstantValueAttribute { + + private final ConstantValueEntry entry; + + public UnboundConstantValueAttribute(ConstantValueEntry entry) { + super(Attributes.CONSTANT_VALUE); + this.entry = entry; + } + + @Override + public ConstantValueEntry constant() { + return entry; + } + + } + + public static final class UnboundDeprecatedAttribute + extends UnboundAttribute + implements DeprecatedAttribute { + public UnboundDeprecatedAttribute() { + super(Attributes.DEPRECATED); + } + } + + public static final class UnboundSyntheticAttribute + extends UnboundAttribute + implements SyntheticAttribute { + public UnboundSyntheticAttribute() { + super(Attributes.SYNTHETIC); + } + } + + public static final class UnboundSignatureAttribute + extends UnboundAttribute + implements SignatureAttribute { + private final Utf8Entry signature; + + public UnboundSignatureAttribute(Utf8Entry signature) { + super(Attributes.SIGNATURE); + this.signature = signature; + } + + @Override + public Utf8Entry signature() { + return signature; + } + } + + public static final class UnboundExceptionsAttribute + extends UnboundAttribute + implements ExceptionsAttribute { + private final List exceptions; + + public UnboundExceptionsAttribute(List exceptions) { + super(Attributes.EXCEPTIONS); + this.exceptions = List.copyOf(exceptions); + } + + @Override + public List exceptions() { + return exceptions; + } + } + + public static final class UnboundAnnotationDefaultAttribute + extends UnboundAttribute + implements AnnotationDefaultAttribute { + private final AnnotationValue annotationDefault; + + public UnboundAnnotationDefaultAttribute(AnnotationValue annotationDefault) { + super(Attributes.ANNOTATION_DEFAULT); + this.annotationDefault = annotationDefault; + } + + @Override + public AnnotationValue defaultValue() { + return annotationDefault; + } + } + + public static final class UnboundSourceFileAttribute extends UnboundAttribute + implements SourceFileAttribute { + private final Utf8Entry sourceFile; + + public UnboundSourceFileAttribute(Utf8Entry sourceFile) { + super(Attributes.SOURCE_FILE); + this.sourceFile = sourceFile; + } + + @Override + public Utf8Entry sourceFile() { + return sourceFile; + } + + } + + public static final class UnboundStackMapTableAttribute extends UnboundAttribute + implements StackMapTableAttribute { + private final List entries; + + public UnboundStackMapTableAttribute(List entries) { + super(Attributes.STACK_MAP_TABLE); + this.entries = List.copyOf(entries); + } + + @Override + public List entries() { + return entries; + } + } + + public static final class UnboundInnerClassesAttribute + extends UnboundAttribute + implements InnerClassesAttribute { + private final List innerClasses; + + public UnboundInnerClassesAttribute(List innerClasses) { + super(Attributes.INNER_CLASSES); + this.innerClasses = List.copyOf(innerClasses); + } + + @Override + public List classes() { + return innerClasses; + } + } + + public static final class UnboundRecordAttribute + extends UnboundAttribute + implements RecordAttribute { + private final List components; + + public UnboundRecordAttribute(List components) { + super(Attributes.RECORD); + this.components = List.copyOf(components); + } + + @Override + public List components() { + return components; + } + } + + public static final class UnboundEnclosingMethodAttribute + extends UnboundAttribute + implements EnclosingMethodAttribute { + private final ClassEntry classEntry; + private final NameAndTypeEntry method; + + public UnboundEnclosingMethodAttribute(ClassEntry classEntry, NameAndTypeEntry method) { + super(Attributes.ENCLOSING_METHOD); + this.classEntry = classEntry; + this.method = method; + } + + @Override + public ClassEntry enclosingClass() { + return classEntry; + } + + @Override + public Optional enclosingMethod() { + return Optional.ofNullable(method); + } + } + + public static final class UnboundMethodParametersAttribute + extends UnboundAttribute + implements MethodParametersAttribute { + private final List parameters; + + public UnboundMethodParametersAttribute(List parameters) { + super(Attributes.METHOD_PARAMETERS); + this.parameters = List.copyOf(parameters); + } + + @Override + public List parameters() { + return parameters; + } + } + + public static final class UnboundModuleTargetAttribute + extends UnboundAttribute + implements ModuleTargetAttribute { + final Utf8Entry moduleTarget; + + public UnboundModuleTargetAttribute(Utf8Entry moduleTarget) { + super(Attributes.MODULE_TARGET); + this.moduleTarget = moduleTarget; + } + + @Override + public Utf8Entry targetPlatform() { + return moduleTarget; + } + } + + public static final class UnboundModuleMainClassAttribute + extends UnboundAttribute + implements ModuleMainClassAttribute { + final ClassEntry mainClass; + + public UnboundModuleMainClassAttribute(ClassEntry mainClass) { + super(Attributes.MODULE_MAIN_CLASS); + this.mainClass = mainClass; + } + + @Override + public ClassEntry mainClass() { + return mainClass; + } + } + + public static final class UnboundModuleHashesAttribute + extends UnboundAttribute + implements ModuleHashesAttribute { + private final Utf8Entry algorithm; + private final List hashes; + + public UnboundModuleHashesAttribute(Utf8Entry algorithm, List hashes) { + super(Attributes.MODULE_HASHES); + this.algorithm = algorithm; + this.hashes = List.copyOf(hashes); + } + + @Override + public Utf8Entry algorithm() { + return algorithm; + } + + @Override + public List hashes() { + return hashes; + } + } + + public static final class UnboundModulePackagesAttribute + extends UnboundAttribute + implements ModulePackagesAttribute { + private final Collection packages; + + public UnboundModulePackagesAttribute(Collection packages) { + super(Attributes.MODULE_PACKAGES); + this.packages = List.copyOf(packages); + } + + @Override + public List packages() { + return List.copyOf(packages); + } + } + + public static final class UnboundModuleResolutionAttribute + extends UnboundAttribute + implements ModuleResolutionAttribute { + private final int resolutionFlags; + + public UnboundModuleResolutionAttribute(int flags) { + super(Attributes.MODULE_RESOLUTION); + resolutionFlags = flags; + } + + @Override + public int resolutionFlags() { + return resolutionFlags; + } + } + + public static final class UnboundPermittedSubclassesAttribute + extends UnboundAttribute + implements PermittedSubclassesAttribute { + private final List permittedSubclasses; + + public UnboundPermittedSubclassesAttribute(List permittedSubclasses) { + super(Attributes.PERMITTED_SUBCLASSES); + this.permittedSubclasses = List.copyOf(permittedSubclasses); + } + + @Override + public List permittedSubclasses() { + return permittedSubclasses; + } + } + + public static final class UnboundNestMembersAttribute + extends UnboundAttribute + implements NestMembersAttribute { + private final List memberEntries; + + public UnboundNestMembersAttribute(List memberEntries) { + super(Attributes.NEST_MEMBERS); + this.memberEntries = List.copyOf(memberEntries); + } + + @Override + public List nestMembers() { + return memberEntries; + } + } + + public static final class UnboundNestHostAttribute + extends UnboundAttribute + implements NestHostAttribute { + private final ClassEntry hostEntry; + + public UnboundNestHostAttribute(ClassEntry hostEntry) { + super(Attributes.NEST_HOST); + this.hostEntry = hostEntry; + } + + @Override + public ClassEntry nestHost() { + return hostEntry; + } + } + + public static final class UnboundCompilationIDAttribute + extends UnboundAttribute + implements CompilationIDAttribute { + private final Utf8Entry idEntry; + + public UnboundCompilationIDAttribute(Utf8Entry idEntry) { + super(Attributes.COMPILATION_ID); + this.idEntry = idEntry; + } + + @Override + public Utf8Entry compilationId() { + return idEntry; + } + } + + public static final class UnboundSourceIDAttribute + extends UnboundAttribute + implements SourceIDAttribute { + private final Utf8Entry idEntry; + + public UnboundSourceIDAttribute(Utf8Entry idEntry) { + super(Attributes.SOURCE_ID); + this.idEntry = idEntry; + } + + @Override + public Utf8Entry sourceId() { + return idEntry; + } + } + + public static final class UnboundSourceDebugExtensionAttribute + extends UnboundAttribute + implements SourceDebugExtensionAttribute { + private final byte[] contents; + + public UnboundSourceDebugExtensionAttribute(byte[] contents) { + super(Attributes.SOURCE_DEBUG_EXTENSION); + this.contents = contents; + } + + @Override + public byte[] contents() { + return contents; + } + } + + public static final class UnboundCharacterRangeTableAttribute + extends UnboundAttribute + implements CharacterRangeTableAttribute { + private final List ranges; + + public UnboundCharacterRangeTableAttribute(List ranges) { + super(Attributes.CHARACTER_RANGE_TABLE); + this.ranges = List.copyOf(ranges); + } + + @Override + public List characterRangeTable() { + return ranges; + } + } + + public static final class UnboundLineNumberTableAttribute + extends UnboundAttribute + implements LineNumberTableAttribute { + private final List lines; + + public UnboundLineNumberTableAttribute(List lines) { + super(Attributes.LINE_NUMBER_TABLE); + this.lines = List.copyOf(lines); + } + + @Override + public List lineNumbers() { + return lines; + } + } + + public static final class UnboundLocalVariableTableAttribute + extends UnboundAttribute + implements LocalVariableTableAttribute { + private final List locals; + + public UnboundLocalVariableTableAttribute(List locals) { + super(Attributes.LOCAL_VARIABLE_TABLE); + this.locals = List.copyOf(locals); + } + + @Override + public List localVariables() { + return locals; + } + } + + public static final class UnboundLocalVariableTypeTableAttribute + extends UnboundAttribute + implements LocalVariableTypeTableAttribute { + private final List locals; + + public UnboundLocalVariableTypeTableAttribute(List locals) { + super(Attributes.LOCAL_VARIABLE_TYPE_TABLE); + this.locals = List.copyOf(locals); + } + + @Override + public List localVariableTypes() { + return locals; + } + } + + public static final class UnboundRuntimeVisibleAnnotationsAttribute + extends UnboundAttribute + implements RuntimeVisibleAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeVisibleAnnotationsAttribute(List elements) { + super(Attributes.RUNTIME_VISIBLE_ANNOTATIONS); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public static final class UnboundRuntimeInvisibleAnnotationsAttribute + extends UnboundAttribute + implements RuntimeInvisibleAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeInvisibleAnnotationsAttribute(List elements) { + super(Attributes.RUNTIME_INVISIBLE_ANNOTATIONS); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public static final class UnboundRuntimeVisibleParameterAnnotationsAttribute + extends UnboundAttribute + implements RuntimeVisibleParameterAnnotationsAttribute { + private final List> elements; + + public UnboundRuntimeVisibleParameterAnnotationsAttribute(List> elements) { + super(Attributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS); + this.elements = List.copyOf(elements); + } + + @Override + public List> parameterAnnotations() { + return elements; + } + } + + public static final class UnboundRuntimeInvisibleParameterAnnotationsAttribute + extends UnboundAttribute + implements RuntimeInvisibleParameterAnnotationsAttribute { + private final List> elements; + + public UnboundRuntimeInvisibleParameterAnnotationsAttribute(List> elements) { + super(Attributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS); + this.elements = List.copyOf(elements); + } + + @Override + public List> parameterAnnotations() { + return elements; + } + } + + public static final class UnboundRuntimeVisibleTypeAnnotationsAttribute + extends UnboundAttribute + implements RuntimeVisibleTypeAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeVisibleTypeAnnotationsAttribute(List elements) { + super(Attributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public static final class UnboundRuntimeInvisibleTypeAnnotationsAttribute + extends UnboundAttribute + implements RuntimeInvisibleTypeAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeInvisibleTypeAnnotationsAttribute(List elements) { + super(Attributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public record UnboundCharacterRangeInfo(int startPc, int endPc, + int characterRangeStart, + int characterRangeEnd, + int flags) + implements CharacterRangeInfo { } + + public record UnboundInnerClassInfo(ClassEntry innerClass, + Optional outerClass, + Optional innerName, + int flagsMask) + implements InnerClassInfo {} + + public record UnboundLineNumberInfo(int startPc, int lineNumber) + implements LineNumberInfo { } + + public record UnboundLocalVariableInfo(int startPc, int length, + Utf8Entry name, + Utf8Entry type, + int slot) + implements LocalVariableInfo { } + + public record UnboundLocalVariableTypeInfo(int startPc, int length, + Utf8Entry name, + Utf8Entry signature, + int slot) + implements LocalVariableTypeInfo { } + + public record UnboundMethodParameterInfo(Optional name, int flagsMask) + implements MethodParameterInfo {} + + public record UnboundModuleExportInfo(PackageEntry exportedPackage, + int exportsFlagsMask, + List exportsTo) + implements ModuleExportInfo { + public UnboundModuleExportInfo(PackageEntry exportedPackage, int exportsFlagsMask, + List exportsTo) { + this.exportedPackage = exportedPackage; + this.exportsFlagsMask = exportsFlagsMask; + this.exportsTo = List.copyOf(exportsTo); + } + } + + public record UnboundModuleHashInfo(ModuleEntry moduleName, + byte[] hash) implements ModuleHashInfo { } + + public record UnboundModuleOpenInfo(PackageEntry openedPackage, int opensFlagsMask, + List opensTo) + implements ModuleOpenInfo { + public UnboundModuleOpenInfo(PackageEntry openedPackage, int opensFlagsMask, + List opensTo) { + this.openedPackage = openedPackage; + this.opensFlagsMask = opensFlagsMask; + this.opensTo = List.copyOf(opensTo); + } + } + + public record UnboundModuleProvideInfo(ClassEntry provides, + List providesWith) + implements ModuleProvideInfo { + public UnboundModuleProvideInfo(ClassEntry provides, List providesWith) { + this.provides = provides; + this.providesWith = List.copyOf(providesWith); + } + } + + public record UnboundModuleRequiresInfo(ModuleEntry requires, int requiresFlagsMask, + Optional requiresVersion) + implements ModuleRequireInfo {} + + public record UnboundRecordComponentInfo(Utf8Entry name, + Utf8Entry descriptor, + List> attributes) + implements RecordComponentInfo { + public UnboundRecordComponentInfo(Utf8Entry name, Utf8Entry descriptor, List> attributes) { + this.name = name; + this.descriptor = descriptor; + this.attributes = List.copyOf(attributes); + } + } + + public record UnboundTypeAnnotation(TargetInfo targetInfo, + List targetPath, + Utf8Entry className, + List elements) implements TypeAnnotation { + + public UnboundTypeAnnotation(TargetInfo targetInfo, List targetPath, + Utf8Entry className, List elements) { + this.targetInfo = targetInfo; + this.targetPath = List.copyOf(targetPath); + this.className = className; + this.elements = List.copyOf(elements); + } + + private int labelToBci(LabelContext lr, Label label) { + //helper method to avoid NPE + if (lr == null) throw new IllegalArgumentException("Illegal targetType '%s' in TypeAnnotation outside of Code attribute".formatted(targetInfo.targetType())); + return lr.labelToBci(label); + } + + @Override + public void writeTo(BufWriter buf) { + LabelContext lr = ((BufWriterImpl) buf).labelContext(); + // target_type + buf.writeU1(targetInfo.targetType().targetTypeValue()); + + // target_info + switch (targetInfo) { + case TypeParameterTarget tpt -> buf.writeU1(tpt.typeParameterIndex()); + case SupertypeTarget st -> buf.writeU2(st.supertypeIndex()); + case TypeParameterBoundTarget tpbt -> { + buf.writeU1(tpbt.typeParameterIndex()); + buf.writeU1(tpbt.boundIndex()); + } + case EmptyTarget et -> { + // nothing to write + } + case FormalParameterTarget fpt -> buf.writeU1(fpt.formalParameterIndex()); + case ThrowsTarget tt -> buf.writeU2(tt.throwsTargetIndex()); + case LocalVarTarget lvt -> { + buf.writeU2(lvt.table().size()); + for (var e : lvt.table()) { + int startPc = labelToBci(lr, e.startLabel()); + buf.writeU2(startPc); + buf.writeU2(labelToBci(lr, e.endLabel()) - startPc); + buf.writeU2(e.index()); + } + } + case CatchTarget ct -> buf.writeU2(ct.exceptionTableIndex()); + case OffsetTarget ot -> buf.writeU2(labelToBci(lr, ot.target())); + case TypeArgumentTarget tat -> { + buf.writeU2(labelToBci(lr, tat.target())); + buf.writeU1(tat.typeArgumentIndex()); + } + } + + // target_path + buf.writeU1(targetPath().size()); + for (TypePathComponent component : targetPath()) { + buf.writeU1(component.typePathKind().tag()); + buf.writeU1(component.typeArgumentIndex()); + } + + // type_index + buf.writeIndex(className); + + // element_value_pairs + buf.writeU2(elements.size()); + for (AnnotationElement pair : elements()) { + buf.writeIndex(pair.name()); + pair.value().writeTo(buf); + } + } + } + + public record TypePathComponentImpl(TypeAnnotation.TypePathComponent.Kind typePathKind, int typeArgumentIndex) + implements TypeAnnotation.TypePathComponent {} + + public static final class UnboundModuleAttribute extends UnboundAttribute implements ModuleAttribute { + private final ModuleEntry moduleName; + private final int moduleFlags; + private final Utf8Entry moduleVersion; + private final List requires; + private final List exports; + private final List opens; + private final List uses; + private final List provides; + + public UnboundModuleAttribute(ModuleEntry moduleName, + int moduleFlags, + Utf8Entry moduleVersion, + Collection requires, + Collection exports, + Collection opens, + Collection uses, + Collection provides) + { + super(Attributes.MODULE); + this.moduleName = moduleName; + this.moduleFlags = moduleFlags; + this.moduleVersion = moduleVersion; + this.requires = List.copyOf(requires); + this.exports = List.copyOf(exports); + this.opens = List.copyOf(opens); + this.uses = List.copyOf(uses); + this.provides = List.copyOf(provides); + } + + @Override + public ModuleEntry moduleName() { + return moduleName; + } + + @Override + public int moduleFlagsMask() { + return moduleFlags; + } + + @Override + public Optional moduleVersion() { + return Optional.ofNullable(moduleVersion); + } + + @Override + public List requires() { + return requires; + } + + @Override + public List exports() { + return exports; + } + + @Override + public List opens() { + return opens; + } + + @Override + public List uses() { + return uses; + } + + @Override + public List provides() { + return provides; + } + } + + public static abstract non-sealed class AdHocAttribute> + extends UnboundAttribute { + + public AdHocAttribute(AttributeMapper mapper) { + super(mapper); + } + + public abstract void writeBody(BufWriter b); + + @Override + public void writeTo(BufWriter b) { + b.writeIndex(b.constantPool().utf8Entry(mapper.name())); + b.writeInt(0); + int start = b.size(); + writeBody(b); + int written = b.size() - start; + b.patchInt(start - 4, 4, written); + } + } + + public static final class EmptyBootstrapAttribute + extends UnboundAttribute + implements BootstrapMethodsAttribute { + public EmptyBootstrapAttribute() { + super(Attributes.BOOTSTRAP_METHODS); + } + + @Override + public int bootstrapMethodsSize() { + return 0; + } + + @Override + public List bootstrapMethods() { + return List.of(); + } + } + + public static abstract sealed class CustomAttribute> + extends UnboundAttribute + permits jdk.internal.classfile.CustomAttribute { + + public CustomAttribute(AttributeMapper mapper) { + super(mapper); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java new file mode 100644 index 0000000000000..1d3ea6dbf14ec --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.util.AbstractList; +import java.util.BitSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import java.lang.reflect.AccessFlag; + +import static jdk.internal.classfile.Classfile.ACC_STATIC; +import jdk.internal.access.SharedSecrets; + +/** + * Helper to create and manipulate type descriptors, where type descriptors are + * represented as JVM type descriptor strings and symbols are represented as + * name strings + */ +public class Util { + + private Util() { + } + + public static String arrayOf(CharSequence s) { + return "[" + s; + } + + public static BitSet findParams(String type) { + BitSet bs = new BitSet(); + if (type.charAt(0) != '(') + throw new IllegalArgumentException(); + loop: for (int i = 1; i < type.length(); ++i) { + switch (type.charAt(i)) { + case '[': + bs.set(i); + while (type.charAt(++i) == '[') + ; + if (type.charAt(i) == 'L') { + while (type.charAt(++i) != ';') + ; + } + break; + case ')': + break loop; + default: + bs.set(i); + if (type.charAt(i) == 'L') { + while (type.charAt(++i) != ';') + ; + } + } + } + return bs; + } + + @SuppressWarnings("fallthrough") + public static int parameterSlots(String type) { + BitSet bs = findParams(type); + int count = 0; + for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) { + count += (type.charAt(i) == 'J' || type.charAt(i) == 'D') ? 2 : 1; + } + return count; + } + + public static int[] parseParameterSlots(int flags, String type) { + BitSet bs = findParams(type); + int[] result = new int[bs.cardinality()]; + int index = 0; + int count = ((flags & ACC_STATIC) != 0) ? 0 : 1; + for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) { + result[index++] = count; + count += (type.charAt(i) == 'J' || type.charAt(i) == 'D') ? 2 : 1; + } + return result; + } + + public static int maxLocals(int flags, String type) { + BitSet bs = findParams(type); + int count = ((flags & ACC_STATIC) != 0) ? 0 : 1; + for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) + count += (type.charAt(i) == 'J' || type.charAt(i) == 'D') ? 2 : 1; + return count; + } + + public static String toClassString(String desc) { + //TODO: this doesn't look right L ... ; + return desc.replace('/', '.'); + } + + public static Iterator parameterTypes(String s) { + //TODO: gracefully non-method types + return new Iterator<>() { + int ch = 1; + + @Override + public boolean hasNext() { + return s.charAt(ch) != ')'; + } + + @Override + public String next() { + char curr = s.charAt(ch); + switch (curr) { + case 'C', 'B', 'S', 'I', 'J', 'F', 'D', 'Z': + ch++; + return String.valueOf(curr); + case '[': + ch++; + return "[" + next(); + case 'L': { + int start = ch; + while (s.charAt(++ch) != ';') { } + ++ch; + return s.substring(start, ch); + } + default: + throw new AssertionError("cannot parse string: " + s); + } + } + }; + } + + public static String returnDescriptor(String s) { + return s.substring(s.indexOf(')') + 1); + } + + public static String toInternalName(ClassDesc cd) { + var desc = cd.descriptorString(); + return switch (desc.charAt(0)) { + case 'L' -> desc.substring(1, desc.length() - 1); + default -> throw new IllegalArgumentException(desc); + }; + } + + public static ClassDesc toClassDesc(String classInternalNameOrArrayDesc) { + return classInternalNameOrArrayDesc.charAt(0) == '[' + ? ClassDesc.ofDescriptor(classInternalNameOrArrayDesc) + : ClassDesc.ofInternalName(classInternalNameOrArrayDesc); + } + + public static List mappedList(List list, Function mapper) { + return new AbstractList<>() { + @Override + public U get(int index) { + return mapper.apply(list.get(index)); + } + + @Override + public int size() { + return list.size(); + } + }; + } + + public static List entryList(List list) { + var result = new Object[list.size()]; // null check + for (int i = 0; i < result.length; i++) { + result[i] = TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(toInternalName(list.get(i)))); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(result); + } + + public static List moduleEntryList(List list) { + var result = new Object[list.size()]; // null check + for (int i = 0; i < result.length; i++) { + result[i] = TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(list.get(i).moduleName())); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArrayNullsAllowed(result); + } + + public static void checkKind(Opcode op, Opcode.Kind k) { + if (op.kind() != k) + throw new IllegalArgumentException( + String.format("Wrong opcode kind specified; found %s(%s), expected %s", op, op.kind(), k)); + } + + public static int flagsToBits(AccessFlag.Location location, Collection flags) { + int i = 0; + for (AccessFlag f : flags) { + if (!f.locations().contains(location)) { + throw new IllegalArgumentException("unexpected flag: " + f + " use in target location: " + location); + } + i |= f.mask(); + } + return i; + } + + public static int flagsToBits(AccessFlag.Location location, AccessFlag... flags) { + int i = 0; + for (AccessFlag f : flags) { + if (!f.locations().contains(location)) { + throw new IllegalArgumentException("unexpected flag: " + f + " use in target location: " + location); + } + i |= f.mask(); + } + return i; + } + + public static boolean has(AccessFlag.Location location, int flagsMask, AccessFlag flag) { + return (flag.mask() & flagsMask) == flag.mask() && flag.locations().contains(location); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java new file mode 100644 index 0000000000000..2fc2a5aa78a4b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl.verifier; + +import java.nio.ByteBuffer; + +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType; +import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.*; + +/** + * @see hotspot/share/interpreter/bytecodes.hpp + * @see hotspot/share/interpreter/bytecodes.cpp + */ +final class VerificationBytecodes { + + static final int _breakpoint = 202, + _fast_agetfield = 203, + _fast_bgetfield = 204, + _fast_cgetfield = 205, + _fast_dgetfield = 206, + _fast_fgetfield = 207, + _fast_igetfield = 208, + _fast_lgetfield = 209, + _fast_sgetfield = 210, + _fast_aputfield = 211, + _fast_bputfield = 212, + _fast_zputfield = 213, + _fast_cputfield = 214, + _fast_dputfield = 215, + _fast_fputfield = 216, + _fast_iputfield = 217, + _fast_lputfield = 218, + _fast_sputfield = 219, + _fast_aload_0 = 220, + _fast_iaccess_0 = 221, + _fast_aaccess_0 = 222, + _fast_faccess_0 = 223, + _fast_iload = 224, + _fast_iload2 = 225, + _fast_icaload = 226, + _fast_invokevfinal = 227, + _fast_linearswitch = 228, + _fast_binaryswitch = 229, + _fast_aldc = 230, + _fast_aldc_w = 231, + _return_register_finalizer = 232, + _invokehandle = 233, + _nofast_getfield = 234, + _nofast_putfield = 235, + _nofast_aload_0 = 236, + _nofast_iload = 237, + _shouldnotreachhere = 238, + number_of_codes = 239; + + static int code_or_bp_at(byte[] code, int bci) { + return code[bci] & 0xff; + } + + static boolean is_valid(int code) { + return 0 <= code && code < number_of_codes; + } + + static int wide_length_for(int code) { + return is_valid(code) ? _lengths[code] >> 4 : -1; + } + + static boolean is_store_into_local(int code) { + return (Classfile.ISTORE <= code && code <= Classfile.ASTORE_3); + } + + static final int _lengths[] = new int[number_of_codes]; + + static int special_length_at(int code, byte bytecode[], int bci, int end) { + switch (code) { + case Classfile.WIDE: + if (bci + 1 >= end) { + return -1; + } + return wide_length_for(bytecode[bci + 1] & 0xff); + case Classfile.TABLESWITCH: + int aligned_bci = align(bci + 1); + if (aligned_bci + 3 * 4 >= end) { + return -1; + } + ByteBuffer bb = ByteBuffer.wrap(bytecode, aligned_bci + 1 * 4, 2 * 4); + int lo = bb.getInt(); + int hi = bb.getInt(); + int len = aligned_bci - bci + (3 + hi - lo + 1) * 4; + return len > 0 ? len : -1; + case Classfile.LOOKUPSWITCH: + case _fast_binaryswitch: + case _fast_linearswitch: + aligned_bci = align(bci + 1); + if (aligned_bci + 2 * 4 >= end) { + return -1; + } + int npairs = ByteBuffer.wrap(bytecode, aligned_bci + 4, 4).getInt(); + len = aligned_bci - bci + (2 + 2 * npairs) * 4; + return len > 0 ? len : -1; + default: + return 0; + } + } + + static int align(int n) { + return (n + 3) & ~3; + } + + static void def(int code, String name, String format, String wide_format, BasicType result_type, int depth) { + def(code, name, format, wide_format, result_type, depth, code); + } + + static void def(int code, String name, String format, String wide_format, BasicType result_type, int depth, int java_code) { + if (wide_format != null && format == null) throw new IllegalArgumentException("short form must exist if there's a wide form"); + int len = format != null ? format.length() : 0; + int wlen = wide_format != null ? wide_format.length() : 0; + _lengths[code] = (wlen << 4) | (len & 0xf); + } + + static { + def(Classfile.NOP, "nop", "b", null, T_VOID, 0); + def(Classfile.ACONST_NULL, "aconst_null", "b", null, T_OBJECT, 1); + def(Classfile.ICONST_M1, "iconst_m1", "b", null, T_INT, 1); + def(Classfile.ICONST_0, "iconst_0", "b", null, T_INT, 1); + def(Classfile.ICONST_1, "iconst_1", "b", null, T_INT, 1); + def(Classfile.ICONST_2, "iconst_2", "b", null, T_INT, 1); + def(Classfile.ICONST_3, "iconst_3", "b", null, T_INT, 1); + def(Classfile.ICONST_4, "iconst_4", "b", null, T_INT, 1); + def(Classfile.ICONST_5, "iconst_5", "b", null, T_INT, 1); + def(Classfile.LCONST_0, "lconst_0", "b", null, T_LONG, 2); + def(Classfile.LCONST_1, "lconst_1", "b", null, T_LONG, 2); + def(Classfile.FCONST_0, "fconst_0", "b", null, T_FLOAT, 1); + def(Classfile.FCONST_1, "fconst_1", "b", null, T_FLOAT, 1); + def(Classfile.FCONST_2, "fconst_2", "b", null, T_FLOAT, 1); + def(Classfile.DCONST_0, "dconst_0", "b", null, T_DOUBLE, 2); + def(Classfile.DCONST_1, "dconst_1", "b", null, T_DOUBLE, 2); + def(Classfile.BIPUSH, "bipush", "bc", null, T_INT, 1); + def(Classfile.SIPUSH, "sipush", "bcc", null, T_INT, 1); + def(Classfile.LDC, "ldc", "bk", null, T_ILLEGAL, 1); + def(Classfile.LDC_W, "ldc_w", "bkk", null, T_ILLEGAL, 1); + def(Classfile.LDC2_W, "ldc2_w", "bkk", null, T_ILLEGAL, 2); + def(Classfile.ILOAD, "iload", "bi", "wbii", T_INT, 1); + def(Classfile.LLOAD, "lload", "bi", "wbii", T_LONG, 2); + def(Classfile.FLOAD, "fload", "bi", "wbii", T_FLOAT, 1); + def(Classfile.DLOAD, "dload", "bi", "wbii", T_DOUBLE, 2); + def(Classfile.ALOAD, "aload", "bi", "wbii", T_OBJECT, 1); + def(Classfile.ILOAD_0, "iload_0", "b", null, T_INT, 1); + def(Classfile.ILOAD_1, "iload_1", "b", null, T_INT, 1); + def(Classfile.ILOAD_2, "iload_2", "b", null, T_INT, 1); + def(Classfile.ILOAD_3, "iload_3", "b", null, T_INT, 1); + def(Classfile.LLOAD_0, "lload_0", "b", null, T_LONG, 2); + def(Classfile.LLOAD_1, "lload_1", "b", null, T_LONG, 2); + def(Classfile.LLOAD_2, "lload_2", "b", null, T_LONG, 2); + def(Classfile.LLOAD_3, "lload_3", "b", null, T_LONG, 2); + def(Classfile.FLOAD_0, "fload_0", "b", null, T_FLOAT, 1); + def(Classfile.FLOAD_1, "fload_1", "b", null, T_FLOAT, 1); + def(Classfile.FLOAD_2, "fload_2", "b", null, T_FLOAT, 1); + def(Classfile.FLOAD_3, "fload_3", "b", null, T_FLOAT, 1); + def(Classfile.DLOAD_0, "dload_0", "b", null, T_DOUBLE, 2); + def(Classfile.DLOAD_1, "dload_1", "b", null, T_DOUBLE, 2); + def(Classfile.DLOAD_2, "dload_2", "b", null, T_DOUBLE, 2); + def(Classfile.DLOAD_3, "dload_3", "b", null, T_DOUBLE, 2); + def(Classfile.ALOAD_0, "aload_0", "b", null, T_OBJECT, 1); + def(Classfile.ALOAD_1, "aload_1", "b", null, T_OBJECT, 1); + def(Classfile.ALOAD_2, "aload_2", "b", null, T_OBJECT, 1); + def(Classfile.ALOAD_3, "aload_3", "b", null, T_OBJECT, 1); + def(Classfile.IALOAD, "iaload", "b", null, T_INT, -1); + def(Classfile.LALOAD, "laload", "b", null, T_LONG, 0); + def(Classfile.FALOAD, "faload", "b", null, T_FLOAT, -1); + def(Classfile.DALOAD, "daload", "b", null, T_DOUBLE, 0); + def(Classfile.AALOAD, "aaload", "b", null, T_OBJECT, -1); + def(Classfile.BALOAD, "baload", "b", null, T_INT, -1); + def(Classfile.CALOAD, "caload", "b", null, T_INT, -1); + def(Classfile.SALOAD, "saload", "b", null, T_INT, -1); + def(Classfile.ISTORE, "istore", "bi", "wbii", T_VOID, -1); + def(Classfile.LSTORE, "lstore", "bi", "wbii", T_VOID, -2); + def(Classfile.FSTORE, "fstore", "bi", "wbii", T_VOID, -1); + def(Classfile.DSTORE, "dstore", "bi", "wbii", T_VOID, -2); + def(Classfile.ASTORE, "astore", "bi", "wbii", T_VOID, -1); + def(Classfile.ISTORE_0, "istore_0", "b", null, T_VOID, -1); + def(Classfile.ISTORE_1, "istore_1", "b", null, T_VOID, -1); + def(Classfile.ISTORE_2, "istore_2", "b", null, T_VOID, -1); + def(Classfile.ISTORE_3, "istore_3", "b", null, T_VOID, -1); + def(Classfile.LSTORE_0, "lstore_0", "b", null, T_VOID, -2); + def(Classfile.LSTORE_1, "lstore_1", "b", null, T_VOID, -2); + def(Classfile.LSTORE_2, "lstore_2", "b", null, T_VOID, -2); + def(Classfile.LSTORE_3, "lstore_3", "b", null, T_VOID, -2); + def(Classfile.FSTORE_0, "fstore_0", "b", null, T_VOID, -1); + def(Classfile.FSTORE_1, "fstore_1", "b", null, T_VOID, -1); + def(Classfile.FSTORE_2, "fstore_2", "b", null, T_VOID, -1); + def(Classfile.FSTORE_3, "fstore_3", "b", null, T_VOID, -1); + def(Classfile.DSTORE_0, "dstore_0", "b", null, T_VOID, -2); + def(Classfile.DSTORE_1, "dstore_1", "b", null, T_VOID, -2); + def(Classfile.DSTORE_2, "dstore_2", "b", null, T_VOID, -2); + def(Classfile.DSTORE_3, "dstore_3", "b", null, T_VOID, -2); + def(Classfile.ASTORE_0, "astore_0", "b", null, T_VOID, -1); + def(Classfile.ASTORE_1, "astore_1", "b", null, T_VOID, -1); + def(Classfile.ASTORE_2, "astore_2", "b", null, T_VOID, -1); + def(Classfile.ASTORE_3, "astore_3", "b", null, T_VOID, -1); + def(Classfile.IASTORE, "iastore", "b", null, T_VOID, -3); + def(Classfile.LASTORE, "lastore", "b", null, T_VOID, -4); + def(Classfile.FASTORE, "fastore", "b", null, T_VOID, -3); + def(Classfile.DASTORE, "dastore", "b", null, T_VOID, -4); + def(Classfile.AASTORE, "aastore", "b", null, T_VOID, -3); + def(Classfile.BASTORE, "bastore", "b", null, T_VOID, -3); + def(Classfile.CASTORE, "castore", "b", null, T_VOID, -3); + def(Classfile.SASTORE, "sastore", "b", null, T_VOID, -3); + def(Classfile.POP, "pop", "b", null, T_VOID, -1); + def(Classfile.POP2, "pop2", "b", null, T_VOID, -2); + def(Classfile.DUP, "dup", "b", null, T_VOID, 1); + def(Classfile.DUP_X1, "dup_x1", "b", null, T_VOID, 1); + def(Classfile.DUP_X2, "dup_x2", "b", null, T_VOID, 1); + def(Classfile.DUP2, "dup2", "b", null, T_VOID, 2); + def(Classfile.DUP2_X1, "dup2_x1", "b", null, T_VOID, 2); + def(Classfile.DUP2_X2, "dup2_x2", "b", null, T_VOID, 2); + def(Classfile.SWAP, "swap", "b", null, T_VOID, 0); + def(Classfile.IADD, "iadd", "b", null, T_INT, -1); + def(Classfile.LADD, "ladd", "b", null, T_LONG, -2); + def(Classfile.FADD, "fadd", "b", null, T_FLOAT, -1); + def(Classfile.DADD, "dadd", "b", null, T_DOUBLE, -2); + def(Classfile.ISUB, "isub", "b", null, T_INT, -1); + def(Classfile.LSUB, "lsub", "b", null, T_LONG, -2); + def(Classfile.FSUB, "fsub", "b", null, T_FLOAT, -1); + def(Classfile.DSUB, "dsub", "b", null, T_DOUBLE, -2); + def(Classfile.IMUL, "imul", "b", null, T_INT, -1); + def(Classfile.LMUL, "lmul", "b", null, T_LONG, -2); + def(Classfile.FMUL, "fmul", "b", null, T_FLOAT, -1); + def(Classfile.DMUL, "dmul", "b", null, T_DOUBLE, -2); + def(Classfile.IDIV, "idiv", "b", null, T_INT, -1); + def(Classfile.LDIV, "ldiv", "b", null, T_LONG, -2); + def(Classfile.FDIV, "fdiv", "b", null, T_FLOAT, -1); + def(Classfile.DDIV, "ddiv", "b", null, T_DOUBLE, -2); + def(Classfile.IREM, "irem", "b", null, T_INT, -1); + def(Classfile.LREM, "lrem", "b", null, T_LONG, -2); + def(Classfile.FREM, "frem", "b", null, T_FLOAT, -1); + def(Classfile.DREM, "drem", "b", null, T_DOUBLE, -2); + def(Classfile.INEG, "ineg", "b", null, T_INT, 0); + def(Classfile.LNEG, "lneg", "b", null, T_LONG, 0); + def(Classfile.FNEG, "fneg", "b", null, T_FLOAT, 0); + def(Classfile.DNEG, "dneg", "b", null, T_DOUBLE, 0); + def(Classfile.ISHL, "ishl", "b", null, T_INT, -1); + def(Classfile.LSHL, "lshl", "b", null, T_LONG, -1); + def(Classfile.ISHR, "ishr", "b", null, T_INT, -1); + def(Classfile.LSHR, "lshr", "b", null, T_LONG, -1); + def(Classfile.IUSHR, "iushr", "b", null, T_INT, -1); + def(Classfile.LUSHR, "lushr", "b", null, T_LONG, -1); + def(Classfile.IAND, "iand", "b", null, T_INT, -1); + def(Classfile.LAND, "land", "b", null, T_LONG, -2); + def(Classfile.IOR, "ior", "b", null, T_INT, -1); + def(Classfile.LOR, "lor", "b", null, T_LONG, -2); + def(Classfile.IXOR, "ixor", "b", null, T_INT, -1); + def(Classfile.LXOR, "lxor", "b", null, T_LONG, -2); + def(Classfile.IINC, "iinc", "bic", "wbiicc", T_VOID, 0); + def(Classfile.I2L, "i2l", "b", null, T_LONG, 1); + def(Classfile.I2F, "i2f", "b", null, T_FLOAT, 0); + def(Classfile.I2D, "i2d", "b", null, T_DOUBLE, 1); + def(Classfile.L2I, "l2i", "b", null, T_INT, -1); + def(Classfile.L2F, "l2f", "b", null, T_FLOAT, -1); + def(Classfile.L2D, "l2d", "b", null, T_DOUBLE, 0); + def(Classfile.F2I, "f2i", "b", null, T_INT, 0); + def(Classfile.F2L, "f2l", "b", null, T_LONG, 1); + def(Classfile.F2D, "f2d", "b", null, T_DOUBLE, 1); + def(Classfile.D2I, "d2i", "b", null, T_INT, -1); + def(Classfile.D2L, "d2l", "b", null, T_LONG, 0); + def(Classfile.D2F, "d2f", "b", null, T_FLOAT, -1); + def(Classfile.I2B, "i2b", "b", null, T_BYTE, 0); + def(Classfile.I2C, "i2c", "b", null, T_CHAR, 0); + def(Classfile.I2S, "i2s", "b", null, T_SHORT, 0); + def(Classfile.LCMP, "lcmp", "b", null, T_VOID, -3); + def(Classfile.FCMPL, "fcmpl", "b", null, T_VOID, -1); + def(Classfile.FCMPG, "fcmpg", "b", null, T_VOID, -1); + def(Classfile.DCMPL, "dcmpl", "b", null, T_VOID, -3); + def(Classfile.DCMPG, "dcmpg", "b", null, T_VOID, -3); + def(Classfile.IFEQ, "ifeq", "boo", null, T_VOID, -1); + def(Classfile.IFNE, "ifne", "boo", null, T_VOID, -1); + def(Classfile.IFLT, "iflt", "boo", null, T_VOID, -1); + def(Classfile.IFGE, "ifge", "boo", null, T_VOID, -1); + def(Classfile.IFGT, "ifgt", "boo", null, T_VOID, -1); + def(Classfile.IFLE, "ifle", "boo", null, T_VOID, -1); + def(Classfile.IF_ICMPEQ, "if_icmpeq", "boo", null, T_VOID, -2); + def(Classfile.IF_ICMPNE, "if_icmpne", "boo", null, T_VOID, -2); + def(Classfile.IF_ICMPLT, "if_icmplt", "boo", null, T_VOID, -2); + def(Classfile.IF_ICMPGE, "if_icmpge", "boo", null, T_VOID, -2); + def(Classfile.IF_ICMPGT, "if_icmpgt", "boo", null, T_VOID, -2); + def(Classfile.IF_ICMPLE, "if_icmple", "boo", null, T_VOID, -2); + def(Classfile.IF_ACMPEQ, "if_acmpeq", "boo", null, T_VOID, -2); + def(Classfile.IF_ACMPNE, "if_acmpne", "boo", null, T_VOID, -2); + def(Classfile.GOTO, "goto", "boo", null, T_VOID, 0); + def(Classfile.JSR, "jsr", "boo", null, T_INT, 0); + def(Classfile.RET, "ret", "bi", "wbii", T_VOID, 0); + def(Classfile.TABLESWITCH, "tableswitch", "", null, T_VOID, -1); // may have backward branches + def(Classfile.LOOKUPSWITCH, "lookupswitch", "", null, T_VOID, -1); // rewriting in interpreter + def(Classfile.IRETURN, "ireturn", "b", null, T_INT, -1); + def(Classfile.LRETURN, "lreturn", "b", null, T_LONG, -2); + def(Classfile.FRETURN, "freturn", "b", null, T_FLOAT, -1); + def(Classfile.DRETURN, "dreturn", "b", null, T_DOUBLE, -2); + def(Classfile.ARETURN, "areturn", "b", null, T_OBJECT, -1); + def(Classfile.RETURN, "return", "b", null, T_VOID, 0); + def(Classfile.GETSTATIC, "getstatic", "bJJ", null, T_ILLEGAL, 1); + def(Classfile.PUTSTATIC, "putstatic", "bJJ", null, T_ILLEGAL, -1); + def(Classfile.GETFIELD, "getfield", "bJJ", null, T_ILLEGAL, 0); + def(Classfile.PUTFIELD, "putfield", "bJJ", null, T_ILLEGAL, -2); + def(Classfile.INVOKEVIRTUAL, "invokevirtual", "bJJ", null, T_ILLEGAL, -1); + def(Classfile.INVOKESPECIAL, "invokespecial", "bJJ", null, T_ILLEGAL, -1); + def(Classfile.INVOKESTATIC, "invokestatic", "bJJ", null, T_ILLEGAL, 0); + def(Classfile.INVOKEINTERFACE, "invokeinterface", "bJJ__", null, T_ILLEGAL, -1); + def(Classfile.INVOKEDYNAMIC, "invokedynamic", "bJJJJ", null, T_ILLEGAL, 0); + def(Classfile.NEW, "new", "bkk", null, T_OBJECT, 1); + def(Classfile.NEWARRAY, "newarray", "bc", null, T_OBJECT, 0); + def(Classfile.ANEWARRAY, "anewarray", "bkk", null, T_OBJECT, 0); + def(Classfile.ARRAYLENGTH, "arraylength", "b", null, T_VOID, 0); + def(Classfile.ATHROW, "athrow", "b", null, T_VOID, -1); + def(Classfile.CHECKCAST, "checkcast", "bkk", null, T_OBJECT, 0); + def(Classfile.INSTANCEOF, "instanceof", "bkk", null, T_INT, 0); + def(Classfile.MONITORENTER, "monitorenter", "b", null, T_VOID, -1); + def(Classfile.MONITOREXIT, "monitorexit", "b", null, T_VOID, -1); + def(Classfile.WIDE, "wide", "", null, T_VOID, 0); + def(Classfile.MULTIANEWARRAY, "multianewarray", "bkkc", null, T_OBJECT, 1); + def(Classfile.IFNULL, "ifnull", "boo", null, T_VOID, -1); + def(Classfile.IFNONNULL, "ifnonnull", "boo", null, T_VOID, -1); + def(Classfile.GOTO_W, "goto_w", "boooo", null, T_VOID, 0); + def(Classfile.JSR_W, "jsr_w", "boooo", null, T_INT, 0); + def(_breakpoint, "breakpoint", "", null, T_VOID, 0); + def(_fast_agetfield, "fast_agetfield", "bJJ", null, T_OBJECT, 0, Classfile.GETFIELD); + def(_fast_bgetfield, "fast_bgetfield", "bJJ", null, T_INT, 0, Classfile.GETFIELD); + def(_fast_cgetfield, "fast_cgetfield", "bJJ", null, T_CHAR, 0, Classfile.GETFIELD); + def(_fast_dgetfield, "fast_dgetfield", "bJJ", null, T_DOUBLE, 0, Classfile.GETFIELD); + def(_fast_fgetfield, "fast_fgetfield", "bJJ", null, T_FLOAT, 0, Classfile.GETFIELD); + def(_fast_igetfield, "fast_igetfield", "bJJ", null, T_INT, 0, Classfile.GETFIELD); + def(_fast_lgetfield, "fast_lgetfield", "bJJ", null, T_LONG, 0, Classfile.GETFIELD); + def(_fast_sgetfield, "fast_sgetfield", "bJJ", null, T_SHORT, 0, Classfile.GETFIELD); + def(_fast_aputfield, "fast_aputfield", "bJJ", null, T_OBJECT, 0, Classfile.PUTFIELD); + def(_fast_bputfield, "fast_bputfield", "bJJ", null, T_INT, 0, Classfile.PUTFIELD); + def(_fast_zputfield, "fast_zputfield", "bJJ", null, T_INT, 0, Classfile.PUTFIELD); + def(_fast_cputfield, "fast_cputfield", "bJJ", null, T_CHAR, 0, Classfile.PUTFIELD); + def(_fast_dputfield, "fast_dputfield", "bJJ", null, T_DOUBLE, 0, Classfile.PUTFIELD); + def(_fast_fputfield, "fast_fputfield", "bJJ", null, T_FLOAT, 0, Classfile.PUTFIELD); + def(_fast_iputfield, "fast_iputfield", "bJJ", null, T_INT, 0, Classfile.PUTFIELD); + def(_fast_lputfield, "fast_lputfield", "bJJ", null, T_LONG, 0, Classfile.PUTFIELD); + def(_fast_sputfield, "fast_sputfield", "bJJ", null, T_SHORT, 0, Classfile.PUTFIELD); + def(_fast_aload_0, "fast_aload_0", "b", null, T_OBJECT, 1, Classfile.ALOAD_0); + def(_fast_iaccess_0, "fast_iaccess_0", "b_JJ", null, T_INT, 1, Classfile.ALOAD_0); + def(_fast_aaccess_0, "fast_aaccess_0", "b_JJ", null, T_OBJECT, 1, Classfile.ALOAD_0); + def(_fast_faccess_0, "fast_faccess_0", "b_JJ", null, T_OBJECT, 1, Classfile.ALOAD_0); + def(_fast_iload, "fast_iload", "bi", null, T_INT, 1, Classfile.ILOAD); + def(_fast_iload2, "fast_iload2", "bi_i", null, T_INT, 2, Classfile.ILOAD); + def(_fast_icaload, "fast_icaload", "bi_", null, T_INT, 0, Classfile.ILOAD); + def(_fast_invokevfinal, "fast_invokevfinal", "bJJ", null, T_ILLEGAL, -1, Classfile.INVOKEVIRTUAL); + def(_fast_linearswitch, "fast_linearswitch", "", null, T_VOID, -1, Classfile.LOOKUPSWITCH); + def(_fast_binaryswitch, "fast_binaryswitch", "", null, T_VOID, -1, Classfile.LOOKUPSWITCH); + def(_return_register_finalizer, "return_register_finalizer", "b", null, T_VOID, 0, Classfile.RETURN); + def(_invokehandle, "invokehandle", "bJJ", null, T_ILLEGAL, -1, Classfile.INVOKEVIRTUAL); + def(_fast_aldc, "fast_aldc", "bj", null, T_OBJECT, 1, Classfile.LDC); + def(_fast_aldc_w, "fast_aldc_w", "bJJ", null, T_OBJECT, 1, Classfile.LDC_W); + def(_nofast_getfield, "nofast_getfield", "bJJ", null, T_ILLEGAL, 0, Classfile.GETFIELD); + def(_nofast_putfield, "nofast PUTFIELD", "bJJ", null, T_ILLEGAL, -2, Classfile.PUTFIELD); + def(_nofast_aload_0, "nofast_aload_0", "b", null, T_ILLEGAL, 1, Classfile.ALOAD_0); + def(_nofast_iload, "nofast_iload", "bi", null, T_ILLEGAL, 1, Classfile.ILOAD); + def(_shouldnotreachhere, "_shouldnotreachhere", "b", null, T_VOID, 0); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java new file mode 100644 index 0000000000000..13aac2c92c68d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl.verifier; + +import java.util.Arrays; + +/** + * @see hotspot/share/classfile/stackMapFrame.hpp + * @see hotspot/share/classfile/stackMapFrame.cpp + */ +class VerificationFrame { + + public static final int FLAG_THIS_UNINIT = 0x01; + + private int _offset; + private int _locals_size, _stack_size; + private int _stack_mark; + private final int _max_locals, _max_stack; + private int _flags; + private final VerificationType[] _locals, _stack; + private final VerifierImpl _verifier; + + public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, VerificationType[] locals, VerificationType[] stack, VerifierImpl v) { + this._offset = offset; + this._locals_size = locals_size; + this._stack_size = stack_size; + this._stack_mark = -1; + this._max_locals = max_locals; + this._max_stack = max_stack; + this._flags = flags; + this._locals = locals; + this._stack = stack; + this._verifier = v; + } + + @Override + public String toString() { + return "frame @" + _offset + " with locals " + (_locals == null ? "[]" : Arrays.asList(_locals)) + " and stack " + (_stack == null ? "[]" : Arrays.asList(_stack)); + } + + void set_offset(int offset) { + this._offset = offset; + } + + void set_flags(int flags) { + _flags = flags; + } + + void set_locals_size(int locals_size) { + _locals_size = locals_size; + } + + void set_stack_size(int stack_size) { + _stack_size = _stack_mark = stack_size; + } + + int offset() { + return _offset; + } + + VerifierImpl verifier() { + return _verifier; + } + + int flags() { + return _flags; + } + + int locals_size() { + return _locals_size; + } + + VerificationType[] locals() { + return _locals; + } + + int stack_size() { + return _stack_size; + } + + VerificationType[] stack() { + return _stack; + } + + int max_locals() { + return _max_locals; + } + + boolean flag_this_uninit() { + return (_flags & FLAG_THIS_UNINIT) == FLAG_THIS_UNINIT; + } + + void reset() { + for (int i = 0; i < _max_locals; i++) { + _locals[i] = VerificationType.bogus_type; + } + for (int i = 0; i < _max_stack; i++) { + _stack[i] = VerificationType.bogus_type; + } + } + + void set_mark() { + if (_stack_mark != -1) { + for (int i = _stack_mark - 1; i >= _stack_size; --i) { + _stack[i] = VerificationType.bogus_type; + } + _stack_mark = _stack_size; + } + } + + void push_stack(VerificationType type) { + if (type.is_check()) _verifier.verifyError("Must be a real type"); + if (_stack_size >= _max_stack) { + _verifier.verifyError("Operand stack overflow"); + } + _stack[_stack_size++] = type; + } + + void push_stack_2(VerificationType type1, VerificationType type2) { + if (!(type1.is_long() || type1.is_double())) _verifier.verifyError("must be long/double"); + if (!(type2.is_long2() || type2.is_double2())) _verifier.verifyError("must be long/double_2"); + if (_stack_size >= _max_stack - 1) { + _verifier.verifyError("Operand stack overflow"); + } + _stack[_stack_size++] = type1; + _stack[_stack_size++] = type2; + } + + VerificationType pop_stack() { + if (_stack_size <= 0) { + _verifier.verifyError("Operand stack underflow"); + } + return _stack[--_stack_size]; + } + + VerificationType pop_stack(VerificationType type) { + if (_stack_size != 0) { + VerificationType top = _stack[_stack_size - 1]; + boolean subtype = type.is_assignable_from(top, verifier()); + if (subtype) { + --_stack_size; + return top; + } + } + return pop_stack_ex(type); + } + + void pop_stack_2(VerificationType type1, VerificationType type2) { + if (!(type1.is_long2() || type1.is_double2())) _verifier.verifyError("must be long/double"); + if (!(type2.is_long() || type2.is_double())) _verifier.verifyError("must be long/double_2"); + if (_stack_size >= 2) { + VerificationType top1 = _stack[_stack_size - 1]; + boolean subtype1 = type1.is_assignable_from(top1, verifier()); + VerificationType top2 = _stack[_stack_size - 2]; + boolean subtype2 = type2.is_assignable_from(top2, verifier()); + if (subtype1 && subtype2) { + _stack_size -= 2; + return; + } + } + pop_stack_ex(type1); + pop_stack_ex(type2); + } + + VerificationFrame(int max_locals, int max_stack, VerifierImpl verifier) { + _offset = 0; + _locals_size = 0; + _stack_size = 0; + _stack_mark = 0; + _max_locals = max_locals; + _max_stack = max_stack; + _flags = 0; + _verifier = verifier; + _locals = new VerificationType[max_locals]; + _stack = new VerificationType[max_stack]; + for (int i = 0; i < max_locals; i++) { + _locals[i] = VerificationType.bogus_type; + } + for (int i = 0; i < max_stack; i++) { + _stack[i] = VerificationType.bogus_type; + } + } + + VerificationFrame frame_in_exception_handler(int flags) { + return new VerificationFrame(_offset, flags, _locals_size, 0, + _max_locals, _max_stack, _locals, new VerificationType[1], + _verifier); + } + + void initialize_object(VerificationType old_object, VerificationType new_object) { + int i; + for (i = 0; i < _max_locals; i++) { + if (_locals[i].equals(old_object)) { + _locals[i] = new_object; + } + } + for (i = 0; i < _stack_size; i++) { + if (_stack[i].equals(old_object)) { + _stack[i] = new_object; + } + } + if (old_object.is_uninitialized_this(_verifier)) { + _flags = 0; + } + } + + VerificationType set_locals_from_arg(VerificationWrapper.MethodWrapper m, VerificationType thisKlass) { + var ss = new VerificationSignature(m.descriptor(), true, _verifier); + int init_local_num = 0; + if (!m.isStatic()) { + init_local_num++; + if (VerifierImpl.object_initializer_name.equals(m.name()) && !VerifierImpl.java_lang_Object.equals(thisKlass.name())) { + _locals[0] = VerificationType.uninitialized_this_type; + _flags |= FLAG_THIS_UNINIT; + } else { + _locals[0] = thisKlass; + } + } + while (!ss.atReturnType()) { + init_local_num += _verifier.change_sig_to_verificationType(ss, _locals, init_local_num); + ss.next(); + } + _locals_size = init_local_num; + switch (ss.type()) { + case T_OBJECT: + case T_ARRAY: + { + String sig = ss.asSymbol(); + return VerificationType.reference_type(sig); + } + case T_INT: return VerificationType.integer_type; + case T_BYTE: return VerificationType.byte_type; + case T_CHAR: return VerificationType.char_type; + case T_SHORT: return VerificationType.short_type; + case T_BOOLEAN: return VerificationType.boolean_type; + case T_FLOAT: return VerificationType.float_type; + case T_DOUBLE: return VerificationType.double_type; + case T_LONG: return VerificationType.long_type; + case T_VOID: return VerificationType.bogus_type; + default: + _verifier.verifyError("Should not reach here"); + return VerificationType.bogus_type; + } + } + + void copy_locals(VerificationFrame src) { + int len = src.locals_size() < _locals_size ? src.locals_size() : _locals_size; + if (len > 0) System.arraycopy(src.locals(), 0, _locals, 0, len); + } + + void copy_stack(VerificationFrame src) { + int len = src.stack_size() < _stack_size ? src.stack_size() : _stack_size; + if (len > 0) System.arraycopy(src.stack(), 0, _stack, 0, len); + } + + private int is_assignable_to(VerificationType[] from, VerificationType[] to, int len) { + int i = 0; + for (; i < len; i++) { + if (!to[i].is_assignable_from(from[i], verifier())) { + break; + } + } + return i; + } + + boolean is_assignable_to(VerificationFrame target) { + if (_max_locals != target.max_locals()) { + _verifier.verifyError("Locals size mismatch", this, target); + } + if (_stack_size != target.stack_size()) { + _verifier.verifyError("Stack size mismatch", this, target); + } + int mismatch_loc; + mismatch_loc = is_assignable_to(_locals, target.locals(), target.locals_size()); + if (mismatch_loc != target.locals_size()) { + _verifier.verifyError("Bad type", this, target); + } + mismatch_loc = is_assignable_to(_stack, target.stack(), _stack_size); + if (mismatch_loc != _stack_size) { + _verifier.verifyError("Bad type", this, target); + } + + if ((_flags | target.flags()) == target.flags()) { + return true; + } else { + _verifier.verifyError("Bad flags", this, target); + } + return false; + } + + VerificationType pop_stack_ex(VerificationType type) { + if (_stack_size <= 0) { + _verifier.verifyError("Operand stack underflow"); + } + VerificationType top = _stack[--_stack_size]; + boolean subtype = type.is_assignable_from(top, verifier()); + if (!subtype) { + _verifier.verifyError("Bad type on operand stack"); + } + return top; + } + + VerificationType get_local(int index, VerificationType type) { + if (index >= _max_locals) { + _verifier.verifyError("Local variable table overflow"); + } + boolean subtype = type.is_assignable_from(_locals[index], + verifier()); + if (!subtype) { + _verifier.verifyError("Bad local variable type"); + } + if(index >= _locals_size) { _locals_size = index + 1; } + return _locals[index]; + } + + void get_local_2(int index, VerificationType type1, VerificationType type2) { + if (!(type1.is_long() || type1.is_double())) _verifier.verifyError("must be long/double"); + if (!(type2.is_long2() || type2.is_double2())) _verifier.verifyError("must be long/double_2"); + if (index >= _locals_size - 1) { + _verifier.verifyError("get long/double overflows locals"); + } + boolean subtype = type1.is_assignable_from(_locals[index], verifier()); + if (!subtype) { + _verifier.verifyError("Bad local variable type"); + } else { + subtype = type2.is_assignable_from(_locals[index + 1], verifier()); + if (!subtype) { + _verifier.verifyError("Bad local variable type"); + } + } + } + + void set_local(int index, VerificationType type) { + if (type.is_check()) _verifier.verifyError("Must be a real type"); + if (index >= _max_locals) { + _verifier.verifyError("Local variable table overflow"); + } + if (_locals[index].is_double() || _locals[index].is_long()) { + if ((index + 1) >= _locals_size) _verifier.verifyError("Local variable table overflow"); + _locals[index + 1] = VerificationType.bogus_type; + } + if (_locals[index].is_double2() || _locals[index].is_long2()) { + if (index < 1) _verifier.verifyError("Local variable table underflow"); + _locals[index - 1] = VerificationType.bogus_type; + } + _locals[index] = type; + if (index >= _locals_size) { + for (int i=_locals_size; i= _max_locals - 1) { + _verifier.verifyError("Local variable table overflow"); + } + if (_locals[index+1].is_double() || _locals[index+1].is_long()) { + if ((index + 2) >= _locals_size) _verifier.verifyError("Local variable table overflow"); + _locals[index + 2] = VerificationType.bogus_type; + } + if (_locals[index].is_double2() || _locals[index].is_long2()) { + if (index < 1) _verifier.verifyError("Local variable table underflow"); + _locals[index - 1] = VerificationType.bogus_type; + } + _locals[index] = type1; + _locals[index+1] = type2; + if (index >= _locals_size - 1) { + for (int i=_locals_size; i + T_BOOLEAN; + case JVM_SIGNATURE_CHAR -> + T_CHAR; + case JVM_SIGNATURE_FLOAT -> + T_FLOAT; + case JVM_SIGNATURE_DOUBLE -> + T_DOUBLE; + case JVM_SIGNATURE_BYTE -> + T_BYTE; + case JVM_SIGNATURE_SHORT -> + T_SHORT; + case JVM_SIGNATURE_INT -> + T_INT; + case JVM_SIGNATURE_LONG -> + T_LONG; + case JVM_SIGNATURE_CLASS -> + T_OBJECT; + case JVM_SIGNATURE_ARRAY -> + T_ARRAY; + case JVM_SIGNATURE_VOID -> + T_VOID; + default -> + throw new IllegalArgumentException("Not a valid type: '" + ch + "'"); + }; + } + } + + static final char JVM_SIGNATURE_DOT = '.', + JVM_SIGNATURE_ARRAY = '[', + JVM_SIGNATURE_BYTE = 'B', + JVM_SIGNATURE_CHAR = 'C', + JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_ENDCLASS = ';', + JVM_SIGNATURE_FLOAT = 'F', + JVM_SIGNATURE_DOUBLE = 'D', + JVM_SIGNATURE_FUNC = '(', + JVM_SIGNATURE_ENDFUNC = ')', + JVM_SIGNATURE_INT = 'I', + JVM_SIGNATURE_LONG = 'J', + JVM_SIGNATURE_SHORT = 'S', + JVM_SIGNATURE_VOID = 'V', + JVM_SIGNATURE_BOOLEAN = 'Z'; + + static boolean isReferenceType(BasicType t) { + return t == BasicType.T_OBJECT || t == BasicType.T_ARRAY; + } + + private static final char TYPE2CHAR_TAB[] = new char[]{ + 0, 0, 0, 0, + JVM_SIGNATURE_BOOLEAN, JVM_SIGNATURE_CHAR, + JVM_SIGNATURE_FLOAT, JVM_SIGNATURE_DOUBLE, + JVM_SIGNATURE_BYTE, JVM_SIGNATURE_SHORT, + JVM_SIGNATURE_INT, JVM_SIGNATURE_LONG, + JVM_SIGNATURE_CLASS, JVM_SIGNATURE_ARRAY, + JVM_SIGNATURE_VOID, 0, + 0, 0, 0, 0 + }; + + static boolean hasEnvelope(char signature_char) { + return signature_char == JVM_SIGNATURE_CLASS; + } + + private BasicType type; + private final String signature; + private final int limit; + private int begin, end, arrayPrefix, state; + + private static final int S_FIELD = 0, S_METHOD = 1, S_METHOD_RETURN = 3; + + boolean atReturnType() { + return state == S_METHOD_RETURN; + } + + boolean isReference() { + return isReferenceType(type); + } + + BasicType type() { + return type; + } + + private int rawSymbolBegin() { + return begin + (hasEnvelope() ? 1 : 0); + } + + private int rawSymbolEnd() { + return end - (hasEnvelope() ? 1 : 0); + } + + private boolean hasEnvelope() { + return hasEnvelope(signature.charAt(begin)); + } + + String asSymbol() { + int begin = rawSymbolBegin(); + int end = rawSymbolEnd(); + return signature.substring(begin, end); + } + + int skipArrayPrefix(int max_skip_length) { + if (type != BasicType.T_ARRAY) { + return 0; + } + if (arrayPrefix > max_skip_length) { + // strip some but not all levels of T_ARRAY + arrayPrefix -= max_skip_length; + begin += max_skip_length; + return max_skip_length; + } + return skipWholeArrayPrefix(); + } + + static BasicType decodeSignatureChar(char ch) { + return BasicType.fromSignature(ch); + } + + private final VerifierImpl context; + + VerificationSignature(String signature, boolean is_method, VerifierImpl context) { + this.signature = signature; + this.limit = signature.length(); + int oz = is_method ? S_METHOD : S_FIELD; + this.state = oz; + this.begin = this.end = oz; + this.arrayPrefix = 0; + this.context = context; + next(); + } + + private int scanType(BasicType type) { + int e = end; + int tem; + switch (type) { + case T_OBJECT: + tem = signature.indexOf(JVM_SIGNATURE_ENDCLASS, e); + return tem < 0 ? limit : tem + 1; + case T_ARRAY: + while (e < limit && signature.charAt(e) == JVM_SIGNATURE_ARRAY) { + e++; + } + arrayPrefix = e - end; + if (hasEnvelope(signature.charAt(e))) { + tem = signature.indexOf(JVM_SIGNATURE_ENDCLASS, e); + return tem < 0 ? limit : tem + 1; + } + return e + 1; + default: + return e + 1; + } + } + + void next() { + final String sig = signature; + int len = limit; + testLen(len); + begin = end; + char ch = sig.charAt(begin); + if (ch == JVM_SIGNATURE_ENDFUNC) { + state = S_METHOD_RETURN; + begin = ++end; + testLen(len); + ch = sig.charAt(begin); + } + try { + BasicType bt = decodeSignatureChar(ch); + type = bt; + end = scanType(bt); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("Not a valid signature: '" + signature + "'", iae); + } + } + + private void testLen(int len) { + if (end >= len) { + if (context == null) { + throw new IllegalArgumentException("Invalid signature " + signature); + } else { + context.verifyError("Invalid signature " + signature); + } + } + } + + int skipWholeArrayPrefix() { + int whole_array_prefix = arrayPrefix; + int new_begin = begin + whole_array_prefix; + begin = new_begin; + char ch = signature.charAt(new_begin); + BasicType bt = decodeSignatureChar(ch); + type = bt; + return whole_array_prefix; + } + + @SuppressWarnings("fallthrough") + static int isValidType(String type, int limit) { + int index = 0; + + // Iterate over any number of array dimensions + while (index < limit && type.charAt(index) == JVM_SIGNATURE_ARRAY) { + ++index; + } + if (index >= limit) { + return -1; + } + switch (type.charAt(index)) { + case JVM_SIGNATURE_BYTE: + case JVM_SIGNATURE_CHAR: + case JVM_SIGNATURE_FLOAT: + case JVM_SIGNATURE_DOUBLE: + case JVM_SIGNATURE_INT: + case JVM_SIGNATURE_LONG: + case JVM_SIGNATURE_SHORT: + case JVM_SIGNATURE_BOOLEAN: + case JVM_SIGNATURE_VOID: + return index + 1; + case JVM_SIGNATURE_CLASS: + for (index = index + 1; index < limit; ++index) { + char c = type.charAt(index); + switch (c) { + case JVM_SIGNATURE_ENDCLASS: + return index + 1; + case '\0': + case JVM_SIGNATURE_DOT: + case JVM_SIGNATURE_ARRAY: + return -1; + default: ; // fall through + } + } + // fall through + default: ; // fall through + } + return -1; + } + + static boolean isValidMethodSignature(String method_sig) { + if (method_sig != null) { + int len = method_sig.length(); + int index = 0; + if (len > 1 && method_sig.charAt(index) == JVM_SIGNATURE_FUNC) { + ++index; + while (index < len && method_sig.charAt(index) != JVM_SIGNATURE_ENDFUNC) { + int res = isValidType(method_sig.substring(index), len - index); + if (res == -1) { + return false; + } else { + index += res; + } + } + if (index < len && method_sig.charAt(index) == JVM_SIGNATURE_ENDFUNC) { + // check the return type + ++index; + return (isValidType(method_sig.substring(index), len - index) == (len - index)); + } + } + } + return false; + } + + static boolean isValidTypeSignature(String sig) { + if (sig == null) return false; + int len = sig.length(); + return (len >= 1 && (isValidType(sig, len) == len)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java new file mode 100644 index 0000000000000..db07e0aa45b96 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl.verifier; + +import static jdk.internal.classfile.impl.verifier.VerificationType.*; + +/** + * @see hotspot/share/classfile/stackMapTable.hpp + * @see hotspot/share/classfile/stackMapTable.cpp + */ +class VerificationTable { + + private final int _code_length; + private final int _frame_count; + private final VerificationFrame[] _frame_array; + private final VerifierImpl _verifier; + + int get_frame_count() { + return _frame_count; + } + + int get_offset(int index) { + return _frame_array[index].offset(); + } + + static class StackMapStream { + + private final byte[] _data; + private int _index; + private final VerifierImpl _verifier; + + StackMapStream(byte[] ah, VerifierImpl context) { + _data = ah; + _index = 0; + _verifier = context; + } + + int get_u1() { + if (_data == null || _index >= _data.length) { + _verifier.classError("access beyond the end of attribute"); + } + return _data[_index++] & 0xff; + } + + int get_u2() { + int res = get_u1() << 8; + return res | get_u1(); + } + + boolean at_end() { + return (_data == null) || (_index == _data.length); + } + } + + VerificationTable(byte[] stackmap_data, VerificationFrame init_frame, int max_locals, int max_stack, byte[] code_data, int code_len, + VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl v) { + _verifier = v; + var reader = new StackMapReader(stackmap_data, code_data, code_len, cp, v); + _code_length = code_len; + _frame_count = reader.get_frame_count(); + _frame_array = new VerificationFrame[_frame_count]; + if (_frame_count > 0) { + VerificationFrame pre_frame = init_frame; + for (int i = 0; i < _frame_count; i++) { + VerificationFrame frame = reader.next(pre_frame, i == 0, max_locals, max_stack); + _frame_array[i] = frame; + int offset = frame.offset(); + if (offset >= code_len || code_data[offset] == 0) { + _verifier.verifyError("StackMapTable error: bad offset"); + } + pre_frame = frame; + } + } + reader.check_end(); + } + + int get_index_from_offset(int offset) { + int i = 0; + for (; i < _frame_count; i++) { + if (_frame_array[i].offset() == offset) { + return i; + } + } + return i; + } + + boolean match_stackmap(VerificationFrame frame, int target, boolean match, boolean update) { + int index = get_index_from_offset(target); + return match_stackmap(frame, target, index, match, update); + } + + boolean match_stackmap(VerificationFrame frame, int target, int frame_index, boolean match, boolean update) { + if (frame_index < 0 || frame_index >= _frame_count) { + _verifier.verifyError(String.format("Expecting a stackmap frame at branch target %d", target)); + } + VerificationFrame stackmap_frame = _frame_array[frame_index]; + boolean result = true; + if (match) { + result = frame.is_assignable_to(stackmap_frame); + } + if (update) { + int lsize = stackmap_frame.locals_size(); + int ssize = stackmap_frame.stack_size(); + if (frame.locals_size() > lsize || frame.stack_size() > ssize) { + frame.reset(); + } + frame.set_locals_size(lsize); + frame.copy_locals(stackmap_frame); + frame.set_stack_size(ssize); + frame.copy_stack(stackmap_frame); + frame.set_flags(stackmap_frame.flags()); + } + return result; + } + + void check_jump_target(VerificationFrame frame, int target) { + boolean match = match_stackmap(frame, target, true, false); + if (!match || (target < 0 || target >= _code_length)) { + _verifier.verifyError(String.format("Inconsistent stackmap frames at branch target %d", target)); + } + } + + static class StackMapReader { + + private final VerificationWrapper.ConstantPoolWrapper _cp; + private final StackMapStream _stream; + private final byte[] _code_data; + private final int _code_length; + private final int _frame_count; + + void check_verification_type_array_size(int size, int max_size) { + if (size < 0 || size > max_size) { + _verifier.classError("StackMapTable format error: bad type array size"); + } + } + + private static final int + SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, + SAME_EXTENDED = 251, + FULL = 255; + + public int get_frame_count() { + return _frame_count; + } + + public void check_end() { + if (!_stream.at_end()) { + _verifier.classError("wrong attribute size"); + } + } + + private final VerifierImpl _verifier; + + public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl context) { + this._verifier = context; + _stream = new StackMapStream(stackmapData, _verifier); + if (stackmapData != null) { + _frame_count = _stream.get_u2(); + } else { + _frame_count = 0; + } + _code_data = code_data; + _code_length = code_len; + _cp = cp; + } + + int chop(VerificationType[] locals, int length, int chops) { + if (locals == null) return -1; + int pos = length - 1; + for (int i=0; i= nconstants || _cp.tagAt(class_index) != VerifierImpl.JVM_CONSTANT_Class) { + _verifier.classError("bad class index"); + } + return VerificationType.reference_type(_cp.classNameAt(class_index)); + } + if (tag == ITEM_UninitializedThis) { + if (flags != null) { + flags[0] |= VerificationFrame.FLAG_THIS_UNINIT; + } + return VerificationType.uninitialized_this_type; + } + if (tag == ITEM_Uninitialized) { + int offset = _stream.get_u2(); + if (offset >= _code_length || _code_data[offset] != VerifierImpl.NEW_OFFSET) { + _verifier.classError("StackMapTable format error: bad offset for Uninitialized"); + } + return VerificationType.uninitialized_type(offset); + } + _verifier.classError("bad verification type"); + return VerificationType.bogus_type; + } + + public VerificationFrame next(VerificationFrame pre_frame, boolean first, int max_locals, int max_stack) { + VerificationFrame frame; + int offset; + VerificationType[] locals = null; + int frame_type = _stream.get_u1(); + if (frame_type < 64) { + if (first) { + offset = frame_type; + if (pre_frame.locals_size() > 0) { + locals = new VerificationType[pre_frame.locals_size()]; + } + } else { + offset = pre_frame.offset() + frame_type + 1; + locals = pre_frame.locals(); + } + frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), 0, max_locals, max_stack, locals, null, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } + if (frame_type < 128) { + if (first) { + offset = frame_type - 64; + if (pre_frame.locals_size() > 0) { + locals = new VerificationType[pre_frame.locals_size()]; + } + } else { + offset = pre_frame.offset() + frame_type - 63; + locals = pre_frame.locals(); + } + VerificationType[] stack = new VerificationType[2]; + int stack_size = 1; + stack[0] = parse_verification_type(null); + if (stack[0].is_category2()) { + stack[1] = stack[0].to_category2_2nd(_verifier); + stack_size = 2; + } + check_verification_type_array_size(stack_size, max_stack); + frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), stack_size, max_locals, max_stack, locals, stack, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } + int offset_delta = _stream.get_u2(); + if (frame_type < SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + _verifier.classError("reserved frame type"); + } + if (frame_type == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + if (first) { + offset = offset_delta; + if (pre_frame.locals_size() > 0) { + locals = new VerificationType[pre_frame.locals_size()]; + } + } else { + offset = pre_frame.offset() + offset_delta + 1; + locals = pre_frame.locals(); + } + VerificationType[] stack = new VerificationType[2]; + int stack_size = 1; + stack[0] = parse_verification_type(null); + if (stack[0].is_category2()) { + stack[1] = stack[0].to_category2_2nd(_verifier); + stack_size = 2; + } + check_verification_type_array_size(stack_size, max_stack); + frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), stack_size, max_locals, max_stack, locals, stack, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } + if (frame_type <= SAME_EXTENDED) { + locals = pre_frame.locals(); + int length = pre_frame.locals_size(); + int chops = SAME_EXTENDED - frame_type; + int new_length = length; + int flags = pre_frame.flags(); + if (chops != 0) { + new_length = chop(locals, length, chops); + check_verification_type_array_size(new_length, max_locals); + flags = 0; + for (int i=0; i 0) { + locals = new VerificationType[new_length]; + } else { + locals = null; + } + } else { + offset = pre_frame.offset() + offset_delta + 1; + } + frame = new VerificationFrame(offset, flags, new_length, 0, max_locals, max_stack, locals, null, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } else if (frame_type < SAME_EXTENDED + 4) { + int appends = frame_type - SAME_EXTENDED; + int real_length = pre_frame.locals_size(); + int new_length = real_length + appends*2; + locals = new VerificationType[new_length]; + VerificationType[] pre_locals = pre_frame.locals(); + int i; + for (i=0; i 0) { + locals = new VerificationType[locals_size*2]; + } + int i; + for (i=0; i 0) { + stack = new VerificationType[stack_size*2]; + } + for (i=0; ihotspot/share/classfile/verificationType.hpp + * @see hotspot/share/classfile/verificationType.cpp + */ +class VerificationType { + + private static final int BitsPerByte = 8; + + static final int + ITEM_Top = 0, + ITEM_Integer = 1, + ITEM_Float = 2, + ITEM_Double = 3, + ITEM_Long = 4, + ITEM_Null = 5, + ITEM_UninitializedThis = 6, + ITEM_Object = 7, + ITEM_Uninitialized = 8, + ITEM_Bogus = -1; + + VerificationType(String sym) { + _data = 0x100; + _sym = sym; + } + public VerificationType(int data, String sym) { + _data = data; + _sym = sym; + } + private final int _data; + private final String _sym; + + @Override + public int hashCode() { + return _sym == null ? _data : _sym.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof VerificationType ? (_data == ((VerificationType)obj)._data) && Objects.equals(_sym, ((VerificationType)obj)._sym) : false; + } + + private static final Map _constantsMap = new IdentityHashMap<>(18); + + @Override + public String toString() { + if (_constantsMap.isEmpty()) { + for (Field f : VerificationType.class.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers()) && f.getType() == VerificationType.class) try { + _constantsMap.put((VerificationType)f.get(null), f.getName()); + } catch (IllegalAccessException ignore) {} + } + } + if (_sym != null) return _sym; + if ((_data & 0xff) == Uninitialized) return "uninit@" + (_data >> 8); + return _constantsMap.getOrDefault(this, java.lang.Integer.toHexString(_data)); + } + + String name() { + return _sym; + } + private static final int + ITEM_Boolean = 9, ITEM_Byte = 10, ITEM_Short = 11, ITEM_Char = 12, + ITEM_Long_2nd = 13, ITEM_Double_2nd = 14; + + private static final int + TypeMask = 0x00000003, + // Topmost types encoding + Reference = 0x0, // _sym contains the name + Primitive = 0x1, // see below for primitive list + Uninitialized = 0x2, // 0x00ffff00 contains bci + TypeQuery = 0x3, // Meta-types used for category testing + // Utility flags + ReferenceFlag = 0x00, // For reference query types + Category1Flag = 0x01, // One-word values + Category2Flag = 0x02, // First word of a two-word value + Category2_2ndFlag = 0x04, // Second word of a two-word value + // special reference values + Null = 0x00000000, // A reference with a 0 sym is null + // Primitives categories (the second byte determines the category) + Category1 = (Category1Flag << BitsPerByte) | Primitive, + Category2 = (Category2Flag << BitsPerByte) | Primitive, + Category2_2nd = (Category2_2ndFlag << BitsPerByte) | Primitive, + // Primitive values (type descriminator stored in most-signifcant bytes) + // Bogus needs the " | Primitive". Else, isReference(Bogus) returns TRUE. + Bogus = (ITEM_Bogus << 2 * BitsPerByte) | Primitive, + Boolean = (ITEM_Boolean << 2 * BitsPerByte) | Category1, + Byte = (ITEM_Byte << 2 * BitsPerByte) | Category1, + Short = (ITEM_Short << 2 * BitsPerByte) | Category1, + Char = (ITEM_Char << 2 * BitsPerByte) | Category1, + Integer = (ITEM_Integer << 2 * BitsPerByte) | Category1, + Float = (ITEM_Float << 2 * BitsPerByte) | Category1, + Long = (ITEM_Long << 2 * BitsPerByte) | Category2, + Double = (ITEM_Double << 2 * BitsPerByte) | Category2, + Long_2nd = (ITEM_Long_2nd << 2 * BitsPerByte) | Category2_2nd, + Double_2nd = (ITEM_Double_2nd << 2 * BitsPerByte) | Category2_2nd, + // Used by Uninitialized (second and third bytes hold the bci) + BciMask = 0xffff << BitsPerByte, + // A bci of -1 is an Unintialized-This + BciForThis = 0xffff, + // Query values + ReferenceQuery = (ReferenceFlag << BitsPerByte) | TypeQuery, + Category1Query = (Category1Flag << BitsPerByte) | TypeQuery, + Category2Query = (Category2Flag << BitsPerByte) | TypeQuery, + Category2_2ndQuery = (Category2_2ndFlag << BitsPerByte) | TypeQuery; + + VerificationType(int raw_data) { + this._data = raw_data; + this._sym = null; + } + + static final VerificationType bogus_type = new VerificationType(Bogus), + null_type = new VerificationType(Null), + integer_type = new VerificationType(Integer), + float_type = new VerificationType(Float), + long_type = new VerificationType(Long), + long2_type = new VerificationType(Long_2nd), + double_type = new VerificationType(Double), + boolean_type = new VerificationType(Boolean), + byte_type = new VerificationType(Byte), + char_type = new VerificationType(Char), + short_type = new VerificationType(Short), + double2_type = new VerificationType(Double_2nd), + // "check" types are used for queries. A "check" type is not assignable + // to anything, but the specified types are assignable to a "check". For + // example, any category1 primitive is assignable to category1_check and + // any reference is assignable to reference_check. + reference_check = new VerificationType(ReferenceQuery), + category1_check = new VerificationType(Category1Query), + category2_check = new VerificationType(Category2Query); + + static VerificationType reference_type(String sh) { + return new VerificationType(sh); + } + + static VerificationType uninitialized_type(int bci) { + return new VerificationType(bci << 1 * BitsPerByte | Uninitialized); + } + + static final VerificationType uninitialized_this_type = uninitialized_type(BciForThis); + + boolean is_bogus() { + return (_data == Bogus); + } + + boolean is_null() { + return (_data == Null); + } + + boolean is_integer() { + return (_data == Integer); + } + + boolean is_long() { + return (_data == Long); + } + + boolean is_double() { + return (_data == Double); + } + + boolean is_long2() { + return (_data == Long_2nd ); + } + + boolean is_double2() { + return (_data == Double_2nd); + } + + boolean is_reference() { + return ((_data & TypeMask) == Reference); + } + + boolean is_category1(VerifierImpl context) { + // This should return true for all one-word types, which are category1 + // primitives, and references (including uninitialized refs). Though + // the 'query' types should technically return 'false' here, if we + // allow this to return true, we can perform the test using only + // 2 operations rather than 8 (3 masks, 3 compares and 2 logical 'ands'). + // Since noone should call this on a query type anyway, this is ok. + if(is_check()) context.verifyError("Must not be a check type (wrong value returned)"); + // should only return false if it's a primitive, and the category1 flag + // is not set. + return ((_data & Category1) != Primitive); + } + + boolean is_category2() { + return ((_data & Category2) == Category2); + } + + boolean is_category2_2nd() { + return ((_data & Category2_2nd) == Category2_2nd); + } + + boolean is_check() { + return (_data & TypeQuery) == TypeQuery; + } + + boolean is_x_array(char sig) { + return is_null() || (is_array() &&(name().charAt(1) == sig)); + } + + boolean is_int_array() { + return is_x_array(JVM_SIGNATURE_INT); + } + + boolean is_byte_array() { + return is_x_array(JVM_SIGNATURE_BYTE); + } + + boolean is_bool_array() { + return is_x_array(JVM_SIGNATURE_BOOLEAN); + } + + boolean is_char_array() { + return is_x_array(JVM_SIGNATURE_CHAR); + } + + boolean is_short_array() { + return is_x_array(JVM_SIGNATURE_SHORT); + } + + boolean is_long_array() { + return is_x_array(JVM_SIGNATURE_LONG); + } + + boolean is_float_array() { + return is_x_array(JVM_SIGNATURE_FLOAT); + } + + boolean is_double_array() { + return is_x_array(JVM_SIGNATURE_DOUBLE); + } + + boolean is_object_array() { + return is_x_array(JVM_SIGNATURE_CLASS); + } + + boolean is_array_array() { + return is_x_array(JVM_SIGNATURE_ARRAY); + } + + boolean is_reference_array() { + return is_object_array() || is_array_array(); + } + + boolean is_object() { + return (is_reference() && !is_null() && name().length() >= 1 && name().charAt(0) != JVM_SIGNATURE_ARRAY); + } + + boolean is_array() { + return (is_reference() && !is_null() && name().length() >= 2 && name().charAt(0) == JVM_SIGNATURE_ARRAY); + } + + boolean is_uninitialized() { + return ((_data & Uninitialized) == Uninitialized); + } + + boolean is_uninitialized_this(VerifierImpl context) { + return is_uninitialized() && bci(context) == BciForThis; + } + + VerificationType to_category2_2nd(VerifierImpl context) { + if (!(is_category2())) context.verifyError("Must be a double word"); + return is_long() ? long2_type : double2_type; + } + + int bci(VerifierImpl context) { + if (!(is_uninitialized())) context.verifyError("Must be uninitialized type"); + return ((_data & BciMask) >> 1 * BitsPerByte); + } + + boolean is_assignable_from(VerificationType from, VerifierImpl context) { + boolean ret = _is_assignable_from(from, context); + context.errorContext = ret ? "" : String.format("(%s is not assignable from %s)", this, from); + return ret; + } + + private boolean _is_assignable_from(VerificationType from, VerifierImpl context) { + if (equals(from) || is_bogus()) { + return true; + } else { + switch(_data) { + case Category1Query: + return from.is_category1(context); + case Category2Query: + return from.is_category2(); + case Category2_2ndQuery: + return from.is_category2_2nd(); + case ReferenceQuery: + return from.is_reference() || from.is_uninitialized(); + case Boolean: + case Byte: + case Char: + case Short: + return from.is_integer(); + default: + if (is_reference() && from.is_reference()) { + return is_reference_assignable_from(from, context); + } else { + return false; + } + } + } + } + + // Check to see if one array component type is assignable to another. + // Same as is_assignable_from() except int primitives must be identical. + boolean is_component_assignable_from(VerificationType from, VerifierImpl context) { + if (equals(from) || is_bogus()) { + return true; + } else { + switch (_data) { + case Boolean: + case Byte: + case Char: + case Short: + return false; + default: + return is_assignable_from(from, context); + } + } + } + + int dimensions(VerifierImpl context) { + if (!(is_array())) context.verifyError("Must be an array"); + int index = 0; + while (name().charAt(index) == JVM_SIGNATURE_ARRAY) index++; + return index; + } + + static VerificationType from_tag(int tag, VerifierImpl context) { + switch (tag) { + case ITEM_Top: return bogus_type; + case ITEM_Integer: return integer_type; + case ITEM_Float: return float_type; + case ITEM_Double: return double_type; + case ITEM_Long: return long_type; + case ITEM_Null: return null_type; + default: + context.verifyError("Should not reach here"); + return bogus_type; + } + } + + boolean resolve_and_check_assignability(ClassHierarchyImpl assignResolver, String name, String from_name, boolean from_is_array, boolean from_is_object) { + //let's delegate assignability to SPI + var desc = Util.toClassDesc(name); + if (assignResolver.isInterface(desc)) { + return !from_is_array || "java/lang/Cloneable".equals(name) || "java/io/Serializable".equals(name); + } else if (from_is_object) { + return assignResolver.isAssignableFrom(desc, Util.toClassDesc(from_name)); + } + return false; + } + + boolean is_reference_assignable_from(VerificationType from, VerifierImpl context) { + ClassHierarchyImpl clsTree = context.class_hierarchy(); + if (from.is_null()) { + return true; + } else if (is_null()) { + return false; + } else if (name().equals(from.name())) { + return true; + } else if (is_object()) { + if (VerifierImpl.java_lang_Object.equals(name())) { + return true; + } + return resolve_and_check_assignability(clsTree, name(), from.name(), from.is_array(), from.is_object()); + } else if (is_array() && from.is_array()) { + VerificationType comp_this = get_component(context); + VerificationType comp_from = from.get_component(context); + if (!comp_this.is_bogus() && !comp_from.is_bogus()) { + return comp_this.is_component_assignable_from(comp_from, context); + } + } + return false; + } + + VerificationType get_component(VerifierImpl context) { + if (!(is_array() && name().length() >= 2)) context.verifyError("Must be a valid array"); + var ss = new VerificationSignature(name(), false, context); + ss.skipArrayPrefix(1); + switch (ss.type()) { + case T_BOOLEAN: return VerificationType.boolean_type; + case T_BYTE: return VerificationType.byte_type; + case T_CHAR: return VerificationType.char_type; + case T_SHORT: return VerificationType.short_type; + case T_INT: return VerificationType.integer_type; + case T_LONG: return VerificationType.long_type; + case T_FLOAT: return VerificationType.float_type; + case T_DOUBLE: return VerificationType.double_type; + case T_ARRAY: + case T_OBJECT: { + if (!(ss.isReference())) context.verifyError("Unchecked verifier input"); + String component = ss.asSymbol(); + return VerificationType.reference_type(component); + } + default: + return VerificationType.bogus_type; + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java new file mode 100644 index 0000000000000..5f3716ab76aac --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022, 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. + * + */ +package jdk.internal.classfile.impl.verifier; + +import java.util.LinkedList; +import java.util.List; + +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.attribute.LocalVariableInfo; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.CodeImpl; +import jdk.internal.classfile.impl.Util; + +public final class VerificationWrapper { + private final ClassModel clm; + private final ConstantPoolWrapper cp; + + public VerificationWrapper(ClassModel clm) { + this.clm = clm; + this.cp = new ConstantPoolWrapper(clm.constantPool()); + } + + String thisClassName() { + return clm.thisClass().asInternalName(); + } + + int majorVersion() { + return clm.majorVersion(); + } + + String superclassName() { + return clm.superclass().map(ClassEntry::asInternalName).orElse(null); + } + + Iterable interfaceNames() { + return Util.mappedList(clm.interfaces(), ClassEntry::asInternalName); + } + + Iterable methods() { + return clm.methods().stream().map(m -> new MethodWrapper(m)).toList(); + } + + boolean findField(String name, String sig) { + for (var f : clm.fields()) + if (f.fieldName().stringValue().equals(name) && f.fieldType().stringValue().equals(sig)) + return true; + return false; + } + + class MethodWrapper { + + final MethodModel m; + private final CodeImpl c; + private final List exc; + + MethodWrapper(MethodModel m) { + this.m = m; + this.c = (CodeImpl)m.code().orElse(null); + exc = new LinkedList<>(); + if (c != null) c.iterateExceptionHandlers((start, end, handler, catchType) -> { + exc.add(new int[] {start, end, handler, catchType}); + }); + } + + ConstantPoolWrapper constantPool() { + return cp; + } + + boolean isNative() { + return m.flags().has(AccessFlag.NATIVE); + } + + boolean isAbstract() { + return m.flags().has(AccessFlag.ABSTRACT); + } + + boolean isBridge() { + return m.flags().has(AccessFlag.BRIDGE); + } + + boolean isStatic() { + return m.flags().has(AccessFlag.STATIC); + } + + String name() { + return m.methodName().stringValue(); + } + + int maxStack() { + return c == null ? 0 : c.maxStack(); + } + + int maxLocals() { + return c == null ? 0 : c.maxLocals(); + } + + String descriptor() { + return m.methodType().stringValue(); + } + + int codeLength() { + return c == null ? 0 : c.codeLength(); + } + + byte[] codeArray() { + return c == null ? null : c.codeArray(); + } + + List exceptionTable() { + return exc; + } + + List localVariableTable() { + var attro = c.findAttribute(Attributes.LOCAL_VARIABLE_TABLE); + return attro.map(lvta -> lvta.localVariables()).orElse(List.of()); + } + + byte[] stackMapTableRawData() { + var attro = c.findAttribute(Attributes.STACK_MAP_TABLE); + return attro.map(attr -> ((BoundAttribute) attr).contents()).orElse(null); + } + + } + + static class ConstantPoolWrapper { + + private final ConstantPool cp; + + ConstantPoolWrapper(ConstantPool cp) { + this.cp = cp; + } + + int entryCount() { + return cp.entryCount(); + } + + String classNameAt(int index) { + return ((ClassEntry)cp.entryByIndex(index)).asInternalName(); + } + + String dynamicConstantSignatureAt(int index) { + return ((DynamicConstantPoolEntry)cp.entryByIndex(index)).type().stringValue(); + } + + int tagAt(int index) { + return cp.entryByIndex(index).tag(); + } + + private NameAndTypeEntry _refNameType(int index) { + var e = cp.entryByIndex(index); + return (e instanceof DynamicConstantPoolEntry de) ? de.nameAndType() : + e != null ? ((MemberRefEntry)e).nameAndType() : null; + } + + String refNameAt(int index) { + return _refNameType(index).name().stringValue(); + } + + String refSignatureAt(int index) { + return _refNameType(index).type().stringValue(); + } + + int refClassIndexAt(int index) { + return ((MemberRefEntry)cp.entryByIndex(index)).owner().index(); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java new file mode 100644 index 0000000000000..ea3baa0647148 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java @@ -0,0 +1,1828 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.impl.verifier; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import jdk.internal.classfile.ClassHierarchyResolver; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.components.ClassPrinter; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.impl.ClassHierarchyImpl; +import jdk.internal.classfile.impl.RawBytecodeHelper; +import static jdk.internal.classfile.impl.RawBytecodeHelper.ILLEGAL; +import jdk.internal.classfile.impl.verifier.VerificationWrapper.ConstantPoolWrapper; +import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.*; +import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType; +import static jdk.internal.classfile.impl.verifier.VerificationFrame.FLAG_THIS_UNINIT; + +/** + * @see java.base/share/native/include/classfile_constants.h.template + * @see hotspot/share/classfile/verifier.hpp + * @see hotspot/share/classfile/verifier.cpp + */ +public final class VerifierImpl { + static final int + JVM_CONSTANT_Utf8 = 1, + JVM_CONSTANT_Unicode = 2, + JVM_CONSTANT_Integer = 3, + JVM_CONSTANT_Float = 4, + JVM_CONSTANT_Long = 5, + JVM_CONSTANT_Double = 6, + JVM_CONSTANT_Class = 7, + JVM_CONSTANT_String = 8, + JVM_CONSTANT_Fieldref = 9, + JVM_CONSTANT_Methodref = 10, + JVM_CONSTANT_InterfaceMethodref = 11, + JVM_CONSTANT_NameAndType = 12, + JVM_CONSTANT_MethodHandle = 15, + JVM_CONSTANT_MethodType = 16, + JVM_CONSTANT_Dynamic = 17, + JVM_CONSTANT_InvokeDynamic = 18, + JVM_CONSTANT_Module = 19, + JVM_CONSTANT_Package = 20, + JVM_CONSTANT_ExternalMax = 20; + +static final char JVM_SIGNATURE_SPECIAL = '<', + JVM_SIGNATURE_ARRAY = '[', + JVM_SIGNATURE_BYTE = 'B', + JVM_SIGNATURE_CHAR = 'C', + JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_FLOAT = 'F', + JVM_SIGNATURE_DOUBLE = 'D', + JVM_SIGNATURE_INT = 'I', + JVM_SIGNATURE_LONG = 'J', + JVM_SIGNATURE_SHORT = 'S', + JVM_SIGNATURE_BOOLEAN = 'Z'; + + static final String java_lang_String = "java/lang/String"; + static final String object_initializer_name = ""; + static final String java_lang_invoke_MethodHandle = "java/lang/invoke/MethodHandle"; + static final String java_lang_Object = "java/lang/Object"; + static final String java_lang_invoke_MethodType = "java/lang/invoke/MethodType"; + static final String java_lang_Throwable = "java/lang/Throwable"; + static final String java_lang_Class = "java/lang/Class"; + + String errorContext = ""; + private int bci; + + static void log_info(Consumer logger, String messageFormat, Object... args) { + if (logger != null) logger.accept(String.format(messageFormat + "%n", args)); + } + private final Consumer _logger; + void log_info(String messageFormat, Object... args) { + log_info(_logger, messageFormat, args); + } + + + static final int STACKMAP_ATTRIBUTE_MAJOR_VERSION = 50; + static final int INVOKEDYNAMIC_MAJOR_VERSION = 51; + static final int NOFAILOVER_MAJOR_VERSION = 51; + + public static List verify(ClassModel classModel, Consumer logger) { + return verify(classModel, ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER, logger); + } + + public static List verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer logger) { + var klass = new VerificationWrapper(classModel); + if (!is_eligible_for_verification(klass)) { + return List.of(); + } + log_info(logger, "Start class verification for: %s", klass.thisClassName()); + try { + if (klass.majorVersion() >= STACKMAP_ATTRIBUTE_MAJOR_VERSION) { + var errors = new VerifierImpl(klass, classHierarchyResolver, logger).verify_class(); + if (!errors.isEmpty() && klass.majorVersion() < NOFAILOVER_MAJOR_VERSION) { + log_info(logger, "Fail over class verification to old verifier for: %s", klass.thisClassName()); + return inference_verify(klass); + } else { + return errors; + } + } else { + return inference_verify(klass); + } + } finally { + log_info(logger, "End class verification for: %s", klass.thisClassName()); + } + } + + public static boolean is_eligible_for_verification(VerificationWrapper klass) { + String name = klass.thisClassName(); + return !java_lang_Object.equals(name) && + !java_lang_Class.equals(name) && + !java_lang_String.equals(name) && + !java_lang_Throwable.equals(name); + } + + static List inference_verify(VerificationWrapper klass) { + return List.of(new VerifyError("Inference verification is not supported")); + } + + static class sig_as_verification_types { + private int _num_args; + private ArrayList _sig_verif_types; + + sig_as_verification_types(ArrayList sig_verif_types) { + this._sig_verif_types = sig_verif_types; + this._num_args = 0; + } + + int num_args() { + return _num_args; + } + + void set_num_args(int num_args) { + _num_args = num_args; + } + + ArrayList sig_verif_types() { + return _sig_verif_types; + } + } + + VerificationType cp_ref_index_to_type(int index, ConstantPoolWrapper cp) { + return cp_index_to_type(cp.refClassIndexAt(index), cp); + } + + final VerificationWrapper _klass; + final ClassHierarchyImpl _class_hierarchy; + VerificationWrapper.MethodWrapper _method; + VerificationType _this_type; + + static final int BYTECODE_OFFSET = 1, NEW_OFFSET = 2; + + VerificationWrapper current_class() { + return _klass; + } + + ClassHierarchyImpl class_hierarchy() { + return _class_hierarchy; + } + + VerificationType current_type() { + return _this_type; + } + + VerificationType cp_index_to_type(int index, ConstantPoolWrapper cp) { + return VerificationType.reference_type(cp.classNameAt(index)); + } + + int change_sig_to_verificationType(VerificationSignature sig_type, VerificationType inference_types[], int inference_type_index) { + BasicType bt = sig_type.type(); + switch (bt) { + case T_OBJECT: + case T_ARRAY: + String name = sig_type.asSymbol(); + inference_types[inference_type_index] = VerificationType.reference_type(name); + return 1; + case T_LONG: + inference_types[inference_type_index] = VerificationType.long_type; + inference_types[++inference_type_index] = VerificationType.long2_type; + return 2; + case T_DOUBLE: + inference_types[inference_type_index] = VerificationType.double_type; + inference_types[++inference_type_index] = VerificationType.double2_type; + return 2; + case T_INT: + case T_BOOLEAN: + case T_BYTE: + case T_CHAR: + case T_SHORT: + inference_types[inference_type_index] = VerificationType.integer_type; + return 1; + case T_FLOAT: + inference_types[inference_type_index] = VerificationType.float_type; + return 1; + default: + verifyError("Should not reach here"); + return 1; + } + } + + private static final int NONZERO_PADDING_BYTES_IN_SWITCH_MAJOR_VERSION = 51; + private static final int STATIC_METHOD_IN_INTERFACE_MAJOR_VERSION = 52; + private static final int MAX_ARRAY_DIMENSIONS = 255; + + VerifierImpl(VerificationWrapper klass, ClassHierarchyResolver classHierarchyResolver, Consumer logger) { + _klass = klass; + _class_hierarchy = new ClassHierarchyImpl(classHierarchyResolver); + _this_type = VerificationType.reference_type(klass.thisClassName()); + _logger = logger; + } + + private VerificationType object_type() { + return VerificationType.reference_type(java_lang_Object); + } + + List verify_class() { + log_info("Verifying class %s with new format", _klass.thisClassName()); + var errors = new ArrayList(); + for (VerificationWrapper.MethodWrapper m : _klass.methods()) { + if (m.isNative() || m.isAbstract() || m.isBridge()) { + continue; + } + verify_method(m, errors); + } + return errors; + } + + void translate_signature(String method_sig, sig_as_verification_types sig_verif_types) { + var sig_stream = new VerificationSignature(method_sig, true, this); + VerificationType[] sig_type = new VerificationType[2]; + int sig_i = 0; + ArrayList verif_types = sig_verif_types.sig_verif_types(); + while (!sig_stream.atReturnType()) { + int n = change_sig_to_verificationType(sig_stream, sig_type, 0); + if (n > 2) verifyError("Unexpected signature type"); + for (int x = 0; x < n; x++) { + verif_types.add(sig_type[x]); + } + sig_i += n; + sig_stream.next(); + } + sig_verif_types.set_num_args(sig_i); + if (sig_stream.type() != BasicType.T_VOID) { + int n = change_sig_to_verificationType(sig_stream, sig_type, 0); + if (n > 2) verifyError("Unexpected signature return type"); + for (int y = 0; y < n; y++) { + verif_types.add(sig_type[y]); + } + } + } + + void create_method_sig_entry(sig_as_verification_types sig_verif_types, String method_sig) { + translate_signature(method_sig, sig_verif_types); + } + + void verify_method(VerificationWrapper.MethodWrapper m, List errorsCollector) { + try { + verify_method(m, m.maxLocals(), m.maxStack(), m.stackMapTableRawData()); + } catch (VerifyError err) { + errorsCollector.add(err); + } catch (Error | Exception e) { + e.printStackTrace(); + errorsCollector.add(new VerifyError(e.toString())); + } + } + + @SuppressWarnings("fallthrough") + void verify_method(VerificationWrapper.MethodWrapper m, int max_locals, int max_stack, byte[] stackmap_data) { + _method = m; + log_info(_logger, "Verifying method %s%s", m.name(), m.descriptor()); + var cp = m.constantPool(); + if (!VerificationSignature.isValidMethodSignature(m.descriptor())) verifyError("Invalid method signature"); + VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, this); + VerificationType return_type = current_frame.set_locals_from_arg(m, current_type()); + int stackmap_index = 0; + int code_length = m.codeLength(); + var code = ByteBuffer.wrap(_method.codeArray(), 0, _method.codeLength()); + byte[] code_data = generate_code_data(code, code_length); + int ex_minmax[] = new int[] {code_length, -1}; + verify_exception_handler_table(code_length, code_data, ex_minmax); + verify_local_variable_table(code_length, code_data); + + VerificationTable stackmap_table = new VerificationTable(stackmap_data, current_frame, max_locals, max_stack, code_data, code_length, cp, this); + + var bcs = new RawBytecodeHelper(code); + boolean no_control_flow = false; + int opcode; + while (!bcs.isLastBytecode()) { + opcode = bcs.rawNext(); + bci = bcs.bci; + current_frame.set_offset(bci); + current_frame.set_mark(); + stackmap_index = verify_stackmap_table(stackmap_index, bci, current_frame, stackmap_table, no_control_flow); + boolean this_uninit = false; + boolean verified_exc_handlers = false; + { + int index; + int target; + VerificationType type, type2 = null; + VerificationType atype; + if (bcs.isWide) { + if (opcode != Classfile.IINC && opcode != Classfile.ILOAD + && opcode != Classfile.ALOAD && opcode != Classfile.LLOAD + && opcode != Classfile.ISTORE && opcode != Classfile.ASTORE + && opcode != Classfile.LSTORE && opcode != Classfile.FLOAD + && opcode != Classfile.DLOAD && opcode != Classfile.FSTORE + && opcode != Classfile.DSTORE) { + verifyError("Bad wide instruction"); + } + } + if (VerificationBytecodes.is_store_into_local(opcode) && bci >= ex_minmax[0] && bci < ex_minmax[1]) { + verify_exception_handler_targets(bci, this_uninit, current_frame, stackmap_table); + verified_exc_handlers = true; + } + switch (opcode) { + case Classfile.NOP : + no_control_flow = false; break; + case Classfile.ACONST_NULL : + current_frame.push_stack( + VerificationType.null_type); + no_control_flow = false; break; + case Classfile.ICONST_M1 : + case Classfile.ICONST_0 : + case Classfile.ICONST_1 : + case Classfile.ICONST_2 : + case Classfile.ICONST_3 : + case Classfile.ICONST_4 : + case Classfile.ICONST_5 : + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.LCONST_0 : + case Classfile.LCONST_1 : + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.FCONST_0 : + case Classfile.FCONST_1 : + case Classfile.FCONST_2 : + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case Classfile.DCONST_0 : + case Classfile.DCONST_1 : + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case Classfile.SIPUSH : + case Classfile.BIPUSH : + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.LDC : + verify_ldc( + opcode, bcs.getIndexU1(), current_frame, + cp, bci); + no_control_flow = false; break; + case Classfile.LDC_W : + case Classfile.LDC2_W : + verify_ldc( + opcode, bcs.getIndexU2(), current_frame, + cp, bci); + no_control_flow = false; break; + case Classfile.ILOAD : + verify_iload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.ILOAD_0 : + case Classfile.ILOAD_1 : + case Classfile.ILOAD_2 : + case Classfile.ILOAD_3 : + index = opcode - Classfile.ILOAD_0; + verify_iload(index, current_frame); + no_control_flow = false; break; + case Classfile.LLOAD : + verify_lload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.LLOAD_0 : + case Classfile.LLOAD_1 : + case Classfile.LLOAD_2 : + case Classfile.LLOAD_3 : + index = opcode - Classfile.LLOAD_0; + verify_lload(index, current_frame); + no_control_flow = false; break; + case Classfile.FLOAD : + verify_fload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.FLOAD_0 : + case Classfile.FLOAD_1 : + case Classfile.FLOAD_2 : + case Classfile.FLOAD_3 : + index = opcode - Classfile.FLOAD_0; + verify_fload(index, current_frame); + no_control_flow = false; break; + case Classfile.DLOAD : + verify_dload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.DLOAD_0 : + case Classfile.DLOAD_1 : + case Classfile.DLOAD_2 : + case Classfile.DLOAD_3 : + index = opcode - Classfile.DLOAD_0; + verify_dload(index, current_frame); + no_control_flow = false; break; + case Classfile.ALOAD : + verify_aload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.ALOAD_0 : + case Classfile.ALOAD_1 : + case Classfile.ALOAD_2 : + case Classfile.ALOAD_3 : + index = opcode - Classfile.ALOAD_0; + verify_aload(index, current_frame); + no_control_flow = false; break; + case Classfile.IALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_int_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.BALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_bool_array() && !atype.is_byte_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.CALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_char_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.SALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_short_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.LALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_long_array()) { + verifyError("Bad type"); + } + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.FALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_float_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case Classfile.DALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_double_array()) { + verifyError("Bad type"); + } + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case Classfile.AALOAD : { + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_reference_array()) { + verifyError("Bad type"); + } + if (atype.is_null()) { + current_frame.push_stack( + VerificationType.null_type); + } else { + VerificationType component = + atype.get_component(this); + current_frame.push_stack(component); + } + no_control_flow = false; break; + } + case Classfile.ISTORE : + verify_istore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.ISTORE_0 : + case Classfile.ISTORE_1 : + case Classfile.ISTORE_2 : + case Classfile.ISTORE_3 : + index = opcode - Classfile.ISTORE_0; + verify_istore(index, current_frame); + no_control_flow = false; break; + case Classfile.LSTORE : + verify_lstore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.LSTORE_0 : + case Classfile.LSTORE_1 : + case Classfile.LSTORE_2 : + case Classfile.LSTORE_3 : + index = opcode - Classfile.LSTORE_0; + verify_lstore(index, current_frame); + no_control_flow = false; break; + case Classfile.FSTORE : + verify_fstore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.FSTORE_0 : + case Classfile.FSTORE_1 : + case Classfile.FSTORE_2 : + case Classfile.FSTORE_3 : + index = opcode - Classfile.FSTORE_0; + verify_fstore(index, current_frame); + no_control_flow = false; break; + case Classfile.DSTORE : + verify_dstore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.DSTORE_0 : + case Classfile.DSTORE_1 : + case Classfile.DSTORE_2 : + case Classfile.DSTORE_3 : + index = opcode - Classfile.DSTORE_0; + verify_dstore(index, current_frame); + no_control_flow = false; break; + case Classfile.ASTORE : + verify_astore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.ASTORE_0 : + case Classfile.ASTORE_1 : + case Classfile.ASTORE_2 : + case Classfile.ASTORE_3 : + index = opcode - Classfile.ASTORE_0; + verify_astore(index, current_frame); + no_control_flow = false; break; + case Classfile.IASTORE : + type = current_frame.pop_stack( + VerificationType.integer_type); + type2 = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_int_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.BASTORE : + type = current_frame.pop_stack( + VerificationType.integer_type); + type2 = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_bool_array() && !atype.is_byte_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.CASTORE : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_char_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.SASTORE : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_short_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.LASTORE : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_long_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.FASTORE : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.pop_stack + (VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_float_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.DASTORE : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_double_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.AASTORE : + type = current_frame.pop_stack(object_type()); + type2 = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + // more type-checking is done at runtime + if (!atype.is_reference_array()) { + verifyError("Bad type"); + } + // 4938384: relaxed constraint in JVMS 3nd edition. + no_control_flow = false; break; + case Classfile.POP : + current_frame.pop_stack( + VerificationType.category1_check); + no_control_flow = false; break; + case Classfile.POP2 : + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + no_control_flow = false; break; + case Classfile.DUP : + type = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type); + current_frame.push_stack(type); + no_control_flow = false; break; + case Classfile.DUP_X1 : + type = current_frame.pop_stack( + VerificationType.category1_check); + type2 = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + case Classfile.DUP_X2 : + { + VerificationType type3 = null; + type = current_frame.pop_stack( + VerificationType.category1_check); + type2 = current_frame.pop_stack(); + if (type2.is_category1(this)) { + type3 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type2.is_category2_2nd()) { + type3 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + current_frame.push_stack(type); + current_frame.push_stack(type3); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + } + case Classfile.DUP2 : + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + type2 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + type2 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + current_frame.push_stack(type2); + current_frame.push_stack(type); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + case Classfile.DUP2_X1 : + { + VerificationType type3; + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + type2 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + type2 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + type3 = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type2); + current_frame.push_stack(type); + current_frame.push_stack(type3); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + } + case Classfile.DUP2_X2 : + VerificationType type3, type4 = null; + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + type2 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + type2 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + type3 = current_frame.pop_stack(); + if (type3.is_category1(this)) { + type4 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type3.is_category2_2nd()) { + type4 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + current_frame.push_stack(type2); + current_frame.push_stack(type); + current_frame.push_stack(type4); + current_frame.push_stack(type3); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + case Classfile.SWAP : + type = current_frame.pop_stack( + VerificationType.category1_check); + type2 = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type); + current_frame.push_stack(type2); + no_control_flow = false; break; + case Classfile.IADD : + case Classfile.ISUB : + case Classfile.IMUL : + case Classfile.IDIV : + case Classfile.IREM : + case Classfile.ISHL : + case Classfile.ISHR : + case Classfile.IUSHR : + case Classfile.IOR : + case Classfile.IXOR : + case Classfile.IAND : + current_frame.pop_stack( + VerificationType.integer_type); + // fall through + case Classfile.INEG : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.LADD : + case Classfile.LSUB : + case Classfile.LMUL : + case Classfile.LDIV : + case Classfile.LREM : + case Classfile.LAND : + case Classfile.LOR : + case Classfile.LXOR : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + // fall through + case Classfile.LNEG : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.LSHL : + case Classfile.LSHR : + case Classfile.LUSHR : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.FADD : + case Classfile.FSUB : + case Classfile.FMUL : + case Classfile.FDIV : + case Classfile.FREM : + current_frame.pop_stack( + VerificationType.float_type); + // fall through + case Classfile.FNEG : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case Classfile.DADD : + case Classfile.DSUB : + case Classfile.DMUL : + case Classfile.DDIV : + case Classfile.DREM : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + // fall through + case Classfile.DNEG : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case Classfile.IINC : + verify_iinc(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case Classfile.I2L : + type = current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.L2I : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.I2F : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case Classfile.I2D : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case Classfile.L2F : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case Classfile.L2D : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case Classfile.F2I : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.F2L : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.F2D : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case Classfile.D2I : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.D2L : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case Classfile.D2F : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case Classfile.I2B : + case Classfile.I2C : + case Classfile.I2S : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.LCMP : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.FCMPL : + case Classfile.FCMPG : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.DCMPL : + case Classfile.DCMPG : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.IF_ICMPEQ: + case Classfile.IF_ICMPNE: + case Classfile.IF_ICMPLT: + case Classfile.IF_ICMPGE: + case Classfile.IF_ICMPGT: + case Classfile.IF_ICMPLE: + current_frame.pop_stack( + VerificationType.integer_type); + // fall through + case Classfile.IFEQ: + case Classfile.IFNE: + case Classfile.IFLT: + case Classfile.IFGE: + case Classfile.IFGT: + case Classfile.IFLE: + current_frame.pop_stack( + VerificationType.integer_type); + target = bcs.dest(); + stackmap_table.check_jump_target( + current_frame, target); + no_control_flow = false; break; + case Classfile.IF_ACMPEQ : + case Classfile.IF_ACMPNE : + current_frame.pop_stack( + VerificationType.reference_check); + // fall through + case Classfile.IFNULL : + case Classfile.IFNONNULL : + current_frame.pop_stack( + VerificationType.reference_check); + target = bcs.dest(); + stackmap_table.check_jump_target + (current_frame, target); + no_control_flow = false; break; + case Classfile.GOTO : + target = bcs.dest(); + stackmap_table.check_jump_target( + current_frame, target); + no_control_flow = true; break; + case Classfile.GOTO_W : + target = bcs.destW(); + stackmap_table.check_jump_target( + current_frame, target); + no_control_flow = true; break; + case Classfile.TABLESWITCH : + case Classfile.LOOKUPSWITCH : + verify_switch( + bcs, code_length, code_data, current_frame, + stackmap_table); + no_control_flow = true; break; + case Classfile.IRETURN : + type = current_frame.pop_stack( + VerificationType.integer_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case Classfile.LRETURN : + type2 = current_frame.pop_stack( + VerificationType.long2_type); + type = current_frame.pop_stack( + VerificationType.long_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case Classfile.FRETURN : + type = current_frame.pop_stack( + VerificationType.float_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case Classfile.DRETURN : + type2 = current_frame.pop_stack( + VerificationType.double2_type); + type = current_frame.pop_stack( + VerificationType.double_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case Classfile.ARETURN : + type = current_frame.pop_stack( + VerificationType.reference_check); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case Classfile.RETURN: + if (!return_type.is_bogus()) { + verifyError("Method expects a return value"); + } + if (object_initializer_name.equals(_method.name()) && + current_frame.flag_this_uninit()) { + verifyError("Constructor must call super() or this() before return"); + } + no_control_flow = true; break; + case Classfile.GETSTATIC : + case Classfile.PUTSTATIC : + verify_field_instructions(bcs, current_frame, cp, true); + no_control_flow = false; break; + case Classfile.GETFIELD : + case Classfile.PUTFIELD : + verify_field_instructions(bcs, current_frame, cp, false); + no_control_flow = false; break; + case Classfile.INVOKEVIRTUAL : + case Classfile.INVOKESPECIAL : + case Classfile.INVOKESTATIC : + this_uninit = verify_invoke_instructions(bcs, code_length, current_frame, (bci >= ex_minmax[0] && bci < ex_minmax[1]), this_uninit, return_type, cp, stackmap_table); + no_control_flow = false; break; + case Classfile.INVOKEINTERFACE : + case Classfile.INVOKEDYNAMIC : + this_uninit = verify_invoke_instructions(bcs, code_length, current_frame, (bci >= ex_minmax[0] && bci < ex_minmax[1]), this_uninit, return_type, cp, stackmap_table); + no_control_flow = false; break; + case Classfile.NEW : + { + index = bcs.getIndexU2(); + verify_cp_class_type(bci, index, cp); + VerificationType new_class_type = + cp_index_to_type(index, cp); + if (!new_class_type.is_object()) { + verifyError("Illegal new instruction"); + } + type = VerificationType.uninitialized_type(bci); + current_frame.push_stack(type); + no_control_flow = false; break; + } + case Classfile.NEWARRAY : + type = get_newarray_type(bcs.getIndex(), bci); + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack(type); + no_control_flow = false; break; + case Classfile.ANEWARRAY : + verify_anewarray(bci, bcs.getIndexU2(), cp, current_frame); + no_control_flow = false; break; + case Classfile.ARRAYLENGTH : + type = current_frame.pop_stack( + VerificationType.reference_check); + if (!(type.is_null() || type.is_array())) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case Classfile.CHECKCAST : + { + index = bcs.getIndexU2(); + verify_cp_class_type(bci, index, cp); + current_frame.pop_stack(object_type()); + VerificationType klass_type = cp_index_to_type( + index, cp); + current_frame.push_stack(klass_type); + no_control_flow = false; break; + } + case Classfile.INSTANCEOF : { + index = bcs.getIndexU2(); + verify_cp_class_type(bci, index, cp); + current_frame.pop_stack(object_type()); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + } + case Classfile.MONITORENTER : + case Classfile.MONITOREXIT : + current_frame.pop_stack( + VerificationType.reference_check); + no_control_flow = false; break; + case Classfile.MULTIANEWARRAY : + { + index = bcs.getIndexU2(); + int dim = _method.codeArray()[bcs.bci+3] & 0xff; + verify_cp_class_type(bci, index, cp); + VerificationType new_array_type = + cp_index_to_type(index, cp); + if (!new_array_type.is_array()) { + verifyError("Illegal constant pool index in multianewarray instruction"); + } + if (dim < 1 || new_array_type.dimensions(this) < dim) { + verifyError(String.format("Illegal dimension in multianewarray instruction: %d", dim)); + } + for (int i = 0; i < dim; i++) { + current_frame.pop_stack( + VerificationType.integer_type); + } + current_frame.push_stack(new_array_type); + no_control_flow = false; break; + } + case Classfile.ATHROW : + type = VerificationType.reference_type(java_lang_Throwable); + current_frame.pop_stack(type); + no_control_flow = true; break; + default: + verifyError(String.format("Bad instruction: %02x", opcode)); + } + } + if (verified_exc_handlers && this_uninit) verifyError("Exception handler targets got verified before this_uninit got set"); + if (!verified_exc_handlers && bci >= ex_minmax[0] && bci < ex_minmax[1]) { + verify_exception_handler_targets(bci, this_uninit, current_frame, stackmap_table); + } + } + if (!no_control_flow) { + verifyError("Control flow falls through code end"); + } + } + + private byte[] generate_code_data(ByteBuffer code, int code_length) { + byte code_data[] = new byte[code_length]; + var bcs = new RawBytecodeHelper(code); + while (!bcs.isLastBytecode()) { + if (bcs.rawNext() != ILLEGAL) { + int bci = bcs.bci; + if (bcs.rawCode == Classfile.NEW) { + code_data[bci] = NEW_OFFSET; + } else { + code_data[bci] = BYTECODE_OFFSET; + } + } else { + verifyError("Bad instruction"); + } + } + return code_data; + } + + void verify_exception_handler_table(int code_length, byte[] code_data, int[] minmax) { + var cp = _method.constantPool(); + for (var exhandler : _method.exceptionTable()) { + int start_pc = exhandler[0]; + int end_pc = exhandler[1]; + int handler_pc = exhandler[2]; + if (start_pc >= code_length || code_data[start_pc] == 0) { + classError(String.format("Illegal exception table start_pc %d", start_pc)); + } + if (end_pc != code_length) { + if (end_pc > code_length || code_data[end_pc] == 0) { + classError(String.format("Illegal exception table end_pc %d", end_pc)); + } + } + if (handler_pc >= code_length || code_data[handler_pc] == 0) { + classError(String.format("Illegal exception table handler_pc %d", handler_pc)); + } + int catch_type_index = exhandler[3]; + if (catch_type_index != 0) { + VerificationType catch_type = cp_index_to_type(catch_type_index, cp); + VerificationType throwable = VerificationType.reference_type(java_lang_Throwable); + boolean is_subclass = throwable.is_assignable_from(catch_type, this); + if (!is_subclass) { + verifyError(String.format("Catch type is not a subclass of Throwable in exception handler %d", handler_pc)); + } + } + if (start_pc < minmax[0]) minmax[0] = start_pc; + if (end_pc > minmax[1]) minmax[1] = end_pc; + } + } + + void verify_local_variable_table(int code_length, byte[] code_data) { + for (var lvte : _method.localVariableTable()) { + int start_bci = lvte.startPc(); + int length = lvte.length(); + if (start_bci >= code_length || code_data[start_bci] == 0) { + classError(String.format("Illegal local variable table start_pc %d", start_bci)); + } + int end_bci = start_bci + length; + if (end_bci != code_length) { + if (end_bci >= code_length || code_data[end_bci] == 0) { + classError(String.format("Illegal local variable table length %d", length)); + } + } + } + } + + int verify_stackmap_table(int stackmap_index, int bci, VerificationFrame current_frame, VerificationTable stackmap_table, boolean no_control_flow) { + if (stackmap_index < stackmap_table.get_frame_count()) { + int this_offset = stackmap_table.get_offset(stackmap_index); + if (no_control_flow && this_offset > bci) { + verifyError("Expecting a stack map frame"); + } + if (this_offset == bci) { + boolean matches = stackmap_table.match_stackmap(current_frame, this_offset, stackmap_index, !no_control_flow, true); + if (!matches) { + verifyError("Instruction type does not match stack map"); + } + stackmap_index++; + } else if (this_offset < bci) { + classError(String.format("Bad stack map offset %d", this_offset)); + } + } else if (no_control_flow) { + verifyError("Expecting a stack map frame"); + } + return stackmap_index; + } + + void verify_exception_handler_targets(int bci, boolean this_uninit, VerificationFrame current_frame, VerificationTable stackmap_table) { + var cp = _method.constantPool(); + for(var exhandler : _method.exceptionTable()) { + int start_pc = exhandler[0]; + int end_pc = exhandler[1]; + int handler_pc = exhandler[2]; + int catch_type_index = exhandler[3]; + if(bci >= start_pc && bci < end_pc) { + int flags = current_frame.flags(); + if (this_uninit) { flags |= FLAG_THIS_UNINIT; } + VerificationFrame new_frame = current_frame.frame_in_exception_handler(flags); + if (catch_type_index != 0) { + VerificationType catch_type = cp_index_to_type(catch_type_index, cp); + new_frame.push_stack(catch_type); + } else { + VerificationType throwable = VerificationType.reference_type(java_lang_Throwable); + new_frame.push_stack(throwable); + } + boolean matches = stackmap_table.match_stackmap(new_frame, handler_pc, true, false); + if (!matches) { + verifyError(String.format("Stack map does not match the one at exception handler %d", handler_pc)); + } + } + } + } + + void verify_cp_index(int bci, ConstantPoolWrapper cp, int index) { + int nconstants = cp.entryCount(); + if ((index <= 0) || (index >= nconstants)) { + verifyError(String.format("Illegal constant pool index %d", index)); + } + } + + void verify_cp_type(int bci, int index, ConstantPoolWrapper cp, int types) { + verify_cp_index(bci, cp, index); + int tag = cp.tagAt(index); + if ((types & (1 << tag))== 0) { + verifyError(String.format("Illegal type at constant pool entry %d", index)); + } + } + + void verify_cp_class_type(int bci, int index, ConstantPoolWrapper cp) { + verify_cp_index(bci, cp, index); + int tag = cp.tagAt(index); + if (tag != JVM_CONSTANT_Class) { + verifyError(String.format("Illegal type at constant pool entry %d", index)); + } + } + + void verify_ldc(int opcode, int index, VerificationFrame current_frame, ConstantPoolWrapper cp, int bci) { + verify_cp_index(bci, cp, index); + int tag = cp.tagAt(index); + int types = 0; + if (opcode == Classfile.LDC || opcode == Classfile.LDC_W) { + types = (1 << JVM_CONSTANT_Integer) | (1 << JVM_CONSTANT_Float) + | (1 << JVM_CONSTANT_String) | (1 << JVM_CONSTANT_Class) + | (1 << JVM_CONSTANT_MethodHandle) | (1 << JVM_CONSTANT_MethodType) + | (1 << JVM_CONSTANT_Dynamic); + verify_cp_type(bci, index, cp, types); + } else { + if (opcode != Classfile.LDC2_W) verifyError("must be ldc2_w"); + types = (1 << JVM_CONSTANT_Double) | (1 << JVM_CONSTANT_Long) | (1 << JVM_CONSTANT_Dynamic); + verify_cp_type(bci, index, cp, types); + } + switch (tag) { + case JVM_CONSTANT_Utf8 -> current_frame.push_stack(object_type()); + case JVM_CONSTANT_String -> current_frame.push_stack(VerificationType.reference_type(java_lang_String)); + case JVM_CONSTANT_Class -> current_frame.push_stack(VerificationType.reference_type(java_lang_Class)); + case JVM_CONSTANT_Integer -> current_frame.push_stack(VerificationType.integer_type); + case JVM_CONSTANT_Float -> current_frame.push_stack(VerificationType.float_type); + case JVM_CONSTANT_Double -> current_frame.push_stack_2(VerificationType.double_type, VerificationType.double2_type); + case JVM_CONSTANT_Long -> current_frame.push_stack_2(VerificationType.long_type, VerificationType.long2_type); + case JVM_CONSTANT_MethodHandle -> current_frame.push_stack(VerificationType.reference_type(java_lang_invoke_MethodHandle)); + case JVM_CONSTANT_MethodType -> current_frame.push_stack(VerificationType.reference_type(java_lang_invoke_MethodType)); + case JVM_CONSTANT_Dynamic -> { + String constant_type = cp.dynamicConstantSignatureAt(index); + if (!VerificationSignature.isValidTypeSignature(constant_type)) verifyError("Invalid type for dynamic constant"); + VerificationType[] v_constant_type = new VerificationType[2]; + var sig_stream = new VerificationSignature(constant_type, false, this); + int n = change_sig_to_verificationType(sig_stream, v_constant_type, 0); + int opcode_n = (opcode == Classfile.LDC2_W ? 2 : 1); + if (n != opcode_n) { + types &= ~(1 << JVM_CONSTANT_Dynamic); + verify_cp_type(bci, index, cp, types); + } + for (int i = 0; i < n; i++) { + current_frame.push_stack(v_constant_type[i]); + } + } + default -> verifyError("Invalid index in ldc"); + } + } + + void verify_switch(RawBytecodeHelper bcs, int code_length, byte[] code_data, VerificationFrame current_frame, VerificationTable stackmap_table) { + int bci = bcs.bci; + int aligned_bci = VerificationBytecodes.align(bci + 1); + // 4639449 & 4647081: padding bytes must be 0 + if (_klass.majorVersion() < NONZERO_PADDING_BYTES_IN_SWITCH_MAJOR_VERSION) { + int padding_offset = 1; + while ((bci + padding_offset) < aligned_bci) { + if (_method.codeArray()[bci + padding_offset] != 0) { + verifyError("Nonzero padding byte in lookupswitch or tableswitch"); + } + padding_offset++; + } + } + int default_ofset = bcs.getInt(aligned_bci); + int keys, delta; + current_frame.pop_stack(VerificationType.integer_type); + if (bcs.rawCode == Classfile.TABLESWITCH) { + int low = bcs.getInt(aligned_bci + 4); + int high = bcs.getInt(aligned_bci + 2*4); + if (low > high) { + verifyError("low must be less than or equal to high in tableswitch"); + } + keys = high - low + 1; + if (keys < 0) { + verifyError("too many keys in tableswitch"); + } + delta = 1; + } else { + // Make sure that the lookupswitch items are sorted + keys = bcs.getInt(aligned_bci + 4); + if (keys < 0) { + verifyError("number of keys in lookupswitch less than 0"); + } + delta = 2; + for (int i = 0; i < (keys - 1); i++) { + int this_key = bcs.getInt(aligned_bci + (2+2*i)*4); + int next_key = bcs.getInt(aligned_bci + (2+2*i+2)*4); + if (this_key >= next_key) { + verifyError("Bad lookupswitch instruction"); + } + } + } + int target = bci + default_ofset; + stackmap_table.check_jump_target(current_frame, target); + for (int i = 0; i < keys; i++) { + aligned_bci = VerificationBytecodes.align(bcs.bci + 1); + target = bci + bcs.getInt(aligned_bci + (3+i*delta)*4); + stackmap_table.check_jump_target(current_frame, target); + } + } + + void verify_field_instructions(RawBytecodeHelper bcs, VerificationFrame current_frame, ConstantPoolWrapper cp, boolean allow_arrays) { + int index = bcs.getIndexU2(); + verify_cp_type(bcs.bci, index, cp, 1 << JVM_CONSTANT_Fieldref); + String field_name = cp.refNameAt(index); + String field_sig = cp.refSignatureAt(index); + if (!VerificationSignature.isValidTypeSignature(field_sig)) verifyError("Invalid field signature"); + VerificationType ref_class_type = cp_ref_index_to_type(index, cp); + if (!ref_class_type.is_object() && + (!allow_arrays || !ref_class_type.is_array())) { + verifyError(String.format("Expecting reference to class in class %s at constant pool index %d", _klass.thisClassName(), index)); + } + VerificationType target_class_type = ref_class_type; + VerificationType[] field_type = new VerificationType[2]; + var sig_stream = new VerificationSignature(field_sig, false, this); + VerificationType stack_object_type = null; + int n = change_sig_to_verificationType(sig_stream, field_type, 0); + boolean is_assignable; + switch (bcs.rawCode) { + case Classfile.GETSTATIC -> { + for (int i = 0; i < n; i++) { + current_frame.push_stack(field_type[i]); + } + } + case Classfile.PUTSTATIC -> { + for (int i = n - 1; i >= 0; i--) { + current_frame.pop_stack(field_type[i]); + } + } + case Classfile.GETFIELD -> { + stack_object_type = current_frame.pop_stack( + target_class_type); + for (int i = 0; i < n; i++) { + current_frame.push_stack(field_type[i]); + } + } + case Classfile.PUTFIELD -> { + for (int i = n - 1; i >= 0; i--) { + current_frame.pop_stack(field_type[i]); + } + stack_object_type = current_frame.pop_stack(); + if (stack_object_type.is_uninitialized_this(this) && + target_class_type.equals(current_type()) && + _klass.findField(field_name, field_sig)) { + stack_object_type = current_type(); + } + is_assignable = target_class_type.is_assignable_from(stack_object_type, this); + if (!is_assignable) { + verifyError("Bad type on operand stack in putfield"); + } + } + default -> verifyError("Should not reach here"); + } + } + + // Return TRUE if all code paths starting with start_bc_offset end in + // bytecode athrow or loop. + boolean ends_in_athrow(int start_bc_offset) { + log_info("unimplemented VerifierImpl.ends_in_athrow"); + return true; + } + + boolean verify_invoke_init(RawBytecodeHelper bcs, int ref_class_index, VerificationType ref_class_type, + VerificationFrame current_frame, int code_length, boolean in_try_block, + boolean this_uninit, ConstantPoolWrapper cp, VerificationTable stackmap_table) { + int bci = bcs.bci; + VerificationType type = current_frame.pop_stack(VerificationType.reference_check); + if (type.is_uninitialized_this(this)) { + String superk_name = current_class().superclassName(); + if (!current_class().thisClassName().equals(ref_class_type.name()) && + !superk_name.equals(ref_class_type.name())) { + verifyError("Bad method call"); + } + if (in_try_block) { + for(var exhandler : _method.exceptionTable()) { + int start_pc = exhandler[0]; + int end_pc = exhandler[1]; + + if (bci >= start_pc && bci < end_pc) { + if (!ends_in_athrow(exhandler[2])) { + verifyError("Bad method call from after the start of a try block"); + } + } + } + verify_exception_handler_targets(bci, true, current_frame, stackmap_table); + } + current_frame.initialize_object(type, current_type()); + this_uninit = true; + } else if (type.is_uninitialized()) { + int new_offset = type.bci(this); + if (new_offset > (code_length - 3) || (_method.codeArray()[new_offset] & 0xff) != Classfile.NEW) { + verifyError("Expecting new instruction"); + } + int new_class_index = bcs.getIndexU2Raw(new_offset + 1); + verify_cp_class_type(bci, new_class_index, cp); + VerificationType new_class_type = cp_index_to_type( + new_class_index, cp); + if (!new_class_type.equals(ref_class_type)) { + verifyError("Call to wrong method"); + } + if (in_try_block) { + verify_exception_handler_targets(bci, this_uninit, current_frame, + stackmap_table); + } + current_frame.initialize_object(type, new_class_type); + } else { + verifyError("Bad operand type when invoking "); + } + return this_uninit; + } + + static boolean is_same_or_direct_interface(VerificationWrapper klass, VerificationType klass_type, VerificationType ref_class_type) { + if (ref_class_type.equals(klass_type)) return true; + for (String k_name : klass.interfaceNames()) { + if (ref_class_type.equals(VerificationType.reference_type(k_name))) { + return true; + } + } + return false; + } + + boolean verify_invoke_instructions(RawBytecodeHelper bcs, int code_length, VerificationFrame current_frame, boolean in_try_block, boolean this_uninit, VerificationType return_type, ConstantPoolWrapper cp, VerificationTable stackmap_table) { + // Make sure the constant pool item is the right type + int index = bcs.getIndexU2(); + int opcode = bcs.rawCode; + int types = 0; + switch (opcode) { + case Classfile.INVOKEINTERFACE: + types = 1 << JVM_CONSTANT_InterfaceMethodref; + break; + case Classfile.INVOKEDYNAMIC: + types = 1 << JVM_CONSTANT_InvokeDynamic; + break; + case Classfile.INVOKESPECIAL: + case Classfile.INVOKESTATIC: + types = (_klass.majorVersion() < STATIC_METHOD_IN_INTERFACE_MAJOR_VERSION) ? + (1 << JVM_CONSTANT_Methodref) : + ((1 << JVM_CONSTANT_InterfaceMethodref) | (1 << JVM_CONSTANT_Methodref)); + break; + default: + types = 1 << JVM_CONSTANT_Methodref; + } + verify_cp_type(bcs.bci, index, cp, types); + String method_name = cp.refNameAt(index); + String method_sig = cp.refSignatureAt(index); + if (!VerificationSignature.isValidMethodSignature(method_sig)) verifyError("Invalid method signature"); + VerificationType ref_class_type = null; + if (opcode == Classfile.INVOKEDYNAMIC) { + if (_klass.majorVersion() < INVOKEDYNAMIC_MAJOR_VERSION) { + classError(String.format("invokedynamic instructions not supported by this class file version (%d), class %s", _klass.majorVersion(), _klass.thisClassName())); + } + } else { + ref_class_type = cp_ref_index_to_type(index, cp); + } + String sig = cp.refSignatureAt(index); + sig_as_verification_types mth_sig_verif_types; + ArrayList verif_types = new ArrayList<>(10); + mth_sig_verif_types = new sig_as_verification_types(verif_types); + create_method_sig_entry(mth_sig_verif_types, sig); + int nargs = mth_sig_verif_types.num_args(); + int bci = bcs.bci; + if (opcode == Classfile.INVOKEINTERFACE) { + if ((_method.codeArray()[bci+3] & 0xff) != (nargs+1)) { + verifyError("Inconsistent args count operand in invokeinterface"); + } + if ((_method.codeArray()[bci+4] & 0xff) != 0) { + verifyError("Fourth operand byte of invokeinterface must be zero"); + } + } + if (opcode == Classfile.INVOKEDYNAMIC) { + if ((_method.codeArray()[bci+3] & 0xff) != 0 || (_method.codeArray()[bci+4] & 0xff) != 0) { + verifyError("Third and fourth operand bytes of invokedynamic must be zero"); + } + } + if (method_name.charAt(0) == JVM_SIGNATURE_SPECIAL) { + if (opcode != Classfile.INVOKESPECIAL || + !object_initializer_name.equals(method_name)) { + verifyError("Illegal call to internal method"); + } + } else if (opcode == Classfile.INVOKESPECIAL + && !is_same_or_direct_interface(current_class(), current_type(), ref_class_type) + && !ref_class_type.equals(VerificationType.reference_type( + current_class().superclassName()))) { + boolean have_imr_indirect = cp.tagAt(index) == JVM_CONSTANT_InterfaceMethodref; + boolean subtype = ref_class_type.is_assignable_from(current_type(), this); + if (!subtype) { + verifyError("Bad invokespecial instruction: current class isn't assignable to reference class."); + } else if (have_imr_indirect) { + verifyError("Bad invokespecial instruction: interface method reference is in an indirect superinterface."); + } + + } + ArrayList sig_verif_types = mth_sig_verif_types.sig_verif_types(); + if (sig_verif_types == null) verifyError("Missing signature's array of verification types"); + for (int i = nargs - 1; i >= 0; i--) { // Run backwards + current_frame.pop_stack(sig_verif_types.get(i)); + } + if (opcode != Classfile.INVOKESTATIC && + opcode != Classfile.INVOKEDYNAMIC) { + if (object_initializer_name.equals(method_name)) { // method + this_uninit = verify_invoke_init(bcs, index, ref_class_type, current_frame, + code_length, in_try_block, this_uninit, cp, stackmap_table); + } else { + switch (opcode) { + case Classfile.INVOKESPECIAL -> + current_frame.pop_stack(current_type()); + case Classfile.INVOKEVIRTUAL -> { + VerificationType stack_object_type = + current_frame.pop_stack(ref_class_type); + if (current_type() != stack_object_type) { + cp.classNameAt(cp.refClassIndexAt(index)); + } + } + default -> { + if (opcode != Classfile.INVOKEINTERFACE) + verifyError("Unexpected opcode encountered"); + current_frame.pop_stack(ref_class_type); + } + } + } + } + int sig_verif_types_len = sig_verif_types.size(); + if (sig_verif_types_len > nargs) { // There's a return type + if (object_initializer_name.equals(method_name)) { + verifyError("Return type must be void in method"); + } + + if (sig_verif_types_len > nargs + 2) verifyError("Signature verification types array return type is bogus"); + for (int i = nargs; i < sig_verif_types_len; i++) { + if (!(i == nargs || sig_verif_types.get(i).is_long2() || sig_verif_types.get(i).is_double2())) verifyError("Unexpected return verificationType"); + current_frame.push_stack(sig_verif_types.get(i)); + } + } + return this_uninit; + } + + VerificationType get_newarray_type(int index, int bci) { + String[] from_bt = new String[] { + null, null, null, null, "[Z", "[C", "[F", "[D", "[B", "[S", "[I", "[J", + }; + if (index < T_BOOLEAN.type || index > T_LONG.type) { + verifyError("Illegal newarray instruction"); + } + String sig = from_bt[index]; + return VerificationType.reference_type(sig); + } + + void verify_anewarray(int bci, int index, ConstantPoolWrapper cp, VerificationFrame current_frame) { + verify_cp_class_type(bci, index, cp); + current_frame.pop_stack(VerificationType.integer_type); + VerificationType component_type = cp_index_to_type(index, cp); + int length; + String arr_sig_str; + if (component_type.is_array()) { // it's an array + String component_name = component_type.name(); + length = component_name.length(); + if (length > MAX_ARRAY_DIMENSIONS && + component_name.charAt(MAX_ARRAY_DIMENSIONS - 1) == JVM_SIGNATURE_ARRAY) { + verifyError("Illegal anewarray instruction, array has more than 255 dimensions"); + } + length++; + arr_sig_str = String.format("%c%s", JVM_SIGNATURE_ARRAY, component_name); + if (arr_sig_str.length() != length) verifyError("Unexpected number of characters in string"); + } else { // it's an object or interface + String component_name = component_type.name(); + length = component_name.length() + 3; + arr_sig_str = String.format("%c%c%s;", JVM_SIGNATURE_ARRAY, JVM_SIGNATURE_CLASS, component_name); + if (arr_sig_str.length() != length) verifyError("Unexpected number of characters in string"); + } + VerificationType new_array_type = VerificationType.reference_type(arr_sig_str); + current_frame.push_stack(new_array_type); + } + + void verify_iload(int index, VerificationFrame current_frame) { + current_frame.get_local( + index, VerificationType.integer_type); + current_frame.push_stack( + VerificationType.integer_type); + } + + void verify_lload(int index, VerificationFrame current_frame) { + current_frame.get_local_2( + index, VerificationType.long_type, + VerificationType.long2_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + } + + void verify_fload(int index, VerificationFrame current_frame) { + current_frame.get_local( + index, VerificationType.float_type); + current_frame.push_stack( + VerificationType.float_type); + } + + void verify_dload(int index, VerificationFrame current_frame) { + current_frame.get_local_2( + index, VerificationType.double_type, + VerificationType.double2_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + } + + void verify_aload(int index, VerificationFrame current_frame) { + VerificationType type = current_frame.get_local( + index, VerificationType.reference_check); + current_frame.push_stack(type); + } + + void verify_istore(int index, VerificationFrame current_frame) { + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.set_local( + index, VerificationType.integer_type); + } + + void verify_lstore(int index, VerificationFrame current_frame) { + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.set_local_2( + index, VerificationType.long_type, + VerificationType.long2_type); + } + + void verify_fstore(int index, VerificationFrame current_frame) { + current_frame.pop_stack( + VerificationType.float_type); + current_frame.set_local( + index, VerificationType.float_type); + } + + void verify_dstore(int index, VerificationFrame current_frame) { + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.set_local_2( + index, VerificationType.double_type, + VerificationType.double2_type); + } + + void verify_astore(int index, VerificationFrame current_frame) { + VerificationType type = current_frame.pop_stack( + VerificationType.reference_check); + current_frame.set_local(index, type); + } + + void verify_iinc(int index, VerificationFrame current_frame) { + VerificationType type = current_frame.get_local( + index, VerificationType.integer_type); + current_frame.set_local(index, type); + } + + void verify_return_value(VerificationType return_type, VerificationType type, int bci, VerificationFrame current_frame) { + if (return_type.is_bogus()) { + verifyError("Method expects a return value"); + } + boolean match = return_type.is_assignable_from(type, this); + if (!match) { + verifyError("Bad return type"); + } + } + + private void dumpMethod() { + if (_logger != null) ClassPrinter.toTree(_method.m, ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES).toYaml(_logger); + } + + void verifyError(String msg) { + dumpMethod(); + throw new VerifyError(String.format("%s at %s.%s%s @%d %s", msg, _klass.thisClassName(), _method.name(), _method.descriptor(), bci, errorContext)); + } + + void verifyError(String msg, VerificationFrame from, VerificationFrame target) { + dumpMethod(); + throw new VerifyError(String.format("%s at %s.%s%s @%d %s%n while assigning %s%n to %s", msg, _klass.thisClassName(), _method.name(), _method.descriptor(), bci, errorContext, from, target)); + } + + void classError(String msg) { + dumpMethod(); + throw new ClassFormatError(String.format("%s at %s.%s%s", msg, _klass.thisClassName(), _method.name(), _method.descriptor())); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ArrayLoadInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ArrayLoadInstruction.java new file mode 100644 index 0000000000000..f892bcbc9fc18 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ArrayLoadInstruction.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models an array load instruction in the {@code code} array of a {@code Code} + * attribute. Corresponding opcodes will have a {@code kind} of {@link + * Opcode.Kind#ARRAY_LOAD}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface ArrayLoadInstruction extends Instruction + permits AbstractInstruction.UnboundArrayLoadInstruction { + /** + * {@return the component type of the array} + */ + TypeKind typeKind(); + + /** + * {@return an array load instruction} + * + * @param op the opcode for the specific type of array load instruction, + * which must be of kind {@link Opcode.Kind#ARRAY_LOAD} + */ + static ArrayLoadInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.ARRAY_LOAD); + return new AbstractInstruction.UnboundArrayLoadInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ArrayStoreInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ArrayStoreInstruction.java new file mode 100644 index 0000000000000..718780dac5a9e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ArrayStoreInstruction.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models an array store instruction in the {@code code} array of a {@code Code} + * attribute. Corresponding opcodes will have a {@code kind} of {@link + * Opcode.Kind#ARRAY_STORE}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface ArrayStoreInstruction extends Instruction + permits AbstractInstruction.UnboundArrayStoreInstruction { + /** + * {@return the component type of the array} + */ + TypeKind typeKind(); + + /** + * {@return an array store instruction} + * + * @param op the opcode for the specific type of array store instruction, + * which must be of kind {@link Opcode.Kind#ARRAY_STORE} + */ + static ArrayStoreInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.ARRAY_STORE); + return new AbstractInstruction.UnboundArrayStoreInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/BranchInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/BranchInstruction.java new file mode 100644 index 0000000000000..7ad5c1a649a93 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/BranchInstruction.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models a branching instruction (conditional or unconditional) in the {@code + * code} array of a {@code Code} attribute. Corresponding opcodes will have a + * {@code kind} of {@link Opcode.Kind#BRANCH}. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +public sealed interface BranchInstruction extends Instruction + permits AbstractInstruction.BoundBranchInstruction, + AbstractInstruction.UnboundBranchInstruction { + /** + * {@return the target of the branch} + */ + Label target(); + + /** + * {@return a branch instruction} + * + * @param op the opcode for the specific type of branch instruction, + * which must be of kind {@link Opcode.Kind#BRANCH} + */ + static BranchInstruction of(Opcode op, Label target) { + Util.checkKind(op, Opcode.Kind.BRANCH); + return new AbstractInstruction.UnboundBranchInstruction(op, target); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/CharacterRange.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/CharacterRange.java new file mode 100644 index 0000000000000..36132741179c6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/CharacterRange.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.PseudoInstruction; +import jdk.internal.classfile.attribute.CharacterRangeTableAttribute; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; +import jdk.internal.classfile.impl.BoundCharacterRange; + +/** + * A pseudo-instruction which models a single entry in the + * {@link CharacterRangeTableAttribute}. Delivered as a {@link CodeElement} + * during traversal of the elements of a {@link CodeModel}, according to + * the setting of the {@link Classfile.Option#processDebug(boolean)} option. + */ +public sealed interface CharacterRange extends PseudoInstruction + permits AbstractPseudoInstruction.UnboundCharacterRange, BoundCharacterRange { + /** + * {@return the start of the instruction range} + */ + Label startScope(); + + /** + * {@return the end of the instruction range} + */ + Label endScope(); + + /** + * {@return the encoded start of the character range region (inclusive)} + * The value is constructed from the line_number/column_number pair as given + * by {@code line_number << 10 + column_number}, where the source file is + * viewed as an array of (possibly multi-byte) characters. + */ + int characterRangeStart(); + + /** + * {@return the encoded end of the character range region (exclusive)}. + * The value is constructed from the line_number/column_number pair as given + * by {@code line_number << 10 + column_number}, where the source file is + * viewed as an array of (possibly multi-byte) characters. + */ + int characterRangeEnd(); + + /** + * A flags word, indicating the kind of range. Multiple flag bits + * may be set. Valid flags include + * {@link jdk.internal.classfile.Classfile#CRT_STATEMENT}, + * {@link jdk.internal.classfile.Classfile#CRT_BLOCK}, + * {@link jdk.internal.classfile.Classfile#CRT_ASSIGNMENT}, + * {@link jdk.internal.classfile.Classfile#CRT_FLOW_CONTROLLER}, + * {@link jdk.internal.classfile.Classfile#CRT_FLOW_TARGET}, + * {@link jdk.internal.classfile.Classfile#CRT_INVOKE}, + * {@link jdk.internal.classfile.Classfile#CRT_CREATE}, + * {@link jdk.internal.classfile.Classfile#CRT_BRANCH_TRUE}, + * {@link jdk.internal.classfile.Classfile#CRT_BRANCH_FALSE}. + * + * @see jdk.internal.classfile.attribute.CharacterRangeInfo#flags() + * + * @return the flags + */ + int flags(); + + /** + * {@return a character range pseudo-instruction} + * + * @param startScope the start of the instruction range + * @param endScope the end of the instruction range + * @param characterRangeStart the encoded start of the character range region (inclusive) + * @param characterRangeEnd the encoded end of the character range region (exclusive) + * @param flags a flags word, indicating the kind of range + */ + static CharacterRange of(Label startScope, Label endScope, int characterRangeStart, int characterRangeEnd, int flags) { + return new AbstractPseudoInstruction.UnboundCharacterRange(startScope, endScope, characterRangeStart, characterRangeEnd, flags); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ConstantInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ConstantInstruction.java new file mode 100644 index 0000000000000..031aee61b1c74 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ConstantInstruction.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.lang.constant.ConstantDesc; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models a constant-load instruction in the {@code code} array of a {@code + * Code} attribute, including "intrinsic constant" instructions (e.g., {@code + * iconst_0}), "argument constant" instructions (e.g., {@code bipush}), and "load + * constant" instructions (e.g., {@code LDC}). Corresponding opcodes will have + * a {@code kind} of {@link Opcode.Kind#CONSTANT}. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +public sealed interface ConstantInstruction extends Instruction { + + /** + * {@return the constant value} + */ + ConstantDesc constantValue(); + + /** + * {@return the type of the constant} + */ + TypeKind typeKind(); + + /** + * Models an "intrinsic constant" instruction (e.g., {@code + * iconst_0}). + */ + sealed interface IntrinsicConstantInstruction extends ConstantInstruction + permits AbstractInstruction.UnboundIntrinsicConstantInstruction { + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return opcode().primaryTypeKind(); + } + } + + /** + * Models an "argument constant" instruction (e.g., {@code + * bipush}). + */ + sealed interface ArgumentConstantInstruction extends ConstantInstruction + permits AbstractInstruction.BoundArgumentConstantInstruction, + AbstractInstruction.UnboundArgumentConstantInstruction { + + @Override + Integer constantValue(); + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return opcode().primaryTypeKind(); + } + } + + /** + * Models a "load constant" instruction (e.g., {@code + * ldc}). + */ + sealed interface LoadConstantInstruction extends ConstantInstruction + permits AbstractInstruction.BoundLoadConstantInstruction, + AbstractInstruction.UnboundLoadConstantInstruction { + + /** + * {@return the constant value} + */ + LoadableConstantEntry constantEntry(); + + /** + * {@return the type of the constant} + */ + @Override + default TypeKind typeKind() { + return constantEntry().typeKind(); + } + } + + /** + * {@return an intrinsic constant instruction} + * + * @param op the opcode for the specific type of intrinsic constant instruction, + * which must be of kind {@link Opcode.Kind#CONSTANT} + */ + static IntrinsicConstantInstruction ofIntrinsic(Opcode op) { + Util.checkKind(op, Opcode.Kind.CONSTANT); + if (op.constantValue() == null) + throw new IllegalArgumentException(String.format("Wrong opcode specified; found %s, expected xCONST_val", op)); + return new AbstractInstruction.UnboundIntrinsicConstantInstruction(op); + } + + /** + * {@return an argument constant instruction} + * + * @param op the opcode for the specific type of intrinsic constant instruction, + * which must be of kind {@link Opcode.Kind#CONSTANT} + * @param value the constant value + */ + static ArgumentConstantInstruction ofArgument(Opcode op, int value) { + Util.checkKind(op, Opcode.Kind.CONSTANT); + if (op != Opcode.BIPUSH && op != Opcode.SIPUSH) + throw new IllegalArgumentException(String.format("Wrong opcode specified; found %s, expected BIPUSH or SIPUSH", op, op.kind())); + return new AbstractInstruction.UnboundArgumentConstantInstruction(op, value); + } + + /** + * {@return a load constant instruction} + * + * @param op the opcode for the specific type of load constant instruction, + * which must be of kind {@link Opcode.Kind#CONSTANT} + * @param constant the constant value + */ + static LoadConstantInstruction ofLoad(Opcode op, LoadableConstantEntry constant) { + Util.checkKind(op, Opcode.Kind.CONSTANT); + if (op != Opcode.LDC && op != Opcode.LDC_W && op != Opcode.LDC2_W) + throw new IllegalArgumentException(String.format("Wrong opcode specified; found %s, expected LDC, LDC_W or LDC2_W", op, op.kind())); + return new AbstractInstruction.UnboundLoadConstantInstruction(op, constant); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ConvertInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ConvertInstruction.java new file mode 100644 index 0000000000000..a07cde370f8c8 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ConvertInstruction.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.BytecodeHelpers; +import jdk.internal.classfile.impl.Util; + +/** + * Models a primitive conversion instruction in the {@code code} array of a + * {@code Code} attribute, such as {@code i2l}. Corresponding opcodes will have + * a {@code kind} of {@link Opcode.Kind#CONVERT}. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +public sealed interface ConvertInstruction extends Instruction + permits AbstractInstruction.UnboundConvertInstruction { + /** + * {@return the source type to convert from} + */ + TypeKind fromType(); + + /** + * {@return the destination type to convert to} + */ + TypeKind toType(); + + /** + * {@return A conversion instruction} + * + * @param fromType the type to convert from + * @param toType the type to convert to + */ + static ConvertInstruction of(TypeKind fromType, TypeKind toType) { + return of(BytecodeHelpers.convertOpcode(fromType, toType)); + } + + /** + * {@return a conversion instruction} + * + * @param op the opcode for the specific type of conversion instruction, + * which must be of kind {@link Opcode.Kind#CONVERT} + */ + static ConvertInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.CONVERT); + return new AbstractInstruction.UnboundConvertInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ExceptionCatch.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ExceptionCatch.java new file mode 100644 index 0000000000000..38cd07970340f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ExceptionCatch.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.util.Optional; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.PseudoInstruction; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; + +/** + * A pseudo-instruction modeling an entry in the exception table of a code + * attribute. Entries in the exception table model catch and finally blocks. + * Delivered as a {@link CodeElement} when traversing the contents + * of a {@link CodeModel}. + * + * @see PseudoInstruction + */ +public sealed interface ExceptionCatch extends PseudoInstruction + permits AbstractPseudoInstruction.ExceptionCatchImpl { + /** + * {@return the handler for the exception} + */ + Label handler(); + + /** + * {@return the beginning of the instruction range for the guarded instructions} + */ + Label tryStart(); + + /** + * {@return the end of the instruction range for the guarded instructions} + */ + Label tryEnd(); + + /** + * {@return the type of the exception to catch, or empty if this handler is + * unconditional} + */ + Optional catchType(); + + /** + * {@return an exception table pseudo-instruction} + * @param handler the handler for the exception + * @param tryStart the beginning of the instruction range for the gaurded instructions + * @param tryEnd the end of the instruction range for the gaurded instructions + * @param catchTypeEntry the type of exception to catch, or empty if this + * handler is unconditional + */ + static ExceptionCatch of(Label handler, Label tryStart, Label tryEnd, + Optional catchTypeEntry) { + return new AbstractPseudoInstruction.ExceptionCatchImpl(handler, tryStart, tryEnd, catchTypeEntry.orElse(null)); + } + + /** + * {@return an exception table pseudo-instruction for an unconditional handler} + * @param handler the handler for the exception + * @param tryStart the beginning of the instruction range for the gaurded instructions + * @param tryEnd the end of the instruction range for the gaurded instructions + */ + static ExceptionCatch of(Label handler, Label tryStart, Label tryEnd) { + return new AbstractPseudoInstruction.ExceptionCatchImpl(handler, tryStart, tryEnd, (ClassEntry) null); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/FieldInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/FieldInstruction.java new file mode 100644 index 0000000000000..e30e5684f0049 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/FieldInstruction.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.lang.constant.ClassDesc; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.Util; + +/** + * Models a field access instruction in the {@code code} array of a {@code Code} + * attribute. Corresponding opcodes will have a {@code kind} of {@link + * Opcode.Kind#FIELD_ACCESS}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface FieldInstruction extends Instruction + permits AbstractInstruction.BoundFieldInstruction, AbstractInstruction.UnboundFieldInstruction { + /** + * {@return the {@link FieldRefEntry} constant described by this instruction} + */ + FieldRefEntry field(); + + /** + * {@return the class holding the field} + */ + default ClassEntry owner() { + return field().owner(); + } + + /** + * {@return the name of the field} + */ + default Utf8Entry name() { + return field().nameAndType().name(); + } + + /** + * {@return the field descriptor of the field} + */ + default Utf8Entry type() { + return field().nameAndType().type(); + } + + /** + * {@return a symbolic descriptor for the type of the field} + */ + default ClassDesc typeSymbol() { + return ClassDesc.ofDescriptor(type().stringValue()); + } + + /** + * {@return a field access instruction} + * + * @param op the opcode for the specific type of field access instruction, + * which must be of kind {@link Opcode.Kind#FIELD_ACCESS} + * @param field a constant pool entry describing the field + */ + static FieldInstruction of(Opcode op, FieldRefEntry field) { + Util.checkKind(op, Opcode.Kind.FIELD_ACCESS); + return new AbstractInstruction.UnboundFieldInstruction(op, field); + } + + /** + * {@return a field access instruction} + * + * @param op the opcode for the specific type of field access instruction, + * which must be of kind {@link Opcode.Kind#FIELD_ACCESS} + * @param owner the class holding the field + * @param name the name of the field + * @param type the field descriptor + */ + static FieldInstruction of(Opcode op, + ClassEntry owner, + Utf8Entry name, + Utf8Entry type) { + return of(op, owner, TemporaryConstantPool.INSTANCE.nameAndTypeEntry(name, type)); + } + + /** + * {@return a field access instruction} + * + * @param op the opcode for the specific type of field access instruction, + * which must be of kind {@link Opcode.Kind#FIELD_ACCESS} + * @param owner the class holding the field + * @param nameAndType the name and field descriptor of the field + */ + static FieldInstruction of(Opcode op, + ClassEntry owner, + NameAndTypeEntry nameAndType) { + return of(op, TemporaryConstantPool.INSTANCE.fieldRefEntry(owner, nameAndType)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/IncrementInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/IncrementInstruction.java new file mode 100644 index 0000000000000..5b4b6796c077c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/IncrementInstruction.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a local variable increment instruction in the {@code code} array of a + * {@code Code} attribute. Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#INCREMENT}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface IncrementInstruction extends Instruction + permits AbstractInstruction.BoundIncrementInstruction, + AbstractInstruction.UnboundIncrementInstruction { + /** + * {@return the local variable slot to increment} + */ + int slot(); + + /** + * {@return the value to increment by} + */ + int constant(); + + /** + * {@return an increment instruction} + * + * @param slot the local variable slot to increment + * @param constant the value to increment by + */ + static IncrementInstruction of(int slot, int constant) { + return new AbstractInstruction.UnboundIncrementInstruction(slot, constant); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/InvokeDynamicInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/InvokeDynamicInstruction.java new file mode 100644 index 0000000000000..5230b0a3d85a6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/InvokeDynamicInstruction.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import java.util.function.Function; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models an {@code invokedynamic} instruction in the {@code code} array of a + * {@code Code} attribute. Delivered as a {@link CodeElement} when traversing + * the elements of a {@link CodeModel}. + */ +public sealed interface InvokeDynamicInstruction extends Instruction + permits AbstractInstruction.BoundInvokeDynamicInstruction, AbstractInstruction.UnboundInvokeDynamicInstruction { + /** + * {@return an {@link InvokeDynamicEntry} describing the call site} + */ + InvokeDynamicEntry invokedynamic(); + + /** + * {@return the invocation name of the call site} + */ + default Utf8Entry name() { + return invokedynamic().name(); + } + + /** + * {@return the invocation type of the call site} + */ + default Utf8Entry type() { + return invokedynamic().type(); + } + + /** + * {@return the invocation type of the call site, as a symbolic descriptor} + */ + default MethodTypeDesc typeSymbol() { + return MethodTypeDesc.ofDescriptor(type().stringValue()); + } + + /** + * {@return the bootstrap method of the call site} + */ + default DirectMethodHandleDesc bootstrapMethod() { + return invokedynamic().bootstrap() + .bootstrapMethod() + .asSymbol(); + } + + /** + * {@return the bootstrap arguments of the call site} + */ + default List bootstrapArgs() { + return Util.mappedList(invokedynamic().bootstrap().arguments(), new Function<>() { + @Override + public ConstantDesc apply(LoadableConstantEntry loadableConstantEntry) { + return loadableConstantEntry.constantValue(); + } + }); + } + + /** + * {@return an invokedynamic instruction} + * + * @param invokedynamic the constant pool entry describing the call site + */ + static InvokeDynamicInstruction of(InvokeDynamicEntry invokedynamic) { + return new AbstractInstruction.UnboundInvokeDynamicInstruction(invokedynamic); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/InvokeInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/InvokeInstruction.java new file mode 100644 index 0000000000000..a76f2b3ddafd5 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/InvokeInstruction.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.lang.constant.MethodTypeDesc; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodRefEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.Util; + +/** + * Models a method invocation instruction in the {@code code} array of a {@code + * Code} attribute, other than {@code invokedynamic}. Corresponding opcodes + * will have a {@code kind} of {@link Opcode.Kind#INVOKE}. Delivered as a + * {@link CodeElement} when traversing the elements of a {@link CodeModel}. + */ +public sealed interface InvokeInstruction extends Instruction + permits AbstractInstruction.BoundInvokeInterfaceInstruction, AbstractInstruction.BoundInvokeInstruction, AbstractInstruction.UnboundInvokeInstruction { + /** + * {@return the {@link MethodRefEntry} or {@link InterfaceMethodRefEntry} + * constant described by this instruction} + */ + MemberRefEntry method(); + + /** + * {@return whether the class holding the method is an interface} + */ + boolean isInterface(); + + /** + * @return for an {@code invokeinterface}, the {@code count} value, as defined in {@jvms 6.5} + */ + int count(); + + /** + * {@return the class holding the method} + */ + default ClassEntry owner() { + return method().owner(); + } + + /** + * {@return the name of the method} + */ + default Utf8Entry name() { + return method().nameAndType().name(); + } + + /** + * {@return the method descriptor of the method} + */ + default Utf8Entry type() { + return method().nameAndType().type(); + } + + /** + * {@return a symbolic descriptor for the method type} + */ + default MethodTypeDesc typeSymbol() { + return MethodTypeDesc.ofDescriptor(type().stringValue()); + } + + + /** + * {@return an invocation instruction} + * + * @param op the opcode for the specific type of invocation instruction, + * which must be of kind {@link Opcode.Kind#INVOKE} + * @param method a constant pool entry describing the method + */ + static InvokeInstruction of(Opcode op, MemberRefEntry method) { + Util.checkKind(op, Opcode.Kind.INVOKE); + return new AbstractInstruction.UnboundInvokeInstruction(op, method); + } + + /** + * {@return an invocation instruction} + * + * @param op the opcode for the specific type of invocation instruction, + * which must be of kind {@link Opcode.Kind#INVOKE} + * @param owner the class holding the method + * @param name the name of the method + * @param type the method descriptor + * @param isInterface whether the class holding the method is an interface + */ + static InvokeInstruction of(Opcode op, + ClassEntry owner, + Utf8Entry name, + Utf8Entry type, + boolean isInterface) { + return of(op, owner, TemporaryConstantPool.INSTANCE.nameAndTypeEntry(name, type), isInterface); + } + + /** + * {@return an invocation instruction} + * + * @param op the opcode for the specific type of invocation instruction, + * which must be of kind {@link Opcode.Kind#INVOKE} + * @param owner the class holding the method + * @param nameAndType the name and type of the method + * @param isInterface whether the class holding the method is an interface + */ + static InvokeInstruction of(Opcode op, + ClassEntry owner, + NameAndTypeEntry nameAndType, + boolean isInterface) { + return of(op, isInterface + ? TemporaryConstantPool.INSTANCE.interfaceMethodRefEntry(owner, nameAndType) + : TemporaryConstantPool.INSTANCE.methodRefEntry(owner, nameAndType)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/LabelTarget.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/LabelTarget.java new file mode 100644 index 0000000000000..a6948e0736909 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/LabelTarget.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.PseudoInstruction; +import jdk.internal.classfile.impl.LabelImpl; + +/** + * A pseudo-instruction which indicates that the specified label corresponds to + * the current position in the {@code Code} attribute. Delivered as a {@link + * CodeElement} during traversal of the elements of a {@link CodeModel}. + * + * @see PseudoInstruction + */ +public sealed interface LabelTarget extends PseudoInstruction + permits LabelImpl { + Label label(); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/LineNumber.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/LineNumber.java new file mode 100644 index 0000000000000..36d7735d13cd2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/LineNumber.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.PseudoInstruction; +import jdk.internal.classfile.attribute.LineNumberTableAttribute; +import jdk.internal.classfile.impl.LineNumberImpl; + +/** + * A pseudo-instruction which models a single entry in the + * {@link LineNumberTableAttribute}. Delivered as a {@link CodeElement} + * during traversal of the elements of a {@link CodeModel}, according to + * the setting of the {@link Classfile.Option#processLineNumbers(boolean)} option. + * + * @see PseudoInstruction + */ +public sealed interface LineNumber extends PseudoInstruction + permits LineNumberImpl { + + /** + * {@return the line number} + */ + int line(); + + /** + * {@return a line number pseudo-instruction} + * + * @param line the line number + */ + static LineNumber of(int line) { + return LineNumberImpl.of(line); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/LoadInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/LoadInstruction.java new file mode 100644 index 0000000000000..afbc3481cdbb2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/LoadInstruction.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.BytecodeHelpers; +import jdk.internal.classfile.impl.Util; + +/** + * Models a local variable load instruction in the {@code code} array of a + * {@code Code} attribute. Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#LOAD}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface LoadInstruction extends Instruction + permits AbstractInstruction.BoundLoadInstruction, + AbstractInstruction.UnboundLoadInstruction { + int slot(); + + TypeKind typeKind(); + + /** + * {@return a local variable load instruction} + * + * @param kind the type of the value to be loaded + * @param slot the local varaible slot to load from + */ + static LoadInstruction of(TypeKind kind, int slot) { + return of(BytecodeHelpers.loadOpcode(kind, slot), slot); + } + + /** + * {@return a local variable load instruction} + * + * @param op the opcode for the specific type of load instruction, + * which must be of kind {@link Opcode.Kind#LOAD} + * @param slot the local varaible slot to load from + */ + static LoadInstruction of(Opcode op, int slot) { + Util.checkKind(op, Opcode.Kind.LOAD); + return new AbstractInstruction.UnboundLoadInstruction(op, slot); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/LocalVariable.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/LocalVariable.java new file mode 100644 index 0000000000000..e60acac19c275 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/LocalVariable.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.lang.constant.ClassDesc; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.PseudoInstruction; +import jdk.internal.classfile.attribute.LocalVariableTableAttribute; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; +import jdk.internal.classfile.impl.BoundLocalVariable; +import jdk.internal.classfile.impl.TemporaryConstantPool; + +/** + * A pseudo-instruction which models a single entry in the + * {@link LocalVariableTableAttribute}. Delivered as a {@link CodeElement} + * during traversal of the elements of a {@link CodeModel}, according to + * the setting of the {@link Classfile.Option#processDebug(boolean)} option. + * + * @see PseudoInstruction + */ +public sealed interface LocalVariable extends PseudoInstruction + permits AbstractPseudoInstruction.UnboundLocalVariable, BoundLocalVariable { + /** + * {@return the local variable slot} + */ + int slot(); + + /** + * {@return the local variable name} + */ + Utf8Entry name(); + + /** + * {@return the local variable field descriptor} + */ + Utf8Entry type(); + + /** + * {@return the local variable type, as a symbolic descriptor} + */ + default ClassDesc typeSymbol() { + return ClassDesc.ofDescriptor(type().stringValue()); + } + + /** + * {@return the start range of the local variable scope} + */ + Label startScope(); + + /** + * {@return the end range of the local variable scope} + */ + Label endScope(); + + boolean writeTo(BufWriter buf); + + /** + * {@return a local variable pseudo-instruction} + * + * @param slot the local variable slot + * @param nameEntry the local variable name + * @param descriptorEntry the local variable descriptor + * @param startScope the start range of the local variable scope + * @param endScope the end range of the local variable scope + */ + static LocalVariable of(int slot, Utf8Entry nameEntry, Utf8Entry descriptorEntry, Label startScope, Label endScope) { + return new AbstractPseudoInstruction.UnboundLocalVariable(slot, nameEntry, descriptorEntry, + startScope, endScope); + } + + /** + * {@return a local variable pseudo-instruction} + * + * @param slot the local variable slot + * @param name the local variable name + * @param descriptor the local variable descriptor + * @param startScope the start range of the local variable scope + * @param endScope the end range of the local variable scope + */ + static LocalVariable of(int slot, String name, ClassDesc descriptor, Label startScope, Label endScope) { + return of(slot, + TemporaryConstantPool.INSTANCE.utf8Entry(name), + TemporaryConstantPool.INSTANCE.utf8Entry(descriptor.descriptorString()), + startScope, endScope); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/LocalVariableType.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/LocalVariableType.java new file mode 100644 index 0000000000000..9093e777304b8 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/LocalVariableType.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.BufWriter; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.PseudoInstruction; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; +import jdk.internal.classfile.impl.BoundLocalVariableType; +import jdk.internal.classfile.impl.TemporaryConstantPool; + +/** + * A pseudo-instruction which models a single entry in the {@link + * LocalVariableTypeTableAttribute}. Delivered as a {@link CodeElement} during + * traversal of the elements of a {@link CodeModel}, according to the setting of + * the {@link Classfile.Option#processDebug(boolean)} option. + */ +public sealed interface LocalVariableType extends PseudoInstruction + permits AbstractPseudoInstruction.UnboundLocalVariableType, BoundLocalVariableType { + /** + * {@return the local variable slot} + */ + int slot(); + + /** + * {@return the local variable name} + */ + Utf8Entry name(); + + /** + * {@return the local variable signature} + */ + Utf8Entry signature(); + + /** + * {@return the local variable signature} + */ + default Signature signatureSymbol() { + return Signature.parseFrom(signature().stringValue()); + } + + /** + * {@return the start range of the local variable scope} + */ + Label startScope(); + + /** + * {@return the end range of the local variable scope} + */ + Label endScope(); + + boolean writeTo(BufWriter buf); + + /** + * {@return a local variable type pseudo-instruction} + * + * @param slot the local variable slot + * @param nameEntry the local variable name + * @param signatureEntry the local variable signature + * @param startScope the start range of the local variable scope + * @param endScope the end range of the local variable scope + */ + static LocalVariableType of(int slot, Utf8Entry nameEntry, Utf8Entry signatureEntry, Label startScope, Label endScope) { + return new AbstractPseudoInstruction.UnboundLocalVariableType(slot, nameEntry, signatureEntry, + startScope, endScope); + } + + /** + * {@return a local variable type pseudo-instruction} + * + * @param slot the local variable slot + * @param name the local variable name + * @param signature the local variable signature + * @param startScope the start range of the local variable scope + * @param endScope the end range of the local variable scope + */ + static LocalVariableType of(int slot, String name, Signature signature, Label startScope, Label endScope) { + return of(slot, + TemporaryConstantPool.INSTANCE.utf8Entry(name), + TemporaryConstantPool.INSTANCE.utf8Entry(signature.signatureString()), + startScope, endScope); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/LookupSwitchInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/LookupSwitchInstruction.java new file mode 100644 index 0000000000000..d17c3a66872fe --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/LookupSwitchInstruction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.util.List; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code lookupswitch} instruction in the {@code code} array of a + * {@code Code} attribute. Delivered as a {@link CodeElement} when traversing + * the elements of a {@link CodeModel}. + */ +public sealed interface LookupSwitchInstruction extends Instruction + permits AbstractInstruction.BoundLookupSwitchInstruction, + AbstractInstruction.UnboundLookupSwitchInstruction { + /** + * {@return the target of the default case} + */ + Label defaultTarget(); + + /** + * {@return the cases of the switch} + */ + List cases(); + + /** + * {@return a lookup switch instruction} + * + * @param defaultTarget the default target of the switch + * @param cases the cases of the switch + */ + static LookupSwitchInstruction of(Label defaultTarget, List cases) { + return new AbstractInstruction.UnboundLookupSwitchInstruction(defaultTarget, cases); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/MonitorInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/MonitorInstruction.java new file mode 100644 index 0000000000000..43f6b8799f60d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/MonitorInstruction.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models a {@code monitorenter} or {@code monitorexit} instruction in the + * {@code code} array of a {@code Code} attribute. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +public sealed interface MonitorInstruction extends Instruction + permits AbstractInstruction.UnboundMonitorInstruction { + + /** + * {@return a monitor instruction} + * + * @param op the opcode for the specific type of monitor instruction, + * which must be of kind {@link Opcode.Kind#MONITOR} + */ + static MonitorInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.MONITOR); + return new AbstractInstruction.UnboundMonitorInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/NewMultiArrayInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewMultiArrayInstruction.java new file mode 100644 index 0000000000000..9d22b841a4c1d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewMultiArrayInstruction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code multianewarray} invocation instruction in the {@code code} + * array of a {@code Code} attribute. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ +public sealed interface NewMultiArrayInstruction extends Instruction + permits AbstractInstruction.BoundNewMultidimensionalArrayInstruction, + AbstractInstruction.UnboundNewMultidimensionalArrayInstruction { + + /** + * {@return the type of the array, as a symbolic descriptor} + */ + ClassEntry arrayType(); + + /** + * {@return the number of dimensions of the aray} + */ + int dimensions(); + + /** + * {@return a new multi-dimensional array instruction} + * + * @param arrayTypeEntry the type of the array + * @param dimensions the number of dimensions of the array + */ + static NewMultiArrayInstruction of(ClassEntry arrayTypeEntry, + int dimensions) { + return new AbstractInstruction.UnboundNewMultidimensionalArrayInstruction(arrayTypeEntry, dimensions); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/NewObjectInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewObjectInstruction.java new file mode 100644 index 0000000000000..c138357002447 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewObjectInstruction.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code new} instruction in the {@code code} array of a {@code Code} + * attribute. Delivered as a {@link CodeElement} when traversing the elements + * of a {@link CodeModel}. + */ +public sealed interface NewObjectInstruction extends Instruction + permits AbstractInstruction.BoundNewObjectInstruction, AbstractInstruction.UnboundNewObjectInstruction { + + /** + * {@return the type of object to create} + */ + ClassEntry className(); + + /** + * {@return a new object instruction} + * + * @param className the type of object to create + */ + static NewObjectInstruction of(ClassEntry className) { + return new AbstractInstruction.UnboundNewObjectInstruction(className); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/NewPrimitiveArrayInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewPrimitiveArrayInstruction.java new file mode 100644 index 0000000000000..d2c7ffefaf234 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewPrimitiveArrayInstruction.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code newarray} invocation instruction in the {@code code} + * array of a {@code Code} attribute. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ +public sealed interface NewPrimitiveArrayInstruction extends Instruction + permits AbstractInstruction.BoundNewPrimitiveArrayInstruction, + AbstractInstruction.UnboundNewPrimitiveArrayInstruction { + /** + * {@return the component type of the array} + */ + TypeKind typeKind(); + + /** + * {@return a new primitive array instruction} + * + * @param typeKind the component type of the array + */ + static NewPrimitiveArrayInstruction of(TypeKind typeKind) { + return new AbstractInstruction.UnboundNewPrimitiveArrayInstruction(typeKind); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/NewReferenceArrayInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewReferenceArrayInstruction.java new file mode 100644 index 0000000000000..5782c9d096346 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/NewReferenceArrayInstruction.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code anewarray} invocation instruction in the {@code code} + * array of a {@code Code} attribute. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ +public sealed interface NewReferenceArrayInstruction extends Instruction + permits AbstractInstruction.BoundNewReferenceArrayInstruction, AbstractInstruction.UnboundNewReferenceArrayInstruction { + /** + * {@return the component type of the array} + */ + ClassEntry componentType(); + + /** + * {@return a new reference array instruction} + * + * @param componentType the component type of the array + */ + static NewReferenceArrayInstruction of(ClassEntry componentType) { + return new AbstractInstruction.UnboundNewReferenceArrayInstruction(componentType); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/NopInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/NopInstruction.java new file mode 100644 index 0000000000000..65b288cdcb79d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/NopInstruction.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code nop} invocation instruction in the {@code code} + * array of a {@code Code} attribute. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ +public sealed interface NopInstruction extends Instruction + permits AbstractInstruction.UnboundNopInstruction { + /** + * {@return a no-op instruction} + */ + static NopInstruction of() { + return new AbstractInstruction.UnboundNopInstruction(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/OperatorInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/OperatorInstruction.java new file mode 100644 index 0000000000000..7ef4e6d2db244 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/OperatorInstruction.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models an arithmetic operator instruction in the {@code code} array of a + * {@code Code} attribute. Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#OPERATOR}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface OperatorInstruction extends Instruction + permits AbstractInstruction.UnboundOperatorInstruction { + /** + * {@return the operand type of the instruction} + */ + TypeKind typeKind(); + + /** + * {@return an operator instruction} + * + * @param op the opcode for the specific type of array load instruction, + * which must be of kind {@link Opcode.Kind#OPERATOR} + */ + static OperatorInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.OPERATOR); + return new AbstractInstruction.UnboundOperatorInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ReturnInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ReturnInstruction.java new file mode 100644 index 0000000000000..ee17d758a975c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ReturnInstruction.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.BytecodeHelpers; +import jdk.internal.classfile.impl.Util; + +/** + * Models a return-from-method instruction in the {@code code} array of a + * {@code Code} attribute. Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#RETURN}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface ReturnInstruction extends Instruction + permits AbstractInstruction.UnboundReturnInstruction { + TypeKind typeKind(); + + /** + * {@return a return instruction} + * + * @param typeKind the type of the return instruction + */ + static ReturnInstruction of(TypeKind typeKind) { + return of(BytecodeHelpers.returnOpcode(typeKind)); + } + + /** + * {@return a return instruction} + * + * @param op the opcode for the specific type of return instruction, + * which must be of kind {@link Opcode.Kind#RETURN} + */ + static ReturnInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.RETURN); + return new AbstractInstruction.UnboundReturnInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/StackInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/StackInstruction.java new file mode 100644 index 0000000000000..8a95fe59db671 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/StackInstruction.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.Util; + +/** + * Models a stack manipulation instruction in the {@code code} array of a + * {@code Code} attribute. Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#STACK}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface StackInstruction extends Instruction + permits AbstractInstruction.UnboundStackInstruction { + + /** + * {@return a stack manipulation instruction} + * + * @param op the opcode for the specific type of stack instruction, + * which must be of kind {@link Opcode.Kind#STACK} + */ + static StackInstruction of(Opcode op) { + Util.checkKind(op, Opcode.Kind.STACK); + return new AbstractInstruction.UnboundStackInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/StoreInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/StoreInstruction.java new file mode 100644 index 0000000000000..1d11a29c7d2ab --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/StoreInstruction.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.BytecodeHelpers; +import jdk.internal.classfile.impl.Util; + +/** + * Models a local variable store instruction in the {@code code} array of a + * {@code Code} attribute. Corresponding opcodes will have a {@code kind} of + * {@link Opcode.Kind#STORE}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +public sealed interface StoreInstruction extends Instruction + permits AbstractInstruction.BoundStoreInstruction, AbstractInstruction.UnboundStoreInstruction { + int slot(); + TypeKind typeKind(); + + /** + * {@return a local variable store instruction} + * + * @param kind the type of the value to be stored + * @param slot the local varaible slot to store to + */ + static StoreInstruction of(TypeKind kind, int slot) { + return of(BytecodeHelpers.storeOpcode(kind, slot), slot); + } + + /** + * {@return a local variable store instruction} + * + * @param op the opcode for the specific type of store instruction, + * which must be of kind {@link Opcode.Kind#STORE} + * @param slot the local varaible slot to store to + */ + static StoreInstruction of(Opcode op, int slot) { + Util.checkKind(op, Opcode.Kind.STORE); + return new AbstractInstruction.UnboundStoreInstruction(op, slot); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/SwitchCase.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/SwitchCase.java new file mode 100644 index 0000000000000..c03fe3be677d7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/SwitchCase.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.Label; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a single case in a {@code lookupswitch} or {@code tableswitch} + * instruction. + * + * @see LookupSwitchInstruction + * @see TableSwitchInstruction + */ +public sealed interface SwitchCase + permits AbstractInstruction.SwitchCaseImpl { + + /** {@return the integer value corresponding to this case} */ + int caseValue(); + + /** {@return the branch target corresponding to this case} */ + Label target(); + + /** + * Create a {@linkplain SwitchCase} + * + * @param caseValue the integer value for the case + * @param target the branch target for the case + * @return the {@linkplain SwitchCase} + */ + static SwitchCase of(int caseValue, Label target) { + return new AbstractInstruction.SwitchCaseImpl(caseValue, target); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/TableSwitchInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/TableSwitchInstruction.java new file mode 100644 index 0000000000000..382e298e317c7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/TableSwitchInstruction.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.util.List; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models a {@code tableswitch} instruction in the {@code code} array of a + * {@code Code} attribute. Delivered as a {@link CodeElement} when traversing + * the elements of a {@link CodeModel}. + */ +public sealed interface TableSwitchInstruction extends Instruction + permits AbstractInstruction.BoundTableSwitchInstruction, AbstractInstruction.UnboundTableSwitchInstruction { + /** + * {@return the low value of the switch target range, inclusive} + */ + int lowValue(); + + /** + * {@return the high value of the switch target range, inclusive} + */ + int highValue(); + + /** + * {@return the default target of the switch} + */ + Label defaultTarget(); + + /** + * {@return the cases of the switch} + */ + List cases(); + + /** + * {@return a table switch instruction} + * + * @param lowValue the low value of the switch target range, inclusive + * @param highValue the high value of the switch target range, inclusive + * @param defaultTarget the default target of the switch + * @param cases the cases of the switch + */ + static TableSwitchInstruction of(int lowValue, int highValue, Label defaultTarget, List cases) { + return new AbstractInstruction.UnboundTableSwitchInstruction(lowValue, highValue, defaultTarget, cases); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/ThrowInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/ThrowInstruction.java new file mode 100644 index 0000000000000..928354fef51e7 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/ThrowInstruction.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.impl.AbstractInstruction; + +/** + * Models an {@code athrow} instruction in the {@code code} array of a + * {@code Code} attribute. Delivered as a {@link CodeElement} when traversing + * the elements of a {@link CodeModel}. + */ +public sealed interface ThrowInstruction extends Instruction + permits AbstractInstruction.UnboundThrowInstruction { + + /** + * {@return a throw instruction} + */ + static ThrowInstruction of() { + return new AbstractInstruction.UnboundThrowInstruction(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/instruction/TypeCheckInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/instruction/TypeCheckInstruction.java new file mode 100644 index 0000000000000..19400079b3d9a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/instruction/TypeCheckInstruction.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.instruction; + +import java.lang.constant.ClassDesc; + +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.Util; + +/** + * Models an {@code instanceof} or {@code checkcast} instruction in the {@code + * code} array of a {@code Code} attribute. Delivered as a {@link CodeElement} + * when traversing the elements of a {@link CodeModel}. + */ +public sealed interface TypeCheckInstruction extends Instruction + permits AbstractInstruction.BoundTypeCheckInstruction, + AbstractInstruction.UnboundTypeCheckInstruction { + ClassEntry type(); + + /** + * {@return a type check instruction} + * + * @param op the opcode for the specific type of type check instruction, + * which must be of kind {@link Opcode.Kind#TYPE_CHECK} + * @param type the type against which to check or cast + */ + static TypeCheckInstruction of(Opcode op, ClassEntry type) { + Util.checkKind(op, Opcode.Kind.TYPE_CHECK); + return new AbstractInstruction.UnboundTypeCheckInstruction(op, type); + } + + /** + * {@return a type check instruction} + * + * @param op the opcode for the specific type of type check instruction, + * which must be of kind {@link Opcode.Kind#TYPE_CHECK} + * @param type the type against which to check or cast + */ + static TypeCheckInstruction of(Opcode op, ClassDesc type) { + return of(op, TemporaryConstantPool.INSTANCE.classEntry(type)); + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/java/lang/constant/ModuleDesc.java b/src/java.base/share/classes/jdk/internal/classfile/java/lang/constant/ModuleDesc.java new file mode 100644 index 0000000000000..ffc2f00b1b178 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/java/lang/constant/ModuleDesc.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.java.lang.constant; + +import static java.util.Objects.requireNonNull; +import jdk.internal.classfile.impl.ModuleDescImpl; +import static jdk.internal.classfile.impl.ModuleDescImpl.*; + +/** + * A nominal descriptor for a {@link Module} constant. + * + *

To create a {@linkplain ModuleDesc} for a module, use {@link #of}. + * + */ +public sealed interface ModuleDesc + permits ModuleDescImpl { + + /** + * Returns a {@linkplain ModuleDesc} for a module, + * given the name of the module. + *

+ * {@jvms 4.2.3} Module names are not encoded in "internal form" like class and interface names, that is, + * the ASCII periods (.) that separate the identifiers in a module name are not replaced by ASCII forward slashes (/). + *

+ * Module names may be drawn from the entire Unicode codespace, subject to the following constraints: + *

    + *
  • A module name must not contain any code point in the range '\u0000' to '\u001F' inclusive. + *
  • The ASCII backslash (\) is reserved for use as an escape character in module names. + * It must not appear in a module name unless it is followed by an ASCII backslash, an ASCII colon (:), or an ASCII at-sign (@). + * The ASCII character sequence \\ may be used to encode a backslash in a module name. + *
  • The ASCII colon (:) and at-sign (@) are reserved for future use in module names. + * They must not appear in module names unless they are escaped. + * The ASCII character sequences \: and \@ may be used to encode a colon and an at-sign in a module name. + *
+ * @param name module name + * @return a {@linkplain ModuleDesc} describing the desired module + * @throws NullPointerException if the argument is {@code null} + * @throws IllegalArgumentException if the name string is not in the + * correct format + */ + static ModuleDesc of(String name) { + validateModuleName(requireNonNull(name)); + return new ModuleDescImpl(name); + } + + /** + * Returns the module name of this {@linkplain ModuleDesc}. + * + * @return the module name + */ + String moduleName(); + + /** + * Compare the specified object with this descriptor for equality. Returns + * {@code true} if and only if the specified object is also a + * {@linkplain ModuleDesc} and both describe the same module. + * + * @param o the other object + * @return whether this descriptor is equal to the other object + */ + @Override + boolean equals(Object o); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/java/lang/constant/PackageDesc.java b/src/java.base/share/classes/jdk/internal/classfile/java/lang/constant/PackageDesc.java new file mode 100644 index 0000000000000..b9ef690521607 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/java/lang/constant/PackageDesc.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.classfile.java.lang.constant; + +import static java.util.Objects.requireNonNull; +import jdk.internal.classfile.impl.PackageDescImpl; +import static jdk.internal.classfile.impl.PackageDescImpl.*; + +/** + * A nominal descriptor for a {@link Package} constant. + * + *

To create a {@linkplain PackageDesc} for a package, use {@link #of} or + * {@link #ofInternalName(String)}. + * + */ +public sealed interface PackageDesc + permits PackageDescImpl { + + /** + * Returns a {@linkplain PackageDesc} for a package, + * given the name of the package, such as {@code "java.lang"}. + *

+ * {@jls 13.1} + * + * @param name the fully qualified (dot-separated) binary package name + * @return a {@linkplain PackageDesc} describing the desired package + * @throws NullPointerException if the argument is {@code null} + * @throws IllegalArgumentException if the name string is not in the + * correct format + */ + static PackageDesc of(String name) { + validateBinaryPackageName(requireNonNull(name)); + return new PackageDescImpl(binaryToInternal(name)); + } + + /** + * Returns a {@linkplain PackageDesc} for a package, + * given the name of the package in internal form, + * such as {@code "java/lang"}. + *

+ * {@jvms 4.2.1} In this internal form, the ASCII periods (.) that normally separate the identifiers + * which make up the binary name are replaced by ASCII forward slashes (/). + * @param name the fully qualified class name, in internal (slash-separated) form + * @return a {@linkplain PackageDesc} describing the desired package + * @throws NullPointerException if the argument is {@code null} + * @throws IllegalArgumentException if the name string is not in the + * correct format + */ + static PackageDesc ofInternalName(String name) { + validateInternalPackageName(requireNonNull(name)); + return new PackageDescImpl(name); + } + + /** + * Returns the fully qualified (slash-separated) internal package name + * of this {@linkplain PackageDesc}. + * + * @return the package name, or the empty string for the + * default package + */ + String packageInternalName(); + + /** + * Returns the fully qualified (dot-separated) binary package name + * of this {@linkplain PackageDesc}. + * + * @return the package name, or the empty string for the + * default package + */ + default String packageName() { + return internalToBinary(packageInternalName()); + } + + /** + * Compare the specified object with this descriptor for equality. Returns + * {@code true} if and only if the specified object is also a + * {@linkplain PackageDesc} and both describe the same package. + * + * @param o the other object + * @return whether this descriptor is equal to the other object + */ + @Override + boolean equals(Object o); +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/package-info.java b/src/java.base/share/classes/jdk/internal/classfile/package-info.java new file mode 100644 index 0000000000000..5cd82c9c16604 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/package-info.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + *

Classfile parsing, generation, and transformation

+ * The {@code jdk.internal.classfile} package contains classes for reading, writing, and + * modifying Java class files, as specified in Chapter 4 of the Java + * Java Virtual Machine Specification. + * + *

Reading classfiles

+ * The main class for reading classfiles is {@link jdk.internal.classfile.ClassModel}; we + * convert bytes into a {@link jdk.internal.classfile.ClassModel} with {@link + * jdk.internal.classfile.Classfile#parse(byte[], jdk.internal.classfile.Classfile.Option[])}: + *

+ * {@snippet lang=java : + * ClassModel cm = Classfile.parse(bytes); + * } + *

+ * There are several additional overloads of {@code parse} that let you specify + * various processing options. + *

+ * A {@link jdk.internal.classfile.ClassModel} is an immutable description of a class + * file. It provides accessor methods to get at class metadata (e.g., {@link + * jdk.internal.classfile.ClassModel#thisClass()}, {@link jdk.internal.classfile.ClassModel#flags()}), + * as well as subordinate classfile entities ({@link jdk.internal.classfile.ClassModel#fields()}, + * {@link jdk.internal.classfile.ClassModel#attributes()}). A {@link + * jdk.internal.classfile.ClassModel} is inflated lazily; most parts of the classfile are + * not parsed until they are actually needed. + *

+ * We can enumerate the names of the fields and methods in a class by: + * {@snippet lang="java" class="PackageSnippets" region="enumerateFieldsMethods1"} + *

+ * When we enumerate the methods, we get a {@link jdk.internal.classfile.MethodModel} for each method; like a + * {@code ClassModel}, it gives us access to method metadata and + * the ability to descend into subordinate entities such as the bytecodes of the + * method body. In this way, a {@code ClassModel} is the root of a + * tree, with children for fields, methods, and attributes, and {@code MethodModel} in + * turn has its own children (attributes, {@code CodeModel}, etc.) + *

+ * Methods like {@link jdk.internal.classfile.ClassModel#methods} allows us to traverse the class structure + * explicitly, going straight to the parts we are interested in. This is useful + * for certain kinds of analysis, but if we wanted to process the whole + * classfile, we may want something more organized. A {@link + * jdk.internal.classfile.ClassModel} also provides us with a view of the classfile as a + * series of class elements, which may include methods, fields, attributes, + * and more, and which can be distinguished with pattern matching. We could + * rewrite the above example as: + * {@snippet lang="java" class="PackageSnippets" region="enumerateFieldsMethods2"} + *

+ * The models returned as elements from traversing {@code ClassModel} can in + * turn be sources of elements. If we wanted to + * traverse a classfile and enumerate all the classes for which we access fields + * and methods, we can pick out the class elements that describe methods, then + * in turn pick out the method elements that describe the code attribute, and + * finally pick out the code elements that describe field access and invocation + * instructions: + * {@snippet lang="java" class="PackageSnippets" region="gatherDependencies1"} + *

+ * This same query could alternately be processed as a stream pipeline over + * class elements: + * {@snippet lang="java" class="PackageSnippets" region="gatherDependencies2"} + * + *

Models and elements

+ * The view of classfiles presented by this API is framed in terms of + * models and elements. Models represent complex structures, + * such as classes, methods, fields, record elements, or the code body of a + * method. Models can be explored either via random-access navigation (such as + * the {@link jdk.internal.classfile.ClassModel#methods()} accessor) or as a linear + * sequence of elements. (Elements can in turn also be models; a {@link + * jdk.internal.classfile.FieldModel} is also an element of a class.) For each model type + * (e.g., {@link jdk.internal.classfile.MethodModel}), there is a corresponding element + * type ({@link jdk.internal.classfile.MethodElement}). Models and elements are immutable + * and are inflated lazily so creating a model does not necessarily require + * processing its entire content. + * + *

The constant pool

+ * Much of the interesting content in a classfile lives in the constant + * pool. {@link jdk.internal.classfile.ClassModel} provides a lazily-inflated, + * read-only view of the constant pool via {@link jdk.internal.classfile.ClassModel#constantPool()}. + * Descriptions of classfile content is often exposed in the form of various + * subtypes of {@link jdk.internal.classfile.constantpool.PoolEntry}, such as {@link + * jdk.internal.classfile.constantpool.ClassEntry} or {@link jdk.internal.classfile.constantpool.Utf8Entry}. + *

+ * Constant pool entries are also exposed through models and elements; in the + * above traversal example, the {@link jdk.internal.classfile.instruction.InvokeInstruction} + * element exposed a method for {@code owner} that corresponds to a {@code + * Constant_Class_info} entry in the constant pool. + * + *

Attributes

+ * Much of the contents of a classfile is stored in attributes; attributes are + * found on classes, methods, fields, record components, and on the {@code Code} + * attribute. Most attributes are surfaced as elements; for example, {@link + * jdk.internal.classfile.attribute.SignatureAttribute} is a {@link + * jdk.internal.classfile.ClassElement}, {@link jdk.internal.classfile.MethodElement}, and {@link + * jdk.internal.classfile.FieldElement} since it can appear in all of those places, and is + * included when iterating the elements of the corresponding model. + *

+ * Some attributes are not surfaced as elements; these are attributes that are + * tightly coupled to -- and logically part of -- other parts of the class file. + * These include the {@code BootstrapMethods}, {@code LineNumberTable}, {@code + * StackMapTable}, {@code LocalVariableTable}, and {@code + * LocalVariableTypeTable} attributes. These are processed by the library and + * treated as part of the structure they are coupled to (the entries of the + * {@code BootstrapMethods} attribute are treated as part of the constant pool; + * line numbers and local variable metadata are modeled as elements of {@link + * jdk.internal.classfile.CodeModel}.) + *

+ * The {@code Code} attribute, in addition to being modeled as a {@link + * jdk.internal.classfile.MethodElement}, is also a model in its own right ({@link + * jdk.internal.classfile.CodeModel}) due to its complex structure. + *

+ * Each standard attribute has an interface (in {@code jdk.internal.classfile.attribute}) + * which exposes the contents of the attribute and provides factories to + * construct the attribute. For example, the {@code Signature} attribute is + * defined by the {@link jdk.internal.classfile.attribute.SignatureAttribute} class, and + * provides accessors for {@link jdk.internal.classfile.attribute.SignatureAttribute#signature()} + * as well as factories taking {@link jdk.internal.classfile.constantpool.Utf8Entry} or + * {@link java.lang.String}. + * + *

Custom attributes

+ * Attributes are converted between their classfile form and their corresponding + * object form via an {@link jdk.internal.classfile.AttributeMapper}. An {@code + * AttributeMapper} provides the + * {@link jdk.internal.classfile.AttributeMapper#readAttribute(AttributedElement, + * ClassReader, int)} method for mapping from the classfile format + * to an attribute instance, and the + * {@link jdk.internal.classfile.AttributeMapper#writeAttribute(jdk.internal.classfile.BufWriter, + * java.lang.Object)} method for mapping back to the classfile format. It also + * contains metadata including the attribute name, the set of classfile entities + * where the attribute is applicable, and whether multiple attributes of the + * same kind are allowed on a single entity. + *

+ * There are built-in attribute mappers (in {@link jdk.internal.classfile.Attributes}) for + * each of the attribute types defined in section {@jvms 4.7} of The Java Virtual + * Machine Specification, as well as several common nonstandard attributes used by the + * JDK such as {@code CharacterRangeTable}. + *

+ * Unrecognized attributes are delivered as elements of type {@link + * jdk.internal.classfile.attribute.UnknownAttribute}, which provide access only to the + * {@code byte[]} contents of the attribute. + *

+ * For nonstandard attributes, user-provided attribute mappers can be specified + * through the use of the {@link + * jdk.internal.classfile.Classfile.Option#attributeMapper(java.util.function.Function)}} + * classfile option. Implementations of custom attributes should extend {@link + * jdk.internal.classfile.CustomAttribute}. + * + *

Options

+ *

+ * {@link jdk.internal.classfile.Classfile#parse(byte[], jdk.internal.classfile.Classfile.Option[])} + * accepts a list of options. {@link jdk.internal.classfile.Classfile.Option} exports some + * static boolean options, as well as factories for more complex options, + * including: + *

    + *
  • {@link jdk.internal.classfile.Classfile.Option#generateStackmap(boolean)} + * -- generate stackmaps (default is true)
  • + *
  • {@link jdk.internal.classfile.Classfile.Option#processDebug(boolean)} + * -- processing of debug information, such as local variable metadata (default is true)
  • + *
  • {@link jdk.internal.classfile.Classfile.Option#processLineNumbers(boolean)} + * -- processing of line numbers (default is true)
  • + *
  • {@link jdk.internal.classfile.Classfile.Option#processUnknownAttributes(boolean)} + * -- processing of unrecognized attributes (default is true)
  • + *
  • {@link jdk.internal.classfile.Classfile.Option#constantPoolSharing(boolean)}} + * -- share constant pool when transforming (default is true)
  • + *
  • {@link jdk.internal.classfile.Classfile.Option#classHierarchyResolver(jdk.internal.classfile.ClassHierarchyResolver)} + * -- specify a custom class hierarchy + * resolver used by stack map generation
  • + *
  • {@link jdk.internal.classfile.Classfile.Option#attributeMapper(java.util.function.Function)} + * -- specify format of custom attributes
  • + *
+ *

+ * Most options allow you to request that certain parts of the classfile be + * skipped during traversal, such as debug information or unrecognized + * attributes. Some options allow you to suppress generation of portions of the + * classfile, such as stack maps. Many of these options are to access + * performance tradeoffs; processing debug information and line numbers has a + * cost (both in writing and reading.) If you don't need this information, you + * can suppress it with options to gain some performance. + * + *

Writing classfiles

+ * Classfile generation is accomplished through builders. For each + * entity type that has a model, there is also a corresponding builder type; + * classes are built through {@link jdk.internal.classfile.ClassBuilder}, methods through + * {@link jdk.internal.classfile.MethodBuilder}, etc. + *

+ * Rather than creating builders directly, builders are provided as an argument + * to a user-provided lambda. To generate the familiar "hello world" program, + * we ask for a class builder, and use that class builder to create method + * builders for the constructor and {@code main} method, and in turn use the + * method builders to create a {@code Code} attribute and use the code builders + * to generate the instructions: + * {@snippet lang="java" class="PackageSnippets" region="helloWorld"} + *

+ * Builders often support multiple ways of expressing the same entity at + * different levels of abstraction. For example, the {@code invokevirtual} + * instruction invoking {@code println} could have been generated with {@link + * jdk.internal.classfile.CodeBuilder#invokevirtual(java.lang.constant.ClassDesc, + * java.lang.String, java.lang.constant.MethodTypeDesc) CodeBuilder.invokevirtual}, {@link + * jdk.internal.classfile.CodeBuilder#invokeInstruction(jdk.internal.classfile.Opcode, + * java.lang.constant.ClassDesc, java.lang.String, java.lang.constant.MethodTypeDesc, + * boolean) CodeBuilder.invokeInstruction}, or {@link + * jdk.internal.classfile.CodeBuilder#with(jdk.internal.classfile.ClassfileElement) + * CodeBuilder.with}. + *

+ * The convenience method {@code CodeBuilder.invokevirtual} behaves as if it calls + * the convenience method {@code CodeBuilder.invokeInstruction}, which in turn behaves + * as if it calls method {@code CodeBuilder.with}. This composing of method calls on the + * builder enables the composing of transforms (as described later). + * + *

Symbolic information

+ * To describe symbolic information for classes and types, the API uses the + * nominal descriptor abstractions from {@code java.lang.constant} such as {@link + * java.lang.constant.ClassDesc} and {@link java.lang.constant.MethodTypeDesc}, + * which is less error-prone than using raw strings. + *

+ * If a constant pool entry has a nominal representation then it provides a + * method returning the corresponding nominal descriptor type e.g. + * method {@link jdk.internal.classfile.constantpool.ClassEntry#asSymbol} returns + * {@code ClassDesc}. + *

+ * Where appropriate builders provide two methods for building an element with + * symbolic information, one accepting nominal descriptors, and the other + * accepting constant pool entries. + * + *

Transforming classfiles

+ * Classfile Processing APIs are most frequently used to combine reading and + * writing into transformation, where a classfile is read, localized changes are + * made, but much of the classfile is passed through unchanged. For each kind + * of builder, {@code XxxBuilder} has a method {@code with(XxxElement)} so that + * elements that we wish to pass through unchanged can be handed directly back + * to the builder. + *

+ * If we wanted to strip out methods whose names starts with "debug", we could + * get an existing {@link jdk.internal.classfile.ClassModel}, build a new classfile that + * provides a {@link jdk.internal.classfile.ClassBuilder}, iterate the elements of the + * original {@link jdk.internal.classfile.ClassModel}, and pass through all of them to + * the builder except the methods we want to drop: + * {@snippet lang="java" class="PackageSnippets" region="stripDebugMethods1"} + *

+ * This hands every class element, except for those corresponding to methods + * whose names start with {@code debug}, back to the builder. Transformations + * can of course be more complicated, diving into method bodies and instructions + * and transforming those as well, but the same structure is repeated at every + * level, since every entity has corresponding model, builder, and element + * abstractions. + *

+ * Transformation can be viewed as a "flatMap" operation on the sequence of + * elements; for every element, we could pass it through unchanged, drop it, or + * replace it with one or more elements. Because transformation is such a + * common operation on classfiles, each model type has a corresponding {@code + * XxxTransform} type (which describes a transform on a sequence of {@code + * XxxElement}) and each builder type has {@code transformYyy} methods for transforming + * its child models. A transform is simply a functional interface that takes a + * builder and an element, and an implementation "flatMap"s elements + * into the builder. We could express the above as: + * {@snippet lang="java" class="PackageSnippets" region="stripDebugMethods2"} + * + *

Lifting transforms

+ * While the second example is only slightly shorter than the first, the + * advantage of expressing transformation in this way is that the transform + * operations can be more easily combined. Suppose we want to redirect + * invocations of static methods on {@code Foo} to the corresponding method on + * {@code Bar} instead. We could express this as a transformation on {@link + * jdk.internal.classfile.CodeElement}: + * {@snippet lang="java" class="PackageSnippets" region="fooToBarTransform"} + *

+ * We can then lift this transformation on code elements into a + * transformation on method elements. This intercepts method elements that + * correspond to a {@code Code} attribute, dives into its code elements, and + * applies the code transform to them, and passes other method elements through + * unchanged: + * {@snippet lang=java : + * MethodTransform mt = MethodTransform.transformingCode(fooToBar); + * } + *

+ * and further lift the transform on method elements into one on class + * elements: + * {@snippet lang=java : + * ClassTransform ct = ClassTransform.transformingMethods(mt); + * } + *

+ * and then transform the classfile: + * {@snippet lang=java : + * byte[] newBytes = Classfile.parse(bytes).transform(ct); + * } + *

+ * This is much more concise (and less error-prone) than the equivalent + * expressed by traversing the classfile structure directly: + * {@snippet lang="java" class="PackageSnippets" region="fooToBarUnrolled"} + * + *

Composing transforms

+ * Transforms on the same type of element can be composed in sequence, where the + * output of the first is fed to the input of the second. Suppose we want to + * instrument all method calls, where we print the name of a method before + * calling it: + * {@snippet lang="java" class="PackageSnippets" region="instrumentCallsTransform"} + *

+ * Then we can compose {@code fooToBar} and {@code instrumentCalls} with {@link + * jdk.internal.classfile.CodeTransform#andThen(jdk.internal.classfile.CodeTransform)}: + *

+ * {@snippet lang=java : + * byte[] newBytes = Classfile.parse(bytes) + * .transform(ClassTransform.transformingMethods( + * MethodTransform.transformingCode( + * fooToBar.andThen(instrumentCalls)))); + * } + * + * Transform {@code instrumentCalls} will receive all code elements produced by + * transform {@code forToBar}, either those code elements from the original classfile + * or replacements (replacing static invocations to {@code Foo} with those to {@code Bar}). + * + *

Constant pool sharing

+ * Transformation doesn't merely handle the logistics of reading, transforming + * elements, and writing. Most of the time when we are transforming a + * classfile, we are making relatively minor changes. To optimize such cases, + * transformation seeds the new classfile with a copy of the constant pool from + * the original classfile; this enables significant optimizations (methods and + * attributes that are not transformed can be processed by bulk-copying their + * bytes, rather than parsing them and regenerating their contents.) If + * constant pool sharing is not desired it can be suppressed + * with the {@link jdk.internal.classfile.Classfile.Option#constantPoolSharing(boolean)} option. + * Such suppression may be beneficial when transformation removes many elements, + * resulting in many unreferenced constant pool entries. + * + *

API conventions

+ *

+ * The API is largely derived from a data model + * for the classfile format, which defines each element kind (which includes models and + * attributes) and its properties. For each element kind, there is a + * corresponding interface to describe that element, and factory methods to + * create that element. Some element kinds also have convenience methods on the + * corresponding builder (e.g., {@link + * jdk.internal.classfile.CodeBuilder#invokevirtual(java.lang.constant.ClassDesc, + * java.lang.String, java.lang.constant.MethodTypeDesc)}). + *

+ * Most symbolic information in elements is represented by constant pool entries + * (for example, the owner of a field is represented by a {@link + * jdk.internal.classfile.constantpool.ClassEntry}.) Factories and builders also + * accept nominal descriptors from {@code java.lang.constant} (e.g., {@link + * java.lang.constant.ClassDesc}.) + * + *

Data model

+ * We define each kind of element by its name, an optional arity indicator (zero + * or more, zero or one, exactly one), and a list of components. The elements + * of a class are fields, methods, and the attributes that can appear on + * classes: + *

+ * {@snippet lang="text" : + * ClassElement = + * FieldModel*(UtfEntry name, Utf8Entry descriptor) + * | MethodModel*(UtfEntry name, Utf8Entry descriptor) + * | ModuleAttribute?(int flags, ModuleEntry moduleName, UtfEntry moduleVersion, + * List requires, List opens, + * List exports, List provides, + * List uses) + * | ModulePackagesAttribute?(List packages) + * | ModuleTargetAttribute?(Utf8Entry targetPlatform) + * | ModuleHashesAttribute?(Utf8Entry algorithm, List hashes) + * | ModuleResolutionAttribute?(int resolutionFlags) + * | SourceFileAttribute?(Utf8Entry sourceFile) + * | SourceDebugExtensionsAttribute?(byte[] contents) + * | CompilationIDAttribute?(Utf8Entry compilationId) + * | SourceIDAttribute?(Utf8Entry sourceId) + * | NestHostAttribute?(ClassEntry nestHost) + * | NestMembersAttribute?(List nestMembers) + * | RecordAttribute?(List components) + * | EnclosingMethodAttribute?(ClassEntry className, NameAndTypeEntry method) + * | InnerClassesAttribute?(List classes) + * | PermittedSubclassesAttribute?(List permittedSubclasses) + * | DeclarationElement* + * } + *

+ * where {@code DeclarationElement} are the elements that are common to all declarations + * (classes, methods, fields) and so are factored out: + * + * {@snippet lang="text" : + * DeclarationElement = + * SignatureAttribute?(Utf8Entry signature) + * | SyntheticAttribute?() + * | DeprecatedAttribute?() + * | RuntimeInvisibleAnnotationsAttribute?(List annotations) + * | RuntimeVisibleAnnotationsAttribute?(List annotations) + * | CustomAttribute* + * | UnknownAttribute* + * } + * + * Fields and methods are models with their own elements. The elements of fields + * and methods are fairly simple; most of the complexity of methods lives in the + * {@link jdk.internal.classfile.CodeModel} (which models the {@code Code} attribute + * along with the code-related attributes: stack map table, local variable table, + * line number table, etc.) + * + * {@snippet lang="text" : + * FieldElement = + * DeclarationElement + * | ConstantValueAttribute?(ConstantValueEntry constant) + * + * MethodElement = + * DeclarationElement + * | CodeModel?() + * | AnnotationDefaultAttribute?(ElementValue defaultValue) + * | MethodParametersAttribute?(List parameters) + * | ExceptionsAttribute?(List exceptions) + * } + * + * {@link jdk.internal.classfile.CodeModel} is unique in that its elements are ordered. + * Elements of {@code Code} include ordinary bytecodes, as well as a number of pseudo-instructions + * representing branch targets, line number metadata, local variable metadata, and + * catch blocks. + * + * {@snippet lang="text" : + * CodeElement = Instruction | PseudoInstruction + * + * Instruction = + * LoadInstruction(TypeKind type, int slot) + * | StoreInstruction(TypeKind type, int slot) + * | IncrementInstruction(int slot, int constant) + * | BranchInstruction(Opcode opcode, Label target) + * | LookupSwitchInstruction(Label defaultTarget, List cases) + * | TableSwitchInstruction(Label defaultTarget, int low, int high, + * List cases) + * | ReturnInstruction(TypeKind kind) + * | ThrowInstruction() + * | FieldInstruction(Opcode opcode, FieldRefEntry field) + * | InvokeInstruction(Opcode opcode, MemberRefEntry method, boolean isInterface) + * | InvokeDynamicInstruction(InvokeDynamicEntry invokedynamic) + * | NewObjectInstruction(ClassEntry className) + * | NewReferenceArrayInstruction(ClassEntry componentType) + * | NewPrimitiveArrayInstruction(TypeKind typeKind) + * | NewMultiArrayInstruction(ClassEntry componentType, int dims) + * | ArrayLoadInstruction(Opcode opcode) + * | ArrayStoreInstruction(Opcode opcode) + * | TypeCheckInstruction(Opcode opcode, ClassEntry className) + * | ConvertInstruction(TypeKind from, TypeKind to) + * | OperatorInstruction(Opcode opcode) + * | ConstantInstruction(ConstantDesc constant) + * | StackInstruction(Opcode opcode) + * | MonitorInstruction(Opcode opcode) + * | NopInstruction() + * + * PseudoInstruction = + * | LabelTarget(Label label) + * | LineNumber(int line) + * | ExceptionCatch(Label tryStart, Label tryEnd, Label handler, ClassEntry exception) + * | LocalVariable(int slot, UtfEntry name, Utf8Entry type, Label startScope, Label endScope) + * | LocalVariableType(int slot, Utf8Entry name, Utf8Entry type, Label startScope, Label endScope) + * | CharacterRange(int rangeStart, int rangeEnd, int flags, Label startScope, Label endScope) + * } + */ +package jdk.internal.classfile; diff --git a/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java b/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java new file mode 100644 index 0000000000000..b26e9e3b887d2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.HashSet; +import java.util.Set; + +import java.lang.reflect.AccessFlag; +import java.util.LinkedList; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; + +import static java.util.stream.Collectors.toSet; +import jdk.internal.classfile.components.ClassRemapper; +import jdk.internal.classfile.components.CodeLocalsShifter; +import jdk.internal.classfile.components.CodeRelabeler; +import jdk.internal.classfile.instruction.ReturnInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; + +class PackageSnippets { + void enumerateFieldsMethods1(byte[] bytes) { + // @start region="enumerateFieldsMethods1" + ClassModel cm = Classfile.parse(bytes); + for (FieldModel fm : cm.fields()) + System.out.printf("Field %s%n", fm.fieldName().stringValue()); + for (MethodModel mm : cm.methods()) + System.out.printf("Method %s%n", mm.methodName().stringValue()); + // @end + } + + void enumerateFieldsMethods2(byte[] bytes) { + // @start region="enumerateFieldsMethods2" + ClassModel cm = Classfile.parse(bytes); + for (ClassElement ce : cm) { + switch (ce) { + case MethodModel mm -> System.out.printf("Method %s%n", mm.methodName().stringValue()); + case FieldModel fm -> System.out.printf("Field %s%n", fm.fieldName().stringValue()); + default -> { } + } + } + // @end + } + + void gatherDependencies1(byte[] bytes) { + // @start region="gatherDependencies1" + ClassModel cm = Classfile.parse(bytes); + Set dependencies = new HashSet<>(); + + for (ClassElement ce : cm) { + if (ce instanceof MethodModel mm) { + for (MethodElement me : mm) { + if (me instanceof CodeModel xm) { + for (CodeElement e : xm) { + switch (e) { + case InvokeInstruction i -> dependencies.add(i.owner().asSymbol()); + case FieldInstruction i -> dependencies.add(i.owner().asSymbol()); + default -> { } + } + } + } + } + } + } + // @end + } + + void gatherDependencies2(byte[] bytes) { + // @start region="gatherDependencies2" + ClassModel cm = Classfile.parse(bytes); + Set dependencies = + cm.elementStream() + .flatMap(ce -> ce instanceof MethodMethod mm ? mm.elementStream() : Stream.empty()) + .flatMap(me -> me instanceof CodeModel com ? com.elementStream() : Stream.empty()) + .mapMulti((xe, c) -> { + switch (xe) { + case InvokeInstruction i -> c.accept(i.owner().asSymbol()); + case FieldInstruction i -> c.accept(i.owner().asSymbol()); + default -> { } + } + }) + .collect(toSet()); + // @end + } + + void writeHelloWorld() { + // @start region="helloWorld" + byte[] bytes = Classfile.build(ClassDesc.of("Hello"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("", MethodTypeDesc.of(ConstantDescs.CD_void), Classfile.ACC_PUBLIC, + mb -> mb.withCode( + b -> b.aload(0) + .invokespecial(ConstantDescs.CD_Object, "", + MethodTypeDesc.of(ConstantDescs.CD_void)) + .returnInstruction(TypeKind.VoidType) + ) + ) + .withMethod("main", MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String.arrayType()), + Classfile.ACC_PUBLIC, + mb -> mb.withFlags(AccessFlag.STATIC, AccessFlag.PUBLIC) + .withCode( + b -> b.getstatic(ClassDesc.of("java.lang.System"), "out", ClassDesc.of("java.io.PrintStream")) + .constantInstruction(Opcode.LDC, "Hello World") + .invokevirtual(ClassDesc.of("java.io.PrintStream"), "println", + MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)) + .returnInstruction(TypeKind.VoidType) + )); + }); + // @end + } + + void stripDebugMethods1(byte[] bytes) { + // @start region="stripDebugMethods1" + ClassModel classModel = Classfile.parse(bytes); + byte[] newBytes = Classfile.build(classModel.thisClass().asSymbol(), + classBuilder -> { + for (ClassElement ce : classModel) { + if (!(ce instanceof MethodModel mm + && mm.methodName().stringValue().startsWith("debug"))) + classBuilder.with(ce); + } + }); + // @end + } + + void stripDebugMethods2(byte[] bytes) { + // @start region="stripDebugMethods2" + ClassTransform ct = (builder, element) -> { + if (!(element instanceof MethodModel mm && mm.methodName().stringValue().startsWith("debug"))) + builder.with(element); + }; + byte[] newBytes = Classfile.parse(bytes).transform(ct); + // @end + } + + void fooToBarTransform() { + // @start region="fooToBarTransform" + CodeTransform fooToBar = (b, e) -> { + if (e instanceof InvokeInstruction i + && i.owner().asInternalName().equals("Foo") + && i.opcode() == Opcode.INVOKESTATIC) + b.invokeInstruction(i.opcode(), ClassDesc.of("Bar"), i.name().stringValue(), i.typeSymbol(), i.isInterface()); + else b.with(e); + }; + // @end + } + + void instrumentCallsTransform() { + // @start region="instrumentCallsTransform" + CodeTransform instrumentCalls = (b, e) -> { + if (e instanceof InvokeInstruction i) { + b.getstatic(ClassDesc.of("java.lang.System"), "out", ClassDesc.of("java.io.PrintStream")) + .constantInstruction(Opcode.LDC, i.name().stringValue()) + .invokevirtual(ClassDesc.of("java.io.PrintStream"), "println", + MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)); + } + b.with(e); + }; + // @end + } + + void fooToBarUnrolled(ClassModel classModel) { + // @start region="fooToBarUnrolled" + byte[] newBytes = Classfile.build(classModel.thisClass().asSymbol(), + classBuilder -> { + for (ClassElement ce : classModel) { + if (ce instanceof MethodModel mm) { + classBuilder.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), + mm.flags().flagsMask(), + methodBuilder -> { + for (MethodElement me : mm) { + if (me instanceof CodeModel xm) { + methodBuilder.withCode(codeBuilder -> { + for (CodeElement e : xm) { + if (e instanceof InvokeInstruction i && i.owner().asInternalName().equals("Foo") + && i.opcode() == Opcode.INVOKESTATIC) + codeBuilder.invokeInstruction(i.opcode(), ClassDesc.of("Bar"), + i.name().stringValue(), i.typeSymbol(), i.isInterface()); + else codeBuilder.with(e); + }}); + } + else + methodBuilder.with(me); + } + }); + } + else + classBuilder.with(ce); + } + }); + // @end + } + + void codeRelabeling(ClassModel classModel) { + // @start region="codeRelabeling" + byte[] newBytes = classModel.transform( + ClassTransform.transformingMethodBodies( + CodeTransform.ofStateful(CodeRelabeler::of))); + // @end + } + + // @start region="classInstrumentation" + byte[] classInstrumentation(ClassModel target, ClassModel instrumentor, Predicate instrumentedMethodsFilter) { + var instrumentorCodeMap = instrumentor.methods().stream() + .filter(instrumentedMethodsFilter) + .collect(Collectors.toMap(mm -> mm.methodName().stringValue() + mm.methodType().stringValue(), mm -> mm.code().orElse(null))); + var targetFieldNames = target.fields().stream().map(f -> f.fieldName().stringValue()).collect(Collectors.toSet()); + var targetMethods = target.methods().stream().map(m -> m.methodName().stringValue() + m.methodType().stringValue()).collect(Collectors.toSet()); + var instrumentorClassRemapper = ClassRemapper.of(Map.of(instrumentor.thisClass().asSymbol(), target.thisClass().asSymbol())); + return target.transform( + ClassTransform.transformingMethods( + instrumentedMethodsFilter, + (mb, me) -> { + if (me instanceof CodeModel targetCodeModel) { + var mm = targetCodeModel.parent().get(); + //instrumented methods code is taken from instrumentor + mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()), + //all references to the instrumentor class are remapped to target class + instrumentorClassRemapper.asCodeTransform() + .andThen((codeBuilder, instrumentorCodeElement) -> { + //all invocations of target methods from instrumentor are inlined + if (instrumentorCodeElement instanceof InvokeInstruction inv + && target.thisClass().asInternalName().equals(inv.owner().asInternalName()) + && mm.methodName().stringValue().equals(inv.name().stringValue()) + && mm.methodType().stringValue().equals(inv.type().stringValue())) { + + //store stacked method parameters into locals + var storeStack = new LinkedList(); + int slot = 0; + if (!mm.flags().has(AccessFlag.STATIC)) + storeStack.add(StoreInstruction.of(TypeKind.ReferenceType, slot++)); + for (var pt : mm.methodTypeSymbol().parameterList()) { + var tk = TypeKind.fromDescriptor(pt.descriptorString()); + storeStack.addFirst(StoreInstruction.of(tk, slot)); + slot += tk.slotSize(); + } + storeStack.forEach(codeBuilder::with); + + //inlined target locals must be shifted based on the actual instrumentor locals + codeBuilder.block(inlinedBlockBuilder -> inlinedBlockBuilder + .transform(targetCodeModel, CodeLocalsShifter.of(mm.flags(), mm.methodTypeSymbol()) + .andThen(CodeRelabeler.of()) + .andThen((innerBuilder, shiftedTargetCode) -> { + //returns must be replaced with jump to the end of the inlined method + if (shiftedTargetCode instanceof ReturnInstruction) + innerBuilder.goto_(inlinedBlockBuilder.breakLabel()); + else + innerBuilder.with(shiftedTargetCode); + }))); + } else + codeBuilder.with(instrumentorCodeElement); + })); + } else + mb.with(me); + }) + .andThen(ClassTransform.endHandler(clb -> + //remaining instrumentor fields and methods are injected at the end + clb.transform(instrumentor, + ClassTransform.dropping(cle -> + !(cle instanceof FieldModel fm + && !targetFieldNames.contains(fm.fieldName().stringValue())) + && !(cle instanceof MethodModel mm + && !"".equals(mm.methodName().stringValue()) + && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue()))) + //and instrumentor class references remapped to target class + .andThen(instrumentorClassRemapper))))); + } + // @end +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java index 5233f04beb7a8..ff13a6a7362ff 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java @@ -107,6 +107,8 @@ public class ClassWriter extends ClassFile { /** Type utilities. */ private Types types; + private Symtab syms; + private Check check; /** @@ -172,6 +174,7 @@ protected ClassWriter(Context context) { target = Target.instance(context); source = Source.instance(context); types = Types.instance(context); + syms = Symtab.instance(context); check = Check.instance(context); fileManager = context.get(JavaFileManager.class); poolWriter = Gen.instance(context).poolWriter; @@ -1633,7 +1636,9 @@ public void writeClassFile(OutputStream out, ClassSymbol c) acount += writeExtraAttributes(c); poolbuf.appendInt(JAVA_MAGIC); - if (preview.isEnabled() && preview.usesPreview(c.sourcefile)) { + if (preview.isEnabled() && preview.usesPreview(c.sourcefile) + // do not write PREVIEW_MINOR_VERSION for classes participating in preview + && !preview.participatesInPreview(syms, c, syms.java_base.unnamedPackage)) { poolbuf.appendChar(ClassFile.PREVIEW_MINOR_VERSION); } else { poolbuf.appendChar(target.minorVersion); diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 38032de11b376..412f574b1d4fa 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -44,6 +44,7 @@ tier1_part3 = \ :jdk_vector_sanity \ java/nio/Buffer \ com/sun/crypto/provider/Cipher \ + jdk/classfile \ sun/nio/cs/ISO8859x.java # When adding tests to tier2, make sure they end up in one of the tier2_partX groups diff --git a/test/jdk/jdk/classfile/AccessFlagsTest.java b/test/jdk/jdk/classfile/AccessFlagsTest.java new file mode 100644 index 0000000000000..1f839d14dc1bd --- /dev/null +++ b/test/jdk/jdk/classfile/AccessFlagsTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile AccessFlags. + * @run junit AccessFlagsTest + */ +import java.util.EnumSet; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.AccessFlags; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.ParameterizedTest; + +class AccessFlagsTest { + + @ParameterizedTest + @EnumSource(names = { "CLASS", "METHOD", "FIELD" }) + void testRandomAccessFlagsConverions(AccessFlag.Location ctx) { + IntFunction intFactory = switch (ctx) { + case CLASS -> AccessFlags::ofClass; + case METHOD -> AccessFlags::ofMethod; + case FIELD -> AccessFlags::ofField; + default -> null; + }; + Function flagsFactory = switch (ctx) { + case CLASS -> AccessFlags::ofClass; + case METHOD -> AccessFlags::ofMethod; + case FIELD -> AccessFlags::ofField; + default -> null; + }; + + var allFlags = EnumSet.allOf(AccessFlag.class); + allFlags.removeIf(f -> !f.locations().contains(ctx)); + + var r = new Random(123); + for (int i = 0; i < 1000; i++) { + var randomFlags = allFlags.stream().filter(f -> r.nextBoolean()).toArray(AccessFlag[]::new); + assertEquals(intFactory.apply(flagsFactory.apply(randomFlags).flagsMask()).flags(), Set.of(randomFlags)); + + var randomMask = r.nextInt(Short.MAX_VALUE); + assertEquals(intFactory.apply(randomMask).flagsMask(), randomMask); + } + } + + @Test + void testInvalidFlagsUse() { + assertAll( + () -> assertThrowsForInvalidFlagsUse(AccessFlags::ofClass), + () -> assertThrowsForInvalidFlagsUse(AccessFlags::ofField), + () -> assertThrowsForInvalidFlagsUse(AccessFlags::ofMethod) + ); + } + + void assertThrowsForInvalidFlagsUse(Consumer factory) { + assertThrows(IllegalArgumentException.class, () -> factory.accept(AccessFlag.values())); + } +} diff --git a/test/jdk/jdk/classfile/AdaptCodeTest.java b/test/jdk/jdk/classfile/AdaptCodeTest.java new file mode 100644 index 0000000000000..309e5bb969a89 --- /dev/null +++ b/test/jdk/jdk/classfile/AdaptCodeTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile Code Adaptation. + * @run junit AdaptCodeTest + */ + +import java.lang.constant.ConstantDesc; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import helpers.ByteArrayClassLoader; +import helpers.TestUtil; +import helpers.Transforms; +import jdk.internal.classfile.instruction.ConstantInstruction; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class AdaptCodeTest { + + static final String testClassName = "AdaptCodeTest$TestClass"; + static final Path testClassPath = Paths.get(URI.create(AdaptCodeTest.class.getResource(testClassName + ".class").toString())); + private static final String THIRTEEN = "BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah"; + private static final String SEVEN = "BlahBlahBlahBlahBlahBlahBlah"; + + @Test + void testNullAdaptIterator() throws Exception { + ClassModel cm = Classfile.parse(testClassPath); + for (ClassTransform t : Transforms.noops) { + byte[] newBytes = cm.transform(t); + String result = (String) + new ByteArrayClassLoader(AdaptCodeTest.class.getClassLoader(), testClassName, newBytes) + .getMethod(testClassName, "many") + .invoke(null, "Blah"); + assertEquals(result, THIRTEEN); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "modules/java.base/java/util/AbstractCollection.class", + "modules/java.base/java/util/PriorityQueue.class", + "modules/java.base/java/util/ArraysParallelSortHelpers.class" + }) + void testNullAdaptIterator2(String path) throws Exception { + FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); + ClassModel cm = Classfile.parse(fs.getPath(path)); + for (ClassTransform t : Transforms.noops) { + byte[] newBytes = cm.transform(t); + } + } + + @Test + void testSevenOfThirteenIterator() throws Exception { + ClassModel cm = Classfile.parse(testClassPath); + + var transform = ClassTransform.transformingMethodBodies((codeB, codeE) -> { + switch (codeE) { + case ConstantInstruction i -> { + ConstantDesc val = i.constantValue(); + if ((val instanceof Integer) && ((Integer) val) == 13) { + val = 7; + } + codeB.constantInstruction(i.opcode(), val); + } + default -> codeB.with(codeE); + } + }); + + byte[] newBytes = cm.transform(transform); +// Files.write(Path.of("foo.class"), newBytes); + String result = (String) + new ByteArrayClassLoader(AdaptCodeTest.class.getClassLoader(), testClassName, newBytes) + .getMethod(testClassName, "many") + .invoke(null, "Blah"); + assertEquals(result, SEVEN); + } + + @Test + void testCopy() throws Exception { + ClassModel cm = Classfile.parse(testClassPath); + byte[] newBytes = Classfile.build(cm.thisClass().asSymbol(), cb -> cm.forEachElement(cb)); +// TestUtil.writeClass(newBytes, "TestClass.class"); + String result = (String) + new ByteArrayClassLoader(AdaptCodeTest.class.getClassLoader(), testClassName, newBytes) + .getMethod(testClassName, "many") + .invoke(null, "Blah"); + assertEquals(result, THIRTEEN); + } + + public static class TestClass { + public static String many(String snip) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= 13; ++i) { + sb.append(snip); + } + return sb.toString(); + } + } +} diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java new file mode 100644 index 0000000000000..f963a00e80c4e --- /dev/null +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile advanced transformations. + * @run junit AdvancedTransformationsTest + */ +import helpers.ByteArrayClassLoader; +import java.util.Map; +import java.util.Set; +import jdk.internal.classfile.ClassHierarchyResolver; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.StackMapGenerator; +import jdk.internal.classfile.components.ClassRemapper; +import jdk.internal.classfile.components.CodeLocalsShifter; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static helpers.TestUtil.assertEmpty; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.impl.RawBytecodeHelper; +import jdk.internal.classfile.instruction.InvokeInstruction; +import jdk.internal.classfile.instruction.ReturnInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.components.CodeRelabeler; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.components.ClassPrinter; +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayDeque; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; + +class AdvancedTransformationsTest { + + @Test + void testShiftLocals() throws Exception { + try (var in = StackMapGenerator.class.getResourceAsStream("StackMapGenerator.class")) { + var clm = Classfile.parse(in.readAllBytes()); + var remapped = Classfile.parse(clm.transform((clb, cle) -> { + if (cle instanceof MethodModel mm) { + clb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel com) { + var shifter = CodeLocalsShifter.of(mm.flags(), mm.methodTypeSymbol()); + mb.transformCode(com, new CodeTransform() { + @Override + public void atStart(CodeBuilder builder) { + builder.allocateLocal(TypeKind.ReferenceType); + builder.allocateLocal(TypeKind.LongType); + builder.allocateLocal(TypeKind.IntType); + builder.allocateLocal(TypeKind.DoubleType); + } + @Override + public void accept(CodeBuilder builder, CodeElement element) { + builder.with(element); + } + }.andThen(shifter)); + } else mb.with(me); + }); + } + else + clb.with(cle); + })); + remapped.verify(null); + } + } + + @Test + void testRemapClass() throws Exception { + var map = Map.of( + ConstantDescs.CD_List, ClassDesc.of("remapped.List"), + ClassDesc.ofDescriptor(AbstractPseudoInstruction.ExceptionCatchImpl.class.descriptorString()), ClassDesc.of("remapped.ExceptionCatchImpl"), + ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode"), + ClassDesc.ofDescriptor(StackMapGenerator.class.descriptorString()), ClassDesc.of("remapped.StackMapGenerator") + ); + try (var in = StackMapGenerator.class.getResourceAsStream("StackMapGenerator.class")) { + var clm = Classfile.parse(in.readAllBytes()); + var remapped = Classfile.parse(ClassRemapper.of(map).remapClass(clm)); + assertEmpty(remapped.verify( + ClassHierarchyResolver.of(Set.of(), Map.of( + ClassDesc.of("remapped.RemappedBytecode"), ConstantDescs.CD_Object, + ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode"))) + .orElse(ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER) + , null)); //System.out::print)); + remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> + verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature()))); + remapped.methods().forEach(m -> m.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> { + var md = m.methodTypeSymbol(); + var ms = sa.asMethodSignature(); + verifySignature(md.returnType(), ms.result()); + var args = ms.arguments(); + assertEquals(md.parameterCount(), args.size()); + for (int i=0; i + mab.uses(foo).provides(foo, foo)))))).findAttribute(Attributes.MODULE).get(); + assertEquals(ma.uses().get(0).asSymbol(), bar); + var provides = ma.provides().get(0); + assertEquals(provides.provides().asSymbol(), bar); + assertEquals(provides.providesWith().get(0).asSymbol(), bar); + } + + @Test + void testRemapDetails() throws Exception { + var foo = ClassDesc.ofDescriptor(Foo.class.descriptorString()); + var bar = ClassDesc.ofDescriptor(Bar.class.descriptorString()); + var fooAnno = ClassDesc.ofDescriptor(FooAnno.class.descriptorString()); + var barAnno = ClassDesc.ofDescriptor(BarAnno.class.descriptorString()); + var rec = ClassDesc.ofDescriptor(Rec.class.descriptorString()); + + var remapped = Classfile.parse( + ClassRemapper.of(Map.of(foo, bar, fooAnno, barAnno)).remapClass( + Classfile.parse( + Rec.class.getResourceAsStream(Rec.class.getName() + ".class") + .readAllBytes()))); + var sb = new StringBuilder(); + ClassPrinter.toYaml(remapped, ClassPrinter.Verbosity.TRACE_ALL, sb::append); + String out = sb.toString(); + assertContains(out, + "annotation class: LAdvancedTransformationsTest$BarAnno;", + "type: LAdvancedTransformationsTest$Bar;", + "inner class: AdvancedTransformationsTest$Bar", + "inner class: AdvancedTransformationsTest$BarAnno", + "field type: LAdvancedTransformationsTest$Bar;", + "method type: (LAdvancedTransformationsTest$Bar;)V", + "stack map frame @0: {locals: [THIS, AdvancedTransformationsTest$Bar", + "[{annotation class: LAdvancedTransformationsTest$BarAnno;", + "INVOKESPECIAL, owner: AdvancedTransformationsTest$Bar", + "ANEWARRAY, dimensions: 1, descriptor: AdvancedTransformationsTest$Bar", + "PUTSTATIC, owner: AdvancedTransformationsTest$Bar, field name: fooField, field type: LAdvancedTransformationsTest$Bar;", + "INVOKESTATIC, owner: AdvancedTransformationsTest$Bar, method name: fooMethod, method type: (LAdvancedTransformationsTest$Bar;)LAdvancedTransformationsTest$Bar", + "method type: ()LAdvancedTransformationsTest$Bar;", + "GETFIELD, owner: AdvancedTransformationsTest$Rec, field name: foo, field type: LAdvancedTransformationsTest$Bar;"); + } + + private static void assertContains(String actual, String... expected) { + for (String exp : expected) + assertTrue(actual.contains(exp), "expected text: \"" + exp + "\" not found in:\n" + actual); + } + + private static void verifySignature(ClassDesc desc, Signature sig) { + switch (sig) { + case Signature.ClassTypeSig cts -> + assertEquals(desc.descriptorString(), cts.classDesc().descriptorString()); + case Signature.ArrayTypeSig ats -> + verifySignature(desc.componentType(), ats.componentSignature()); + case Signature.BaseTypeSig bts -> + assertEquals(desc.descriptorString(), bts.signatureString()); + default -> {} + } + } + + @Test + void testInstrumentClass() throws Exception { + var instrumentor = Classfile.parse(AdvancedTransformationsTest.class.getResourceAsStream("AdvancedTransformationsTest$InstrumentorClass.class").readAllBytes()); + var target = Classfile.parse(AdvancedTransformationsTest.class.getResourceAsStream("AdvancedTransformationsTest$TargetClass.class").readAllBytes()); + var instrumentedBytes = instrument(target, instrumentor, mm -> mm.methodName().stringValue().equals("instrumentedMethod")); + assertEmpty(Classfile.parse(instrumentedBytes).verify(null)); //System.out::print)); + var targetClass = new ByteArrayClassLoader(AdvancedTransformationsTest.class.getClassLoader(), "AdvancedTransformationsTest$TargetClass", instrumentedBytes).loadClass("AdvancedTransformationsTest$TargetClass"); + assertEquals(targetClass.getDeclaredMethod("instrumentedMethod", Boolean.class).invoke(targetClass.getDeclaredConstructor().newInstance(), false), 34); + } + + public static class InstrumentorClass { + + //matching fields are mapped + private String privateField; + //non-matching fields are added, however not initialized + int instrumentorField = 8; + + //matching methods are instrumenting frames + public int instrumentedMethod(Boolean instrumented) { +// System.out.println("instrumentor start"); + assertEquals(privateField, "hi"); + int local = 42; + instrumented = true; + //matching method call is inlined + instrumentedMethod(instrumented); + instrumentedMethod(instrumented); + assertEquals(local, 42); + assertEquals(privateField, "hello"); + assertEquals(instrumentorField, 0); + assertEquals(insHelper(), 77); +// System.out.println("instrumentor end"); + return 34; + } + + //non-matching methods are added + private static int insHelper() { + return 77; + } + } + + public static class TargetClass { + + private String privateField = "hi"; + + public int instrumentedMethod(Boolean instrumented) { +// System.out.println("target called"); + assertTrue(instrumented); + anotherTargetMethod(); + privateField = "hello"; + int local = 13; + return local; + } + + public void anotherTargetMethod() { +// System.out.println("anotherTargetMethod called"); + } + } + + //synchronized copy of instrumentation code from jdk.jfr jdk.jfr.internal.instrument.JIClassInstrumentation for testing purposes + private static byte[] instrument(ClassModel target, ClassModel instrumentor, Predicate instrumentedMethodsFilter) { + var instrumentorCodeMap = instrumentor.methods().stream() + .filter(instrumentedMethodsFilter) + .collect(Collectors.toMap(mm -> mm.methodName().stringValue() + mm.methodType().stringValue(), mm -> mm.code().orElseThrow())); + var targetFieldNames = target.fields().stream().map(f -> f.fieldName().stringValue()).collect(Collectors.toSet()); + var targetMethods = target.methods().stream().map(m -> m.methodName().stringValue() + m.methodType().stringValue()).collect(Collectors.toSet()); + var instrumentorClassRemapper = ClassRemapper.of(Map.of(instrumentor.thisClass().asSymbol(), target.thisClass().asSymbol())); + return target.transform( + ClassTransform.transformingMethods( + instrumentedMethodsFilter, + (mb, me) -> { + if (me instanceof CodeModel targetCodeModel) { + var mm = targetCodeModel.parent().get(); + //instrumented methods code is taken from instrumentor + mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()), + //all references to the instrumentor class are remapped to target class + instrumentorClassRemapper.asCodeTransform() + .andThen((codeBuilder, instrumentorCodeElement) -> { + //all invocations of target methods from instrumentor are inlined + if (instrumentorCodeElement instanceof InvokeInstruction inv + && target.thisClass().asInternalName().equals(inv.owner().asInternalName()) + && mm.methodName().stringValue().equals(inv.name().stringValue()) + && mm.methodType().stringValue().equals(inv.type().stringValue())) { + + //store stacked method parameters into locals + var storeStack = new ArrayDeque(); + int slot = 0; + if (!mm.flags().has(AccessFlag.STATIC)) + storeStack.push(StoreInstruction.of(TypeKind.ReferenceType, slot++)); + for (var pt : mm.methodTypeSymbol().parameterList()) { + var tk = TypeKind.fromDescriptor(pt.descriptorString()); + storeStack.push(StoreInstruction.of(tk, slot)); + slot += tk.slotSize(); + } + storeStack.forEach(codeBuilder::with); + + //inlined target locals must be shifted based on the actual instrumentor locals + codeBuilder.block(inlinedBlockBuilder -> inlinedBlockBuilder + .transform(targetCodeModel, CodeLocalsShifter.of(mm.flags(), mm.methodTypeSymbol()) + .andThen(CodeRelabeler.of()) + .andThen((innerBuilder, shiftedTargetCode) -> { + //returns must be replaced with jump to the end of the inlined method + if (shiftedTargetCode instanceof ReturnInstruction) + innerBuilder.goto_(inlinedBlockBuilder.breakLabel()); + else + innerBuilder.with(shiftedTargetCode); + }))); + } else + codeBuilder.with(instrumentorCodeElement); + })); + } else + mb.with(me); + }) + .andThen(ClassTransform.endHandler(clb -> + //remaining instrumentor fields and methods are injected at the end + clb.transform(instrumentor, + ClassTransform.dropping(cle -> + !(cle instanceof FieldModel fm + && !targetFieldNames.contains(fm.fieldName().stringValue())) + && !(cle instanceof MethodModel mm + && !"".equals(mm.methodName().stringValue()) + && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue()))) + //and instrumentor class references remapped to target class + .andThen(instrumentorClassRemapper))))); + } +} diff --git a/test/jdk/jdk/classfile/AnnotationModelTest.java b/test/jdk/jdk/classfile/AnnotationModelTest.java new file mode 100644 index 0000000000000..529df063db85b --- /dev/null +++ b/test/jdk/jdk/classfile/AnnotationModelTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile annotation model. + * @run junit AnnotationModelTest + */ +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Attributes; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; + +import static org.junit.jupiter.api.Assertions.*; + +class AnnotationModelTest { + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + private static final String testClass = "modules/java.base/java/lang/annotation/Target.class"; + static byte[] fileBytes; + + static { + try { + fileBytes = Files.readAllBytes(JRT.getPath(testClass)); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Test + void readAnnos() { + var model = Classfile.parse(fileBytes); + var annotations = model.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).get().annotations(); + + assertEquals(annotations.size(), 3); + } +} diff --git a/test/jdk/jdk/classfile/AnnotationTest.java b/test/jdk/jdk/classfile/AnnotationTest.java new file mode 100644 index 0000000000000..e649c0e0a5c99 --- /dev/null +++ b/test/jdk/jdk/classfile/AnnotationTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile annotations. + * @run junit AnnotationTest + */ +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import org.junit.jupiter.api.Test; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import jdk.internal.classfile.impl.DirectClassBuilder; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * AnnotationTest + */ +class AnnotationTest { + enum E {C}; + + private static Map constants + = Map.ofEntries( + new AbstractMap.SimpleImmutableEntry<>("i", 1), + new AbstractMap.SimpleImmutableEntry<>("j", 1L), + new AbstractMap.SimpleImmutableEntry<>("s", 1), + new AbstractMap.SimpleImmutableEntry<>("b", 1), + new AbstractMap.SimpleImmutableEntry<>("f", 1.0f), + new AbstractMap.SimpleImmutableEntry<>("d", 1.0d), + new AbstractMap.SimpleImmutableEntry<>("z", 1), + new AbstractMap.SimpleImmutableEntry<>("c", (int) '1'), + new AbstractMap.SimpleImmutableEntry<>("st", "1"), + new AbstractMap.SimpleImmutableEntry<>("cl", ClassDesc.of("foo.Bar")), + new AbstractMap.SimpleImmutableEntry<>("en", E.C), + new AbstractMap.SimpleImmutableEntry<>("arr", new Object[] {1, "1", 1.0f}) + ); + + private static final List constantElements = + constants.entrySet().stream() + .map(e -> AnnotationElement.of(e.getKey(), AnnotationValue.of(e.getValue()))) + .toList(); + + private static List elements() { + List list = new ArrayList<>(constantElements); + list.add(AnnotationElement.ofAnnotation("a", Annotation.of(ClassDesc.of("Bar"), constantElements))); + return list; + } + + private static boolean assertAnno(Annotation a, String annoClassDescriptor, boolean deep) { + assertEquals(a.className().stringValue(), annoClassDescriptor); + assertEquals(a.elements().size(), deep ? 13 : 12); + Set names = new HashSet<>(); + for (AnnotationElement evp : a.elements()) { + names.add(evp.name().stringValue()); + switch (evp.name().stringValue()) { + case "i", "j", "s", "b", "f", "d", "z", "c", "st": + assertTrue (evp.value() instanceof AnnotationValue.OfConstant c); + assertEquals(((AnnotationValue.OfConstant) evp.value()).constantValue(), + constants.get(evp.name().stringValue())); + break; + case "cl": + assertTrue (evp.value() instanceof AnnotationValue.OfClass c + && c.className().stringValue().equals("Lfoo/Bar;")); + break; + case "en": + assertTrue (evp.value() instanceof AnnotationValue.OfEnum c + && c.className().stringValue().equals(E.class.descriptorString()) && c.constantName().stringValue().equals("C")); + break; + case "a": + assertTrue (evp.value() instanceof AnnotationValue.OfAnnotation c + && assertAnno(c.annotation(), "LBar;", false)); + break; + case "arr": + assertTrue (evp.value() instanceof AnnotationValue.OfArray); + List values = ((AnnotationValue.OfArray) evp.value()).values(); + assertEquals(values.stream().map(v -> ((AnnotationValue.OfConstant) v).constant().constantValue()).collect(toSet()), + Set.of(1, 1.0f, "1")); + break; + default: + fail("Unexpected annotation element: " + evp.name().stringValue()); + + } + } + assertEquals(names.size(), a.elements().size()); + return true; + } + + private static RuntimeVisibleAnnotationsAttribute buildAnnotationsWithCPB(ConstantPoolBuilder constantPoolBuilder) { + return RuntimeVisibleAnnotationsAttribute.of(Annotation.of(constantPoolBuilder.utf8Entry("LAnno;"), elements())); + } + + @Test + void testAnnos() { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + ((DirectClassBuilder) cb).writeAttribute(buildAnnotationsWithCPB(cb.constantPool())); + cb.withMethod("foo", MethodTypeDesc.of(CD_void), 0, mb -> mb.with(buildAnnotationsWithCPB(mb.constantPool()))); + cb.withField("foo", CD_int, fb -> fb.with(buildAnnotationsWithCPB(fb.constantPool()))); + }); + ClassModel cm = Classfile.parse(bytes); + List ces = cm.elementList(); + List annos = ces.stream() + .filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute) + .map(ce -> (RuntimeVisibleAnnotationsAttribute) ce) + .flatMap(a -> a.annotations().stream()) + .collect(toList()); + List fannos = ces.stream() + .filter(ce -> ce instanceof FieldModel) + .map(ce -> (FieldModel) ce) + .flatMap(ce -> ce.elementList().stream()) + .filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute) + .map(ce -> (RuntimeVisibleAnnotationsAttribute) ce) + .flatMap(am -> am.annotations().stream()) + .collect(toList()); + List mannos = ces.stream() + .filter(ce -> ce instanceof MethodModel) + .map(ce -> (MethodModel) ce) + .flatMap(ce -> ce.elementList().stream()) + .filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute) + .map(ce -> (RuntimeVisibleAnnotationsAttribute) ce) + .flatMap(am -> am.annotations().stream()) + .collect(toList()); + assertEquals(annos.size(), 1); + assertEquals(mannos.size(), 1); + assertEquals(fannos.size(), 1); + assertAnno(annos.get(0), "LAnno;", true); + assertAnno(mannos.get(0), "LAnno;", true); + assertAnno(fannos.get(0), "LAnno;", true); + } + + // annotation default on methods + + private static RuntimeVisibleAnnotationsAttribute buildAnnotations() { + return RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.of("Anno"), + elements())); + } + + @Test + void testAnnosNoCPB() { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + ((DirectClassBuilder) cb).writeAttribute(buildAnnotations()); + cb.withMethod("foo", MethodTypeDesc.of(CD_void), 0, mb -> mb.with(buildAnnotations())); + cb.withField("foo", CD_int, fb -> fb.with(buildAnnotations())); + }); + ClassModel cm = Classfile.parse(bytes); + List ces = cm.elementList(); + List annos = ces.stream() + .filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute) + .map(ce -> (RuntimeVisibleAnnotationsAttribute) ce) + .flatMap(a -> a.annotations().stream()) + .toList(); + List fannos = ces.stream() + .filter(ce -> ce instanceof FieldModel) + .map(ce -> (FieldModel) ce) + .flatMap(ce -> ce.elementList().stream()) + .filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute) + .map(ce -> (RuntimeVisibleAnnotationsAttribute) ce) + .flatMap(am -> am.annotations().stream()) + .toList(); + List mannos = ces.stream() + .filter(ce -> ce instanceof MethodModel) + .map(ce -> (MethodModel) ce) + .flatMap(ce -> ce.elementList().stream()) + .filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute) + .map(ce -> (RuntimeVisibleAnnotationsAttribute) ce) + .flatMap(am -> am.annotations().stream()) + .toList(); + assertEquals(annos.size(), 1); + assertEquals(mannos.size(), 1); + assertEquals(fannos.size(), 1); + assertAnno(annos.get(0), "LAnno;", true); + assertAnno(mannos.get(0), "LAnno;", true); + assertAnno(fannos.get(0), "LAnno;", true); + } +} diff --git a/test/jdk/jdk/classfile/ArrayTest.java b/test/jdk/jdk/classfile/ArrayTest.java new file mode 100644 index 0000000000000..2c2ab0948f2e5 --- /dev/null +++ b/test/jdk/jdk/classfile/ArrayTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile arrays. + * @run junit ArrayTest + */ +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.NewMultiArrayInstruction; +import jdk.internal.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.internal.classfile.instruction.NewReferenceArrayInstruction; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; + +class ArrayTest { + static final String testClassName = "ArrayTest$TestClass"; + static final Path testClassPath = Paths.get(URI.create(ArrayTest.class.getResource(testClassName + ".class").toString())); + + + @Test + void testArrayNew() throws Exception { + ClassModel cm = Classfile.parse(testClassPath); + + for (MethodModel mm : cm.methods()) { + mm.code().ifPresent(code -> { + Iterator it = code.iterator(); + int arrayCreateCount = 1; + while (it.hasNext()) { + CodeElement im = it.next(); + if (im instanceof NewReferenceArrayInstruction + || im instanceof NewPrimitiveArrayInstruction + || im instanceof NewMultiArrayInstruction) { + switch (arrayCreateCount++) { + case 1: { + NewMultiArrayInstruction nai = (NewMultiArrayInstruction) im; + assertEquals(nai.opcode(), Opcode.MULTIANEWARRAY); + assertEquals(nai.arrayType().asInternalName(), "[[[I"); + assertEquals(nai.dimensions(), 3); + break; + } + case 2: { + NewMultiArrayInstruction nai = (NewMultiArrayInstruction) im; + assertEquals(nai.opcode(), Opcode.MULTIANEWARRAY); + assertEquals(nai.arrayType().asInternalName(), + "[[[Ljava/lang/String;"); + assertEquals(nai.dimensions(), 2); + break; + } + case 3: { + NewReferenceArrayInstruction nai = (NewReferenceArrayInstruction) im; + assertEquals(nai.opcode(), Opcode.ANEWARRAY); + assertEquals(nai.componentType().asInternalName(), + "java/lang/String"); + break; + } + case 4: { + NewPrimitiveArrayInstruction nai = (NewPrimitiveArrayInstruction) im; + assertEquals(nai.opcode(), Opcode.NEWARRAY); + assertEquals(nai.typeKind(), TypeKind.DoubleType); + break; + } + } + } + } + if (arrayCreateCount > 1) { + assertEquals(arrayCreateCount, 5); + } + }); + } + } + + public static class TestClass { + public static void makeArrays() { + int[][][] ma = new int[10][20][30]; + String[][][] pa = new String[10][20][]; + String[] sa = new String[5]; + double[] da = new double[3]; + } + } +} diff --git a/test/jdk/jdk/classfile/BSMTest.java b/test/jdk/jdk/classfile/BSMTest.java new file mode 100644 index 0000000000000..b2f63b9fb45cd --- /dev/null +++ b/test/jdk/jdk/classfile/BSMTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile bootstrap methods. + * @run junit BSMTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandles; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import jdk.internal.classfile.*; +import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.LoadableConstantEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import org.junit.jupiter.api.Test; + +import static java.lang.constant.ConstantDescs.CD_String; +import static org.junit.jupiter.api.Assertions.*; + +public class BSMTest { + static final String testClassName = "BSMTest$SomeClass"; + static final Path testClassPath = Paths.get(URI.create(ArrayTest.class.getResource(testClassName + ".class").toString())); + private static final String THIRTEEN = "BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah"; + private static final String SEVEN = "BlahBlahBlahBlahBlahBlahBlah"; + private static final String TWENTY = "BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah"; + private static final String TYPE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;"; + + @Test + void testSevenOfThirteenIterator() throws Exception { + ClassModel cm = Classfile.parse(testClassPath); + byte[] newBytes = cm.transform((cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, (codeB, codeE) -> { + switch (codeE) { + case ConstantInstruction ci -> { + ConstantPoolBuilder cpb = codeB.constantPool(); + + List staticArgs = new ArrayList<>(2); + staticArgs.add(cpb.stringEntry(SEVEN)); + staticArgs.add(cpb.stringEntry(THIRTEEN)); + + MemberRefEntry memberRefEntry = cpb.methodRefEntry(ClassDesc.of("BSMTest"), "bootstrap", MethodTypeDesc.ofDescriptor(TYPE)); + MethodHandleEntry methodHandleEntry = cpb.methodHandleEntry(6, memberRefEntry); + BootstrapMethodEntry bme = cpb.bsmEntry(methodHandleEntry, staticArgs); + ConstantDynamicEntry cde = cpb.constantDynamicEntry(bme, cpb.nameAndTypeEntry("name", CD_String)); + + codeB.constantInstruction(Opcode.LDC, cde.constantValue()); + } + default -> codeB.with(codeE); + } + }); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }); + String result = (String) + new ByteArrayClassLoader(BSMTest.class.getClassLoader(), testClassName, newBytes) + .getMethod(testClassName, "many") + .invoke(null, new Object[0]); + assertEquals(result, TWENTY); + } + + public static String bootstrap(MethodHandles.Lookup lookup, String name, Class clz, Object arg1, Object arg2) { + return (String)arg1 + (String)arg2; + } + + public static class SomeClass { + public static String many() { + String s = "Foo"; + return s; + } + } +} \ No newline at end of file diff --git a/test/jdk/jdk/classfile/BasicBlockTest.java b/test/jdk/jdk/classfile/BasicBlockTest.java new file mode 100644 index 0000000000000..d16219d08ede8 --- /dev/null +++ b/test/jdk/jdk/classfile/BasicBlockTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile complex basic blocks affecting SM generator. + * @run junit BasicBlockTest + */ +import java.io.InputStream; +import java.io.IOException; +import jdk.internal.classfile.Classfile; +import org.junit.jupiter.api.Test; + +class BasicBlockTest { + + public void npeInResolveMystery() { + int i=0; Object key; + Object[] a= new Object[0]; + for (; i < 0; i++) { + if ((key = a[i]) == null) {} + } + } + + void exponentialComplexityInJointNeedLocalPartial(boolean a) { + while (a) { + if ((a || a) && (a || a) && (a || a) && (a || a) && (a || a) && (a || a)) {} else + if ((a || a) && (a || a) && (a || a) && (a || a) && (a || a) && (a || a)) {} else + if ((a || a) && (a || a) && (a || a) && (a || a) && (a || a) && (a || a)) {} else + if ((a || a) && (a || a) && (a || a) && (a || a) && (a || a) && (a || a)) {} else + if ((a || a) && (a || a) && (a || a) && (a || a) && (a || a) && (a || a)) {} else + if ((a || a) && (a || a) && (a || a) && (a || a) && (a || a) && (a || a)) {} + } + } + + @Test + void testPatternsCausingBasicBlockTroubles() throws IOException { + try (InputStream in = BasicBlockTest.class.getResourceAsStream("BasicBlockTest.class")) { + var classModel = Classfile.parse(in.readAllBytes()); + Classfile.build(classModel.thisClass().asSymbol(), cb -> classModel.forEachElement(cb)); + } + } +} diff --git a/test/jdk/jdk/classfile/BuilderBlockTest.java b/test/jdk/jdk/classfile/BuilderBlockTest.java new file mode 100644 index 0000000000000..cff73b8d33260 --- /dev/null +++ b/test/jdk/jdk/classfile/BuilderBlockTest.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile builder blocks. + * @run junit BuilderBlockTest + */ +import java.lang.constant.ClassDesc; + +import static java.lang.constant.ConstantDescs.*; + +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.nio.file.Paths; + +import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.impl.LabelImpl; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +/** + * BuilderBlockTest + */ +class BuilderBlockTest { + + static final String testClassName = "AdaptCodeTest$TestClass"; + static final Path testClassPath = Paths.get("target/test-classes/" + testClassName + ".class"); + + @Test + void testStartEnd() throws Exception { + // Ensure that start=0 at top level, end is undefined until code is done, then end=1 + Label startEnd[] = new Label[2]; + + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.of(CD_void), 0, + mb -> mb.withCode(xb -> { + startEnd[0] = xb.startLabel(); + startEnd[1] = xb.endLabel(); + xb.returnInstruction(TypeKind.VoidType); + assertEquals(((LabelImpl) startEnd[0]).getBCI(), 0); + assertEquals(((LabelImpl) startEnd[1]).getBCI(), -1); + })); + }); + + assertEquals(((LabelImpl) startEnd[0]).getBCI(), 0); + assertEquals(((LabelImpl) startEnd[1]).getBCI(), 1); + } + + @Test + void testStartEndBlock() throws Exception { + Label startEnd[] = new Label[4]; + + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.of(CD_void), 0, + mb -> mb.withCode(xb -> { + startEnd[0] = xb.startLabel(); + startEnd[1] = xb.endLabel(); + xb.nopInstruction(); + xb.block(xxb -> { + startEnd[2] = xxb.startLabel(); + startEnd[3] = xxb.endLabel(); + xxb.nopInstruction(); + }); + xb.returnInstruction(TypeKind.VoidType); + })); + }); + + assertEquals(((LabelImpl) startEnd[0]).getBCI(), 0); + assertEquals(((LabelImpl) startEnd[1]).getBCI(), 3); + assertEquals(((LabelImpl) startEnd[2]).getBCI(), 1); + assertEquals(((LabelImpl) startEnd[3]).getBCI(), 2); + } + + @Test + void testIfThenReturn() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> xb.iload(0) + .ifThen(xxb -> xxb.iconst_1().returnInstruction(TypeKind.IntType)) + .iconst_2() + .returnInstruction(TypeKind.IntType))); + }); + + Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) + .getMethod("Foo", "foo"); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); + + } + + @Test + void testIfThenElseReturn() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> xb.iload(0) + .ifThenElse(xxb -> xxb.iconst_1().returnInstruction(TypeKind.IntType), + xxb -> xxb.iconst_2().returnInstruction(TypeKind.IntType)))); + }); + + Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) + .getMethod("Foo", "foo"); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); + + } + + @Test + void testIfThenBadOpcode() { + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> { + xb.iload(0); + xb.iload(1); + assertThrows(IllegalArgumentException.class, () -> { + xb.ifThen( + Opcode.GOTO, + xxb -> xxb.iconst_1().istore(2)); + }); + xb.iload(2); + xb.ireturn(); + })); + }); + } + + @Test + void testIfThenElseImplicitBreak() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> xb.iload(0) + .ifThenElse(xxb -> xxb.iconst_1().istore(2), + xxb -> xxb.iconst_2().istore(2)) + .iload(2) + .ireturn())); + }); + + Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) + .getMethod("Foo", "foo"); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); + + } + + @Test + void testIfThenElseExplicitBreak() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> xb.iload(0) + .ifThenElse(xxb -> xxb.iconst_1().istore(2).goto_(xxb.breakLabel()), + xxb -> xxb.iconst_2().istore(2).goto_(xxb.breakLabel())) + .iload(2) + .ireturn())); + }); + + Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) + .getMethod("Foo", "foo"); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); + } + + @Test + void testIfThenElseOpcode() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> + xb.iload(0) + .iload(1) + .ifThenElse( + Opcode.IF_ICMPLT, + xxb -> xxb.iconst_1().istore(2), + xxb -> xxb.iconst_2().istore(2)) + .iload(2) + .ireturn())); + }); + + Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) + .getMethod("Foo", "foo"); + assertEquals(fooMethod.invoke(null, 1, 10), 1); + assertEquals(fooMethod.invoke(null, 9, 10), 1); + assertEquals(fooMethod.invoke(null, 10, 10), 2); + assertEquals(fooMethod.invoke(null, 11, 10), 2); + } + + @Test + void testIfThenElseBadOpcode() { + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withMethod("foo", MethodTypeDesc.of(CD_int, CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> { + xb.iload(0); + xb.iload(1); + assertThrows(IllegalArgumentException.class, () -> { + xb.ifThenElse( + Opcode.GOTO, + xxb -> xxb.iconst_1().istore(2), + xxb -> xxb.iconst_2().istore(2)); + }); + xb.iload(2); + xb.ireturn(); + })); + }); + } + + @Test + void testAllocateLocal() { + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.ofDescriptor("(IJI)V"), Classfile.ACC_STATIC, + mb -> mb.withCode(xb -> { + int slot1 = xb.allocateLocal(TypeKind.IntType); + int slot2 = xb.allocateLocal(TypeKind.LongType); + int slot3 = xb.allocateLocal(TypeKind.IntType); + + assertEquals(slot1, 4); + assertEquals(slot2, 5); + assertEquals(slot3, 7); + })); + }); + } + + @Test + void testAllocateLocalBlock() { + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.ofDescriptor("(IJI)V"), Classfile.ACC_STATIC, + mb -> mb.withCode(xb -> { + xb.block(bb -> { + int slot1 = bb.allocateLocal(TypeKind.IntType); + int slot2 = bb.allocateLocal(TypeKind.LongType); + int slot3 = bb.allocateLocal(TypeKind.IntType); + + assertEquals(slot1, 4); + assertEquals(slot2, 5); + assertEquals(slot3, 7); + }); + int slot4 = xb.allocateLocal(TypeKind.IntType); + assertEquals(slot4, 4); + })); + }); + } + + @Test + void testAllocateLocalIfThen() { + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.ofDescriptor("(IJI)V"), Classfile.ACC_STATIC, + mb -> mb.withCode(xb -> { + xb.iconst_0(); + xb.ifThenElse(bb -> { + int slot1 = bb.allocateLocal(TypeKind.IntType); + int slot2 = bb.allocateLocal(TypeKind.LongType); + int slot3 = bb.allocateLocal(TypeKind.IntType); + + assertEquals(slot1, 4); + assertEquals(slot2, 5); + assertEquals(slot3, 7); + }, + bb -> { + int slot1 = bb.allocateLocal(TypeKind.IntType); + + assertEquals(slot1, 4); + }); + int slot4 = xb.allocateLocal(TypeKind.IntType); + assertEquals(slot4, 4); + xb.return_(); + })); + }); + } +} diff --git a/test/jdk/jdk/classfile/BuilderParamTest.java b/test/jdk/jdk/classfile/BuilderParamTest.java new file mode 100644 index 0000000000000..4d744e6753877 --- /dev/null +++ b/test/jdk/jdk/classfile/BuilderParamTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile builder parameters. + * @run junit BuilderParamTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.internal.classfile.Classfile; +import org.junit.jupiter.api.Test; + +import static java.lang.constant.ConstantDescs.CD_void; +import static jdk.internal.classfile.Classfile.ACC_STATIC; +import static org.junit.jupiter.api.Assertions.*; + +/** + * BuilderParamTest + */ +class BuilderParamTest { + @Test + void testDirectBuilder() { + + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.ofDescriptor("(IJI)V"), 0, + mb -> mb.withCode(xb -> { + assertEquals(xb.receiverSlot(), 0); + assertEquals(xb.parameterSlot(0), 1); + assertEquals(xb.parameterSlot(1), 2); + assertEquals(xb.parameterSlot(2), 4); + })); + }); + + Classfile.build(ClassDesc.of("Foo"), cb -> { + cb.withMethod("foo", MethodTypeDesc.ofDescriptor("(IJI)V"), ACC_STATIC, + mb -> mb.withCode(xb -> { + assertEquals(xb.parameterSlot(0), 0); + assertEquals(xb.parameterSlot(1), 1); + assertEquals(xb.parameterSlot(2), 3); + })); + }); + } +} diff --git a/test/jdk/jdk/classfile/BuilderTryCatchTest.java b/test/jdk/jdk/classfile/BuilderTryCatchTest.java new file mode 100644 index 0000000000000..c286342737697 --- /dev/null +++ b/test/jdk/jdk/classfile/BuilderTryCatchTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2022, 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. + */ + +/* + * @test + * @summary Testing Classfile builder blocks. + * @run junit BuilderTryCatchTest + */ + +import jdk.internal.classfile.AccessFlags; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CompoundElement; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.ExceptionCatch; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.AccessFlag; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; + +import static java.lang.constant.ConstantDescs.CD_Double; +import static java.lang.constant.ConstantDescs.CD_Integer; +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.constant.ConstantDescs.CD_String; + +class BuilderTryCatchTest { + + static final ClassDesc CD_IOOBE = IndexOutOfBoundsException.class.describeConstable().get(); + static final ClassDesc CD_NPE = NullPointerException.class.describeConstable().get(); + static final MethodTypeDesc MTD_String = MethodType.methodType(String.class).describeConstable().get(); + + @Test + void testTryCatchCatchAll() throws Throwable { + byte[] bytes = generateTryCatchMethod(catchBuilder -> { + catchBuilder.catching(CD_IOOBE, tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "IndexOutOfBoundsException"); + tb.returnInstruction(TypeKind.ReferenceType); + }).catchingAll(tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "any"); + tb.returnInstruction(TypeKind.ReferenceType); + }); + }); + + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle main = lookup.findStatic(lookup.lookupClass(), "main", + MethodType.methodType(String.class, String[].class)); + + assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + assertEquals(main.invoke(new String[]{}), "IndexOutOfBoundsException"); + assertEquals(main.invoke(null), "any"); + } + + @Test + void testTryCatchCatchAllReachable() throws Throwable { + byte[] bytes = generateTryCatchMethod(catchBuilder -> { + catchBuilder.catching(CD_IOOBE, tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "IndexOutOfBoundsException"); + tb.astore(1); + }).catchingAll(tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "any"); + tb.astore(1); + }); + }); + + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle main = lookup.findStatic(lookup.lookupClass(), "main", + MethodType.methodType(String.class, String[].class)); + + assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + assertEquals(main.invoke(new String[]{}), "IndexOutOfBoundsException"); + assertEquals(main.invoke(null), "any"); + } + + @Test + void testTryMutliCatchReachable() throws Throwable { + byte[] bytes = generateTryCatchMethod(catchBuilder -> + catchBuilder.catchingMulti(List.of(CD_IOOBE, CD_NPE), tb -> { + tb.invokevirtual(CD_Object, "toString", MTD_String); + tb.astore(1); + })); + + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle main = lookup.findStatic(lookup.lookupClass(), "main", + MethodType.methodType(String.class, String[].class)); + + assertTrue(main.invoke(new String[]{}).toString().contains("IndexOutOfBoundsException")); + assertTrue(main.invoke(null).toString().contains("NullPointerException")); + } + + @Test + void testTryCatch() throws Throwable { + byte[] bytes = generateTryCatchMethod(catchBuilder -> { + catchBuilder.catching(CD_IOOBE, tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "IndexOutOfBoundsException"); + tb.returnInstruction(TypeKind.ReferenceType); + }); + }); + + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle main = lookup.findStatic(lookup.lookupClass(), "main", + MethodType.methodType(String.class, String[].class)); + + assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + assertEquals(main.invoke(new String[]{}), "IndexOutOfBoundsException"); + assertThrows(NullPointerException.class, + () -> main.invoke(null)); + } + + @Test + void testTryCatchAll() throws Throwable { + byte[] bytes = generateTryCatchMethod(catchBuilder -> { + catchBuilder.catchingAll(tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "any"); + tb.returnInstruction(TypeKind.ReferenceType); + }); + }); + + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle main = lookup.findStatic(lookup.lookupClass(), "main", + MethodType.methodType(String.class, String[].class)); + + assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + assertEquals(main.invoke(new String[]{}), "any"); + assertEquals(main.invoke(null), "any"); + } + + @Test + void testTryEmptyCatch() { + byte[] bytes = generateTryCatchMethod(catchBuilder -> {}); + + boolean anyGotos = Classfile.parse(bytes).methods().stream() + .flatMap(mm -> mm.code().stream()) + .flatMap(CompoundElement::elementStream) + .anyMatch(codeElement -> + (codeElement instanceof BranchInstruction bi && bi.opcode() == Opcode.GOTO) || + (codeElement instanceof ExceptionCatch)); + assertFalse(anyGotos); + } + + @Test + void testEmptyTry() { + byte[] bytes = Classfile.build(ClassDesc.of("C"), cb -> { + cb.withMethod("main", MethodTypeDesc.of(CD_String, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), mb -> { + mb.withCode(xb -> { + int stringSlot = xb.allocateLocal(TypeKind.ReferenceType); + xb.constantInstruction("S"); + xb.astore(stringSlot); + + assertThrows(IllegalStateException.class, () -> { + xb.trying(tb -> { + }, catchBuilder -> { + fail(); + + catchBuilder.catchingAll(tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "any"); + tb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + + xb.aload(stringSlot); + xb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + } + + @Test + void testLocalAllocation() throws Throwable { + byte[] bytes = Classfile.build(ClassDesc.of("C"), cb -> { + cb.withMethod("main", MethodTypeDesc.of(CD_String, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), mb -> { + mb.withCode(xb -> { + int stringSlot = xb.allocateLocal(TypeKind.ReferenceType); + xb.constantInstruction("S"); + xb.astore(stringSlot); + + xb.trying(tb -> { + int intSlot = tb.allocateLocal(TypeKind.IntType); + + tb.aload(0); + tb.constantInstruction(0); + // IndexOutOfBoundsException + tb.aaload(); + // NullPointerException + tb.invokevirtual(CD_String, "length", MethodType.methodType(int.class).describeConstable().get()); + tb.istore(intSlot); + + tb.iload(intSlot); + tb.invokestatic(CD_Integer, "toString", MethodType.methodType(String.class, int.class).describeConstable().get()); + tb.astore(stringSlot); + }, catchBuilder -> { + catchBuilder.catching(CD_IOOBE, tb -> { + tb.pop(); + + int doubleSlot = tb.allocateLocal(TypeKind.DoubleType); + tb.constantInstruction(Math.PI); + tb.dstore(doubleSlot); + + tb.dload(doubleSlot); + tb.invokestatic(CD_Double, "toString", MethodType.methodType(String.class, double.class).describeConstable().get()); + tb.astore(stringSlot); + }).catchingAll(tb -> { + tb.pop(); + + int refSlot = tb.allocateLocal(TypeKind.ReferenceType); + tb.constantInstruction("REF"); + tb.astore(refSlot); + + tb.aload(refSlot); + tb.invokevirtual(CD_String, "toString", MTD_String); + tb.astore(stringSlot); + }); + }); + + xb.aload(stringSlot); + xb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + + Files.write(Path.of("x.class"), bytes); + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle main = lookup.findStatic(lookup.lookupClass(), "main", + MethodType.methodType(String.class, String[].class)); + + assertEquals(main.invoke(new String[]{"BODY"}), Integer.toString(4)); + assertEquals(main.invoke(new String[]{}), Double.toString(Math.PI)); + assertEquals(main.invoke(null), "REF"); + } + + static byte[] generateTryCatchMethod(Consumer c) { + byte[] bytes = Classfile.build(ClassDesc.of("C"), cb -> { + cb.withMethod("main", MethodTypeDesc.of(CD_String, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), mb -> { + mb.withCode(xb -> { + int stringSlot = xb.allocateLocal(TypeKind.ReferenceType); + xb.constantInstruction("S"); + xb.astore(stringSlot); + + xb.trying(tb -> { + tb.aload(0); + tb.constantInstruction(0); + // IndexOutOfBoundsException + tb.aaload(); + // NullPointerException + tb.invokevirtual(CD_String, "toString", MTD_String); + tb.astore(stringSlot); + }, c); + + xb.aload(stringSlot); + xb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + + return bytes; + } +} \ No newline at end of file diff --git a/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java b/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java new file mode 100644 index 0000000000000..cb4c3cffe46f0 --- /dev/null +++ b/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile class hierarchy resolution SPI. + * @run junit ClassHierarchyInfoTest + */ +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.net.URI; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; +import jdk.internal.classfile.ClassHierarchyResolver; + +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.impl.Util; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ClassHierarchyInfoTest { + + @Test + public void testProduceInvalidStackMaps() throws Exception { + assertThrows(VerifyError.class, () -> transformAndVerify(className -> null)); + } + + @Test + void testProvideCustomClassHierarchy() throws Exception { + transformAndVerify(ClassHierarchyResolver.of( + Set.of(ConstantDescs.CD_Set, + ConstantDescs.CD_Collection), + Map.of(ClassDesc.of("java.util.HashMap$TreeNode"), ClassDesc.of("java.util.HashMap$Node"), + ClassDesc.of("java.util.HashMap$Node"), ConstantDescs.CD_Object, + ClassDesc.of("java.util.HashMap$Values"), ConstantDescs.CD_Object))); + } + + @Test + void testBreakDefaulClassHierarchy() throws Exception { + assertThrows(VerifyError.class, () -> + transformAndVerify(ClassHierarchyResolver.of( + Set.of(), + Map.of(ClassDesc.of("java.util.HashMap$Node"), ClassDesc.of("java.util.HashMap$TreeNode"))).orElse(ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER)) + ); + } + + @Test + void testProvideCustomClassStreamResolver() throws Exception { + var fs = FileSystems.getFileSystem(URI.create("jrt:/")); + transformAndVerify(ClassHierarchyResolver.ofCached(classDesc -> { + try { + return Files.newInputStream(fs.getPath("modules/java.base/" + Util.toInternalName(classDesc) + ".class")); + } catch (IOException ioe) { + throw new AssertionError(ioe); + } + })); + } + + void transformAndVerify(ClassHierarchyResolver res) throws Exception { + Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class"); + var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver(res)); + byte[] newBytes = classModel.transform( + (clb, cle) -> { + if (cle instanceof MethodModel mm) { + clb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel cm) { + mb.withCode(cob -> cm.forEachElement(cob)); + } + else + mb.with(me); + }); + } + else + clb.with(cle); + }); + var errors = Classfile.parse(newBytes).verify(null); + if (!errors.isEmpty()) throw errors.iterator().next(); + } +} diff --git a/test/jdk/jdk/classfile/ClassPrinterTest.java b/test/jdk/jdk/classfile/ClassPrinterTest.java new file mode 100644 index 0000000000000..9b4491d11be43 --- /dev/null +++ b/test/jdk/jdk/classfile/ClassPrinterTest.java @@ -0,0 +1,847 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile ClassPrinter. + * @run junit ClassPrinterTest + */ +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import java.util.Optional; +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.components.ClassPrinter; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ClassPrinterTest { + + ClassModel getClassModel() { + return Classfile.parse(Classfile.build(ClassDesc.of("Foo"), clb -> + clb.withVersion(61, 0) + .withFlags(Classfile.ACC_PUBLIC) + .with(SourceFileAttribute.of("Foo.java")) + .withSuperclass(ClassDesc.of("Boo")) + .withInterfaceSymbols(ClassDesc.of("Phee"), ClassDesc.of("Phoo")) + .with(InnerClassesAttribute.of( + InnerClassInfo.of(ClassDesc.of("Phee"), Optional.of(ClassDesc.of("Phoo")), Optional.of("InnerName"), Classfile.ACC_PROTECTED), + InnerClassInfo.of(ClassDesc.of("Phoo"), Optional.empty(), Optional.empty(), Classfile.ACC_PRIVATE))) + .with(EnclosingMethodAttribute.of(ClassDesc.of("Phee"), Optional.of("enclosingMethod"), Optional.of(MethodTypeDesc.of(ConstantDescs.CD_Double, ConstantDescs.CD_Collection)))) + .with(SyntheticAttribute.of()) + .with(SignatureAttribute.of(ClassSignature.of(Signature.ClassTypeSig.of(ClassDesc.of("Boo")), Signature.ClassTypeSig.of(ClassDesc.of("Phee")), Signature.ClassTypeSig.of(ClassDesc.of("Phoo"))))) + .with(DeprecatedAttribute.of()) + .with(NestHostAttribute.of(ClassDesc.of("Phee"))) + .with(NestMembersAttribute.ofSymbols(ClassDesc.of("Phoo"), ClassDesc.of("Boo"), ClassDesc.of("Bee"))) + .with(RecordAttribute.of(RecordComponentInfo.of("fee", ClassDesc.of("Phoo"), List.of( + SignatureAttribute.of(Signature.of(ClassDesc.of("Phoo"))), + RuntimeInvisibleTypeAnnotationsAttribute.of( + TypeAnnotation.of(TypeAnnotation.TargetInfo.ofField(), + List.of(TypeAnnotation.TypePathComponent.WILDCARD), + ClassDesc.of("Boo"), List.of())))))) + .with(RuntimeInvisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.of("Phoo"), AnnotationElement.ofFloat("flfl", 2), AnnotationElement.ofFloat("frfl", 3)))) + .with(PermittedSubclassesAttribute.ofSymbols(ClassDesc.of("Boo"), ClassDesc.of("Phoo"))) + .withField("f", ConstantDescs.CD_String, fb -> fb + .withFlags(Classfile.ACC_PRIVATE) + .with(RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.of("Phoo"), AnnotationElement.ofFloat("flfl", 0), AnnotationElement.ofFloat("frfl", 1))))) + .withMethod("m", MethodTypeDesc.of(ConstantDescs.CD_Void, ConstantDescs.CD_boolean, ConstantDescs.CD_Throwable), Classfile.ACC_PROTECTED, mb -> mb + .with(AnnotationDefaultAttribute.of(AnnotationValue.ofArray( + AnnotationValue.ofBoolean(true), + AnnotationValue.ofByte((byte)12), + AnnotationValue.ofChar('c'), + AnnotationValue.ofClass(ClassDesc.of("Phee")), + AnnotationValue.ofDouble(1.3), + AnnotationValue.ofEnum(ClassDesc.of("Boo"), "BOO"), + AnnotationValue.ofFloat((float)3.7), + AnnotationValue.ofInt(33), + AnnotationValue.ofLong(3333), + AnnotationValue.ofShort((short)25), + AnnotationValue.ofString("BOO"), + AnnotationValue.ofAnnotation(Annotation.of(ClassDesc.of("Phoo"), AnnotationElement.of("param", AnnotationValue.ofInt(3))))))) + .with(RuntimeVisibleParameterAnnotationsAttribute.of(List.of(List.of(Annotation.of(ClassDesc.of("Phoo"), AnnotationElement.ofFloat("flfl", 22), AnnotationElement.ofFloat("frfl", 11)))))) + .with(RuntimeInvisibleParameterAnnotationsAttribute.of(List.of(List.of(Annotation.of(ClassDesc.of("Phoo"), AnnotationElement.ofFloat("flfl", -22), AnnotationElement.ofFloat("frfl", -11)))))) + .with(ExceptionsAttribute.ofSymbols(ClassDesc.of("Phoo"), ClassDesc.of("Boo"), ClassDesc.of("Bee"))) + .withCode(cob -> + cob.trying(tryb -> { + tryb.lineNumber(1); + tryb.iload(1); + tryb.lineNumber(2); + tryb.ifThen(thb -> thb.aload(2).athrow()); + tryb.lineNumber(3); + tryb.localVariable(2, "variable", ClassDesc.of("Phoo"), tryb.startLabel(), tryb.endLabel()); + tryb.localVariableType(2, "variable", Signature.of(ClassDesc.of("Phoo")), tryb.startLabel(), tryb.endLabel()); + tryb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + TypeAnnotation.of(TypeAnnotation.TargetInfo.ofField(), + List.of(TypeAnnotation.TypePathComponent.WILDCARD), + ClassDesc.of("Boo"), List.of()))); + tryb.return_(); + }, catchb -> catchb.catching(ClassDesc.of("Phee"), cb -> { + cb.lineNumber(4); + cb.athrow(); + })) + .with(RuntimeVisibleTypeAnnotationsAttribute.of( + TypeAnnotation.of(TypeAnnotation.TargetInfo.ofField(), + List.of(TypeAnnotation.TypePathComponent.ARRAY), + ClassDesc.of("Fee"), List.of(AnnotationElement.ofBoolean("yes", false))))) + )))); + } + + @Test + void testPrintYamlTraceAll() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toYaml(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL, out::append); + assertOut(out, + """ + - class name: Foo + version: 61.0 + flags: [PUBLIC] + superclass: Boo + interfaces: [Phee, Phoo] + attributes: [SourceFile, InnerClasses, EnclosingMethod, Synthetic, Signature, Deprecated, NestHost, NestMembers, Record, RuntimeInvisibleAnnotations, PermittedSubclasses] + constant pool: + 1: {tag: Utf8, value: Foo} + 2: {tag: Class, class name index: 1, class internal name: Foo} + 3: {tag: Utf8, value: Boo} + 4: {tag: Class, class name index: 3, class internal name: Boo} + 5: {tag: Utf8, value: f} + 6: {tag: Utf8, value: Ljava/lang/String;} + 7: {tag: Utf8, value: m} + 8: {tag: Utf8, value: (ZLjava/lang/Throwable;)Ljava/lang/Void;} + 9: {tag: Utf8, value: variable} + 10: {tag: Utf8, value: LPhoo;} + 11: {tag: Utf8, value: Phee} + 12: {tag: Class, class name index: 11, class internal name: Phee} + 13: {tag: Utf8, value: Phoo} + 14: {tag: Class, class name index: 13, class internal name: Phoo} + 15: {tag: Utf8, value: RuntimeVisibleAnnotations} + 16: {tag: Utf8, value: flfl} + 17: {tag: Float, value: 0.0} + 18: {tag: Utf8, value: frfl} + 19: {tag: Float, value: 1.0} + 20: {tag: Utf8, value: AnnotationDefault} + 21: {tag: Integer, value: 1} + 22: {tag: Integer, value: 12} + 23: {tag: Integer, value: 99} + 24: {tag: Utf8, value: LPhee;} + 25: {tag: Double, value: 1.3} + 27: {tag: Utf8, value: LBoo;} + 28: {tag: Utf8, value: BOO} + 29: {tag: Float, value: 3.7} + 30: {tag: Integer, value: 33} + 31: {tag: Long, value: 3333} + 33: {tag: Integer, value: 25} + 34: {tag: Utf8, value: param} + 35: {tag: Integer, value: 3} + 36: {tag: Utf8, value: RuntimeVisibleParameterAnnotations} + 37: {tag: Float, value: 22.0} + 38: {tag: Float, value: 11.0} + 39: {tag: Utf8, value: RuntimeInvisibleParameterAnnotations} + 40: {tag: Float, value: '-22.0'} + 41: {tag: Float, value: '-11.0'} + 42: {tag: Utf8, value: Exceptions} + 43: {tag: Utf8, value: Bee} + 44: {tag: Class, class name index: 43, class internal name: Bee} + 45: {tag: Utf8, value: Code} + 46: {tag: Utf8, value: RuntimeInvisibleTypeAnnotations} + 47: {tag: Utf8, value: RuntimeVisibleTypeAnnotations} + 48: {tag: Utf8, value: LFee;} + 49: {tag: Utf8, value: yes} + 50: {tag: Integer, value: 0} + 51: {tag: Utf8, value: LocalVariableTable} + 52: {tag: Utf8, value: LocalVariableTypeTable} + 53: {tag: Utf8, value: LineNumberTable} + 54: {tag: Utf8, value: StackMapTable} + 55: {tag: Utf8, value: SourceFile} + 56: {tag: Utf8, value: Foo.java} + 57: {tag: Utf8, value: InnerClasses} + 58: {tag: Utf8, value: InnerName} + 59: {tag: Utf8, value: EnclosingMethod} + 60: {tag: Utf8, value: enclosingMethod} + 61: {tag: Utf8, value: (Ljava/util/Collection;)Ljava/lang/Double;} + 62: {tag: NameAndType, name index: 60, type index: 61, name: enclosingMethod, type: (Ljava/util/Collection;)Ljava/lang/Double;} + 63: {tag: Utf8, value: Synthetic} + 64: {tag: Utf8, value: Signature} + 65: {tag: Utf8, value: LBoo;LPhee;LPhoo;} + 66: {tag: Utf8, value: Deprecated} + 67: {tag: Utf8, value: NestHost} + 68: {tag: Utf8, value: NestMembers} + 69: {tag: Utf8, value: Record} + 70: {tag: Utf8, value: fee} + 71: {tag: Utf8, value: RuntimeInvisibleAnnotations} + 72: {tag: Float, value: 2.0} + 73: {tag: Float, value: 3.0} + 74: {tag: Utf8, value: PermittedSubclasses} + source file: Foo.java + inner classes: + - {inner class: Phee, outer class: Phoo, inner name: InnerName, flags: [PROTECTED]} + - {inner class: Phoo, outer class: null, inner name: null, flags: [PRIVATE]} + enclosing method: {class: Phee, method name: enclosingMethod, method type: (Ljava/util/Collection;)Ljava/lang/Double;} + signature: LBoo;LPhee;LPhoo; + nest host: Phee + nest members: [Phoo, Boo, Bee] + record components: + - name: fee + type: LPhoo; + attributes: [Signature, RuntimeInvisibleTypeAnnotations] + signature: LPhoo; + invisible type annotations: + - {annotation class: LBoo;, target info: FIELD, values: []} + invisible annotations: + - {annotation class: LPhoo;, values: [{name: flfl, value: {float: 2.0}}, {name: frfl, value: {float: 3.0}}]} + permitted subclasses: [Boo, Phoo] + fields: + - field name: f + flags: [PRIVATE] + field type: Ljava/lang/String; + attributes: [RuntimeVisibleAnnotations] + visible annotations: + - {annotation class: LPhoo;, values: [{name: flfl, value: {float: 0.0}}, {name: frfl, value: {float: 1.0}}]} + methods: + - method name: m + flags: [PROTECTED] + method type: (ZLjava/lang/Throwable;)Ljava/lang/Void; + attributes: [AnnotationDefault, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, Exceptions, Code] + annotation default: {array: [{boolean: true}, {byte: 12}, {char: 99}, {class: LPhee;}, {double: 1.3}, {enum class: LBoo;, contant name: BOO}, {float: 3.7}, {int: 33}, {long: 3333}, {short: 25}, {string: BOO}, {annotation class: LPhoo;}]} + visible parameter annotations: + parameter 1: [{annotation class: LPhoo;, values: [{name: flfl, value: {float: 22.0}}, {name: frfl, value: {float: 11.0}}]}] + invisible parameter annotations: + parameter 1: [{annotation class: LPhoo;, values: [{name: flfl, value: {float: '-22.0'}}, {name: frfl, value: {float: '-11.0'}}]}] + excceptions: [Phoo, Boo, Bee] + code: + max stack: 1 + max locals: 3 + attributes: [RuntimeInvisibleTypeAnnotations, RuntimeVisibleTypeAnnotations, LocalVariableTable, LocalVariableTypeTable, LineNumberTable, StackMapTable] + local variables: + - {start: 0, end: 7, slot: 2, name: variable, type: LPhoo;} + local variable types: + - {start: 0, end: 7, slot: 2, name: variable, signature: LPhoo;} + line numbers: + - {start: 0, line number: 1} + - {start: 1, line number: 2} + - {start: 6, line number: 3} + - {start: 7, line number: 4} + stack map frames: + 6: {locals: [Foo, int, java/lang/Throwable], stack: []} + 7: {locals: [Foo, int, java/lang/Throwable], stack: [Phee]} + invisible type annotations: + - {annotation class: LBoo;, target info: FIELD, values: []} + visible type annotations: + - {annotation class: LFee;, target info: FIELD, values: [{name: yes, value: {boolean: false}}]} + //stack map frame @0: {locals: [Foo, int, java/lang/Throwable], stack: []} + //try block 1 start: {start: 0, end: 7, handler: 7, catch type: Phee} + 0: {opcode: ILOAD_1, slot: 1} + 1: {opcode: IFEQ, target: 6} + 4: {opcode: ALOAD_2, slot: 2, type: LPhoo;, variable name: variable} + 5: {opcode: ATHROW} + //stack map frame @6: {locals: [Foo, int, java/lang/Throwable], stack: []} + 6: {opcode: RETURN} + //stack map frame @7: {locals: [Foo, int, java/lang/Throwable], stack: [Phee]} + //try block 1 end: {start: 0, end: 7, handler: 7, catch type: Phee} + //exception handler 1 start: {start: 0, end: 7, handler: 7, catch type: Phee} + 7: {opcode: ATHROW} + exception handlers: + handler 1: {start: 0, end: 7, handler: 7, type: Phee} + """); + } + + @Test + void testPrintYamlCriticalAttributes() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toYaml(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES, out::append); + assertOut(out, + """ + - class name: Foo + version: 61.0 + flags: [PUBLIC] + superclass: Boo + interfaces: [Phee, Phoo] + attributes: [SourceFile, InnerClasses, EnclosingMethod, Synthetic, Signature, Deprecated, NestHost, NestMembers, Record, RuntimeInvisibleAnnotations, PermittedSubclasses] + nest host: Phee + nest members: [Phoo, Boo, Bee] + permitted subclasses: [Boo, Phoo] + fields: + - field name: f + flags: [PRIVATE] + field type: Ljava/lang/String; + attributes: [RuntimeVisibleAnnotations] + methods: + - method name: m + flags: [PROTECTED] + method type: (ZLjava/lang/Throwable;)Ljava/lang/Void; + attributes: [AnnotationDefault, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, Exceptions, Code] + code: + max stack: 1 + max locals: 3 + attributes: [RuntimeInvisibleTypeAnnotations, RuntimeVisibleTypeAnnotations, LocalVariableTable, LocalVariableTypeTable, LineNumberTable, StackMapTable] + stack map frames: + 6: {locals: [Foo, int, java/lang/Throwable], stack: []} + 7: {locals: [Foo, int, java/lang/Throwable], stack: [Phee]} + //stack map frame @0: {locals: [Foo, int, java/lang/Throwable], stack: []} + //try block 1 start: {start: 0, end: 7, handler: 7, catch type: Phee} + 0: {opcode: ILOAD_1, slot: 1} + 1: {opcode: IFEQ, target: 6} + 4: {opcode: ALOAD_2, slot: 2} + 5: {opcode: ATHROW} + //stack map frame @6: {locals: [Foo, int, java/lang/Throwable], stack: []} + 6: {opcode: RETURN} + //stack map frame @7: {locals: [Foo, int, java/lang/Throwable], stack: [Phee]} + //try block 1 end: {start: 0, end: 7, handler: 7, catch type: Phee} + //exception handler 1 start: {start: 0, end: 7, handler: 7, catch type: Phee} + 7: {opcode: ATHROW} + exception handlers: + handler 1: {start: 0, end: 7, handler: 7, type: Phee} + """); + } + + @Test + void testPrintYamlMembersOnly() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toYaml(getClassModel(), ClassPrinter.Verbosity.MEMBERS_ONLY, out::append); + assertOut(out, + """ + - class name: Foo + version: 61.0 + flags: [PUBLIC] + superclass: Boo + interfaces: [Phee, Phoo] + attributes: [SourceFile, InnerClasses, EnclosingMethod, Synthetic, Signature, Deprecated, NestHost, NestMembers, Record, RuntimeInvisibleAnnotations, PermittedSubclasses] + fields: + - field name: f + flags: [PRIVATE] + field type: Ljava/lang/String; + attributes: [RuntimeVisibleAnnotations] + methods: + - method name: m + flags: [PROTECTED] + method type: (ZLjava/lang/Throwable;)Ljava/lang/Void; + attributes: [AnnotationDefault, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, Exceptions, Code] + """); + } + + @Test + void testPrintJsonTraceAll() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toJson(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL, out::append); + assertOut(out, + """ + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile", "InnerClasses", "EnclosingMethod", "Synthetic", "Signature", "Deprecated", "NestHost", "NestMembers", "Record", "RuntimeInvisibleAnnotations", "PermittedSubclasses"], + "constant pool": { + "1": {"tag": "Utf8", "value": "Foo"}, + "2": {"tag": "Class", "class name index": 1, "class internal name": "Foo"}, + "3": {"tag": "Utf8", "value": "Boo"}, + "4": {"tag": "Class", "class name index": 3, "class internal name": "Boo"}, + "5": {"tag": "Utf8", "value": "f"}, + "6": {"tag": "Utf8", "value": "Ljava/lang/String;"}, + "7": {"tag": "Utf8", "value": "m"}, + "8": {"tag": "Utf8", "value": "(ZLjava/lang/Throwable;)Ljava/lang/Void;"}, + "9": {"tag": "Utf8", "value": "variable"}, + "10": {"tag": "Utf8", "value": "LPhoo;"}, + "11": {"tag": "Utf8", "value": "Phee"}, + "12": {"tag": "Class", "class name index": 11, "class internal name": "Phee"}, + "13": {"tag": "Utf8", "value": "Phoo"}, + "14": {"tag": "Class", "class name index": 13, "class internal name": "Phoo"}, + "15": {"tag": "Utf8", "value": "RuntimeVisibleAnnotations"}, + "16": {"tag": "Utf8", "value": "flfl"}, + "17": {"tag": "Float", "value": "0.0"}, + "18": {"tag": "Utf8", "value": "frfl"}, + "19": {"tag": "Float", "value": "1.0"}, + "20": {"tag": "Utf8", "value": "AnnotationDefault"}, + "21": {"tag": "Integer", "value": "1"}, + "22": {"tag": "Integer", "value": "12"}, + "23": {"tag": "Integer", "value": "99"}, + "24": {"tag": "Utf8", "value": "LPhee;"}, + "25": {"tag": "Double", "value": "1.3"}, + "27": {"tag": "Utf8", "value": "LBoo;"}, + "28": {"tag": "Utf8", "value": "BOO"}, + "29": {"tag": "Float", "value": "3.7"}, + "30": {"tag": "Integer", "value": "33"}, + "31": {"tag": "Long", "value": "3333"}, + "33": {"tag": "Integer", "value": "25"}, + "34": {"tag": "Utf8", "value": "param"}, + "35": {"tag": "Integer", "value": "3"}, + "36": {"tag": "Utf8", "value": "RuntimeVisibleParameterAnnotations"}, + "37": {"tag": "Float", "value": "22.0"}, + "38": {"tag": "Float", "value": "11.0"}, + "39": {"tag": "Utf8", "value": "RuntimeInvisibleParameterAnnotations"}, + "40": {"tag": "Float", "value": "-22.0"}, + "41": {"tag": "Float", "value": "-11.0"}, + "42": {"tag": "Utf8", "value": "Exceptions"}, + "43": {"tag": "Utf8", "value": "Bee"}, + "44": {"tag": "Class", "class name index": 43, "class internal name": "Bee"}, + "45": {"tag": "Utf8", "value": "Code"}, + "46": {"tag": "Utf8", "value": "RuntimeInvisibleTypeAnnotations"}, + "47": {"tag": "Utf8", "value": "RuntimeVisibleTypeAnnotations"}, + "48": {"tag": "Utf8", "value": "LFee;"}, + "49": {"tag": "Utf8", "value": "yes"}, + "50": {"tag": "Integer", "value": "0"}, + "51": {"tag": "Utf8", "value": "LocalVariableTable"}, + "52": {"tag": "Utf8", "value": "LocalVariableTypeTable"}, + "53": {"tag": "Utf8", "value": "LineNumberTable"}, + "54": {"tag": "Utf8", "value": "StackMapTable"}, + "55": {"tag": "Utf8", "value": "SourceFile"}, + "56": {"tag": "Utf8", "value": "Foo.java"}, + "57": {"tag": "Utf8", "value": "InnerClasses"}, + "58": {"tag": "Utf8", "value": "InnerName"}, + "59": {"tag": "Utf8", "value": "EnclosingMethod"}, + "60": {"tag": "Utf8", "value": "enclosingMethod"}, + "61": {"tag": "Utf8", "value": "(Ljava/util/Collection;)Ljava/lang/Double;"}, + "62": {"tag": "NameAndType", "name index": 60, "type index": 61, "name": "enclosingMethod", "type": "(Ljava/util/Collection;)Ljava/lang/Double;"}, + "63": {"tag": "Utf8", "value": "Synthetic"}, + "64": {"tag": "Utf8", "value": "Signature"}, + "65": {"tag": "Utf8", "value": "LBoo;LPhee;LPhoo;"}, + "66": {"tag": "Utf8", "value": "Deprecated"}, + "67": {"tag": "Utf8", "value": "NestHost"}, + "68": {"tag": "Utf8", "value": "NestMembers"}, + "69": {"tag": "Utf8", "value": "Record"}, + "70": {"tag": "Utf8", "value": "fee"}, + "71": {"tag": "Utf8", "value": "RuntimeInvisibleAnnotations"}, + "72": {"tag": "Float", "value": "2.0"}, + "73": {"tag": "Float", "value": "3.0"}, + "74": {"tag": "Utf8", "value": "PermittedSubclasses"}}, + "source file": "Foo.java", + "inner classes": [ + {"inner class": "Phee", "outer class": "Phoo", "inner name": "InnerName", "flags": ["PROTECTED"]}, + {"inner class": "Phoo", "outer class": "null", "inner name": "null", "flags": ["PRIVATE"]}], + "enclosing method": {"class": "Phee", "method name": "enclosingMethod", "method type": "(Ljava/util/Collection;)Ljava/lang/Double;"}, + "signature": "LBoo;LPhee;LPhoo;", + "nest host": "Phee", + "nest members": ["Phoo", "Boo", "Bee"], + "record components": [ + { "name": "fee", + "type": "LPhoo;", + "attributes": ["Signature", "RuntimeInvisibleTypeAnnotations"], + "signature": "LPhoo;", + "invisible type annotations": [ + {"annotation class": "LBoo;", "target info": "FIELD", "values": []}]}], + "invisible annotations": [ + {"annotation class": "LPhoo;", "values": [{"name": "flfl", "value": {"float": "2.0"}}, {"name": "frfl", "value": {"float": "3.0"}}]}], + "permitted subclasses": ["Boo", "Phoo"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "field type": "Ljava/lang/String;", + "attributes": ["RuntimeVisibleAnnotations"], + "visible annotations": [ + {"annotation class": "LPhoo;", "values": [{"name": "flfl", "value": {"float": "0.0"}}, {"name": "frfl", "value": {"float": "1.0"}}]}]}], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "method type": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["AnnotationDefault", "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations", "Exceptions", "Code"], + "annotation default": {"array": [{"boolean": "true"}, {"byte": "12"}, {"char": "99"}, {"class": "LPhee;"}, {"double": "1.3"}, {"enum class": "LBoo;", "contant name": "BOO"}, {"float": "3.7"}, {"int": "33"}, {"long": "3333"}, {"short": "25"}, {"string": "BOO"}, {"annotation class": "LPhoo;"}]}, + "visible parameter annotations": { + "parameter 1": [{"annotation class": "LPhoo;", "values": [{"name": "flfl", "value": {"float": "22.0"}}, {"name": "frfl", "value": {"float": "11.0"}}]}]}, + "invisible parameter annotations": { + "parameter 1": [{"annotation class": "LPhoo;", "values": [{"name": "flfl", "value": {"float": "-22.0"}}, {"name": "frfl", "value": {"float": "-11.0"}}]}]}, + "excceptions": ["Phoo", "Boo", "Bee"], + "code": { + "max stack": 1, + "max locals": 3, + "attributes": ["RuntimeInvisibleTypeAnnotations", "RuntimeVisibleTypeAnnotations", "LocalVariableTable", "LocalVariableTypeTable", "LineNumberTable", "StackMapTable"], + "local variables": [ + {"start": 0, "end": 7, "slot": 2, "name": "variable", "type": "LPhoo;"}], + "local variable types": [ + {"start": 0, "end": 7, "slot": 2, "name": "variable", "signature": "LPhoo;"}], + "line numbers": [ + {"start": 0, "line number": 1}, + {"start": 1, "line number": 2}, + {"start": 6, "line number": 3}, + {"start": 7, "line number": 4}], + "stack map frames": { + "6": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": []}, + "7": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": ["Phee"]}}, + "invisible type annotations": [ + {"annotation class": "LBoo;", "target info": "FIELD", "values": []}], + "visible type annotations": [ + {"annotation class": "LFee;", "target info": "FIELD", "values": [{"name": "yes", "value": {"boolean": "false"}}]}], + "//stack map frame @0": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": []}, + "//try block 1 start": {"start": 0, "end": 7, "handler": 7, "catch type": "Phee"}, + "0": {"opcode": "ILOAD_1", "slot": 1}, + "1": {"opcode": "IFEQ", "target": 6}, + "4": {"opcode": "ALOAD_2", "slot": 2, "type": "LPhoo;", "variable name": "variable"}, + "5": {"opcode": "ATHROW"}, + "//stack map frame @6": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": []}, + "6": {"opcode": "RETURN"}, + "//stack map frame @7": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": ["Phee"]}, + "//try block 1 end": {"start": 0, "end": 7, "handler": 7, "catch type": "Phee"}, + "//exception handler 1 start": {"start": 0, "end": 7, "handler": 7, "catch type": "Phee"}, + "7": {"opcode": "ATHROW"}, + "exception handlers": { + "handler 1": {"start": 0, "end": 7, "handler": 7, "type": "Phee"}}}}]} + """); + } + + @Test + void testPrintJsonCriticalAttributes() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toJson(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES, out::append); + assertOut(out, + """ + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile", "InnerClasses", "EnclosingMethod", "Synthetic", "Signature", "Deprecated", "NestHost", "NestMembers", "Record", "RuntimeInvisibleAnnotations", "PermittedSubclasses"], + "nest host": "Phee", + "nest members": ["Phoo", "Boo", "Bee"], + "permitted subclasses": ["Boo", "Phoo"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "field type": "Ljava/lang/String;", + "attributes": ["RuntimeVisibleAnnotations"]}], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "method type": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["AnnotationDefault", "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations", "Exceptions", "Code"], + "code": { + "max stack": 1, + "max locals": 3, + "attributes": ["RuntimeInvisibleTypeAnnotations", "RuntimeVisibleTypeAnnotations", "LocalVariableTable", "LocalVariableTypeTable", "LineNumberTable", "StackMapTable"], + "stack map frames": { + "6": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": []}, + "7": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": ["Phee"]}}, + "//stack map frame @0": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": []}, + "//try block 1 start": {"start": 0, "end": 7, "handler": 7, "catch type": "Phee"}, + "0": {"opcode": "ILOAD_1", "slot": 1}, + "1": {"opcode": "IFEQ", "target": 6}, + "4": {"opcode": "ALOAD_2", "slot": 2}, + "5": {"opcode": "ATHROW"}, + "//stack map frame @6": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": []}, + "6": {"opcode": "RETURN"}, + "//stack map frame @7": {"locals": ["Foo", "int", "java/lang/Throwable"], "stack": ["Phee"]}, + "//try block 1 end": {"start": 0, "end": 7, "handler": 7, "catch type": "Phee"}, + "//exception handler 1 start": {"start": 0, "end": 7, "handler": 7, "catch type": "Phee"}, + "7": {"opcode": "ATHROW"}, + "exception handlers": { + "handler 1": {"start": 0, "end": 7, "handler": 7, "type": "Phee"}}}}]} + """); + } + + @Test + void testPrintJsonMembersOnly() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toJson(getClassModel(), ClassPrinter.Verbosity.MEMBERS_ONLY, out::append); + assertOut(out, + """ + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile", "InnerClasses", "EnclosingMethod", "Synthetic", "Signature", "Deprecated", "NestHost", "NestMembers", "Record", "RuntimeInvisibleAnnotations", "PermittedSubclasses"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "field type": "Ljava/lang/String;", + "attributes": ["RuntimeVisibleAnnotations"]}], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "method type": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["AnnotationDefault", "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations", "Exceptions", "Code"]}]} + """); + } + + @Test + void testPrintXmlTraceAll() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toXml(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL, out::append); + assertOut(out, + """ + + + Foo + 61.0 + PUBLIC + Boo + PheePhoo + SourceFileInnerClassesEnclosingMethodSyntheticSignatureDeprecatedNestHostNestMembersRecordRuntimeInvisibleAnnotationsPermittedSubclasses + + <_1>Utf8Foo + <_2>Class1Foo + <_3>Utf8Boo + <_4>Class3Boo + <_5>Utf8f + <_6>Utf8Ljava/lang/String; + <_7>Utf8m + <_8>Utf8(ZLjava/lang/Throwable;)Ljava/lang/Void; + <_9>Utf8variable + <_10>Utf8LPhoo; + <_11>Utf8Phee + <_12>Class11Phee + <_13>Utf8Phoo + <_14>Class13Phoo + <_15>Utf8RuntimeVisibleAnnotations + <_16>Utf8flfl + <_17>Float0.0 + <_18>Utf8frfl + <_19>Float1.0 + <_20>Utf8AnnotationDefault + <_21>Integer1 + <_22>Integer12 + <_23>Integer99 + <_24>Utf8LPhee; + <_25>Double1.3 + <_27>Utf8LBoo; + <_28>Utf8BOO + <_29>Float3.7 + <_30>Integer33 + <_31>Long3333 + <_33>Integer25 + <_34>Utf8param + <_35>Integer3 + <_36>Utf8RuntimeVisibleParameterAnnotations + <_37>Float22.0 + <_38>Float11.0 + <_39>Utf8RuntimeInvisibleParameterAnnotations + <_40>Float-22.0 + <_41>Float-11.0 + <_42>Utf8Exceptions + <_43>Utf8Bee + <_44>Class43Bee + <_45>Utf8Code + <_46>Utf8RuntimeInvisibleTypeAnnotations + <_47>Utf8RuntimeVisibleTypeAnnotations + <_48>Utf8LFee; + <_49>Utf8yes + <_50>Integer0 + <_51>Utf8LocalVariableTable + <_52>Utf8LocalVariableTypeTable + <_53>Utf8LineNumberTable + <_54>Utf8StackMapTable + <_55>Utf8SourceFile + <_56>Utf8Foo.java + <_57>Utf8InnerClasses + <_58>Utf8InnerName + <_59>Utf8EnclosingMethod + <_60>Utf8enclosingMethod + <_61>Utf8(Ljava/util/Collection;)Ljava/lang/Double; + <_62>NameAndType6061enclosingMethod(Ljava/util/Collection;)Ljava/lang/Double; + <_63>Utf8Synthetic + <_64>Utf8Signature + <_65>Utf8LBoo;LPhee;LPhoo; + <_66>Utf8Deprecated + <_67>Utf8NestHost + <_68>Utf8NestMembers + <_69>Utf8Record + <_70>Utf8fee + <_71>Utf8RuntimeInvisibleAnnotations + <_72>Float2.0 + <_73>Float3.0 + <_74>Utf8PermittedSubclasses + Foo.java + + PheePhooInnerNamePROTECTED + PhoonullnullPRIVATE + PheeenclosingMethod(Ljava/util/Collection;)Ljava/lang/Double; + LBoo;LPhee;LPhoo; + Phee + PhooBooBee + + + fee + LPhoo; + SignatureRuntimeInvisibleTypeAnnotations + LPhoo; + + LBoo;FIELD + + LPhoo;flfl2.0frfl3.0 + BooPhoo + + + f + PRIVATE + Ljava/lang/String; + RuntimeVisibleAnnotations + + LPhoo;flfl0.0frfl1.0 + + + m + PROTECTED + (ZLjava/lang/Throwable;)Ljava/lang/Void; + AnnotationDefaultRuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotationsExceptionsCode + true1299LPhee;1.3LBoo;BOO3.733333325BOOLPhoo; + + LPhoo;flfl22.0frfl11.0 + + LPhoo;flfl-22.0frfl-11.0 + PhooBooBee + + 1 + 3 + RuntimeInvisibleTypeAnnotationsRuntimeVisibleTypeAnnotationsLocalVariableTableLocalVariableTypeTableLineNumberTableStackMapTable + + <_1>072variableLPhoo; + + <_1>072variableLPhoo; + + <_1>01 + <_2>12 + <_3>63 + <_4>74 + + <_6>Foointjava/lang/Throwable + <_7>Foointjava/lang/ThrowablePhee + + LBoo;FIELD + + LFee;FIELDyesfalse + <__stack_map_frame__0>Foointjava/lang/Throwable + <__try_block_1_start>077Phee + <_0>ILOAD_11 + <_1>IFEQ6 + <_4>ALOAD_22LPhoo;variable + <_5>ATHROW + <__stack_map_frame__6>Foointjava/lang/Throwable + <_6>RETURN + <__stack_map_frame__7>Foointjava/lang/ThrowablePhee + <__try_block_1_end>077Phee + <__exception_handler_1_start>077Phee + <_7>ATHROW + + 077Phee + """); + } + + @Test + void testPrintXmlCriticalAttributes() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toXml(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES, out::append); + assertOut(out, + """ + + + Foo + 61.0 + PUBLIC + Boo + PheePhoo + SourceFileInnerClassesEnclosingMethodSyntheticSignatureDeprecatedNestHostNestMembersRecordRuntimeInvisibleAnnotationsPermittedSubclasses + Phee + PhooBooBee + BooPhoo + + + f + PRIVATE + Ljava/lang/String; + RuntimeVisibleAnnotations + + + m + PROTECTED + (ZLjava/lang/Throwable;)Ljava/lang/Void; + AnnotationDefaultRuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotationsExceptionsCode + + 1 + 3 + RuntimeInvisibleTypeAnnotationsRuntimeVisibleTypeAnnotationsLocalVariableTableLocalVariableTypeTableLineNumberTableStackMapTable + + <_6>Foointjava/lang/Throwable + <_7>Foointjava/lang/ThrowablePhee + <__stack_map_frame__0>Foointjava/lang/Throwable + <__try_block_1_start>077Phee + <_0>ILOAD_11 + <_1>IFEQ6 + <_4>ALOAD_22 + <_5>ATHROW + <__stack_map_frame__6>Foointjava/lang/Throwable + <_6>RETURN + <__stack_map_frame__7>Foointjava/lang/ThrowablePhee + <__try_block_1_end>077Phee + <__exception_handler_1_start>077Phee + <_7>ATHROW + + 077Phee + """); + } + + @Test + void testPrintXmlMembersOnly() throws IOException { + var out = new StringBuilder(); + ClassPrinter.toXml(getClassModel(), ClassPrinter.Verbosity.MEMBERS_ONLY, out::append); + assertOut(out, + """ + + + Foo + 61.0 + PUBLIC + Boo + PheePhoo + SourceFileInnerClassesEnclosingMethodSyntheticSignatureDeprecatedNestHostNestMembersRecordRuntimeInvisibleAnnotationsPermittedSubclasses + + + f + PRIVATE + Ljava/lang/String; + RuntimeVisibleAnnotations + + + m + PROTECTED + (ZLjava/lang/Throwable;)Ljava/lang/Void; + AnnotationDefaultRuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotationsExceptionsCode + """); + } + + @Test + void testWalkTraceAll() throws IOException { + var node = ClassPrinter.toTree(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL); + assertEquals(node.walk().count(), 509); + } + + @Test + void testWalkCriticalAttributes() throws IOException { + var node = ClassPrinter.toTree(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES); + assertEquals(node.walk().count(), 128); + } + + @Test + void testWalkMembersOnly() throws IOException { + var node = ClassPrinter.toTree(getClassModel(), ClassPrinter.Verbosity.MEMBERS_ONLY); + assertEquals(node.walk().count(), 41); + } + + private static void assertOut(StringBuilder out, String expected) { +// System.out.println("-----------------"); +// System.out.println(out.toString()); +// System.out.println("-----------------"); + assertArrayEquals(out.toString().trim().split(" *\r?\n"), expected.trim().split("\n")); + } +} diff --git a/test/jdk/jdk/classfile/ConstantPoolCopyTest.java b/test/jdk/jdk/classfile/ConstantPoolCopyTest.java new file mode 100644 index 0000000000000..ffb75d5b7a00b --- /dev/null +++ b/test/jdk/jdk/classfile/ConstantPoolCopyTest.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile constant pool cloning. + * @run junit ConstantPoolCopyTest + */ +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassReader; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ConstantDynamicEntry; +import jdk.internal.classfile.constantpool.DoubleEntry; +import jdk.internal.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.internal.classfile.constantpool.FieldRefEntry; +import jdk.internal.classfile.constantpool.FloatEntry; +import jdk.internal.classfile.constantpool.IntegerEntry; +import jdk.internal.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.internal.classfile.constantpool.InvokeDynamicEntry; +import jdk.internal.classfile.constantpool.LongEntry; +import jdk.internal.classfile.constantpool.MemberRefEntry; +import jdk.internal.classfile.constantpool.MethodHandleEntry; +import jdk.internal.classfile.constantpool.MethodRefEntry; +import jdk.internal.classfile.constantpool.MethodTypeEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.NameAndTypeEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.StringEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.SplitConstantPool; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.BootstrapMethodEntry; +import jdk.internal.classfile.constantpool.ConstantPool; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class ConstantPoolCopyTest { + private static ClassModel[] rtJarToClassLow(FileSystem fs) { + try { + var modules = Stream.of( + Files.walk(fs.getPath("modules/java.base/java")), + Files.walk(fs.getPath("modules"), 2).filter(p -> p.endsWith("module-info.class"))) + .flatMap(p -> p) + .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")) + .map(ConstantPoolCopyTest::readAllBytes) + .map(bytes -> Classfile.parse(bytes)) + .toArray(ClassModel[]::new); + return modules; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private static byte[] readAllBytes(Path p) { + try { + return Files.readAllBytes(p); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + static ClassModel[] classes() { + return rtJarToClassLow(FileSystems.getFileSystem(URI.create("jrt:/"))); + } + + @ParameterizedTest + @MethodSource("classes") + void cloneConstantPool(ClassModel c) throws Exception { + ConstantPool cp = c.constantPool(); + ConstantPoolBuilder cp2 = new SplitConstantPool((ClassReader) cp); + + assertEquals(cp2.entryCount(), cp.entryCount(), "Cloned constant pool must be same size"); + + for (int i = 1; i < cp.entryCount();) { + PoolEntry cp1i = cp.entryByIndex(i); + PoolEntry cp2i = cp2.entryByIndex(i); + assertTrue(representsTheSame(cp1i, cp2i), cp2i + " does not represent the same constant pool entry as " + cp1i); + i+= cp1i.width(); + } + + // Bootstrap methods + assertEquals(cp.bootstrapMethodCount(), cp2.bootstrapMethodCount()); + for (int i = 0; i { + var dcob = (DirectCodeBuilder)cob; + var curPc = dcob.curPc(); + switch (coe) { + case LineNumber ln -> dcob.writeAttribute(new UnboundAttribute.AdHocAttribute<>(Attributes.LINE_NUMBER_TABLE) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(1); + b.writeU2(curPc); + b.writeU2(ln.line()); + } + }); + case LocalVariable lv -> dcob.writeAttribute(new UnboundAttribute.AdHocAttribute<>(Attributes.LOCAL_VARIABLE_TABLE) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(1); + lv.writeTo(b); + } + }); + case LocalVariableType lvt -> dcob.writeAttribute(new UnboundAttribute.AdHocAttribute<>(Attributes.LOCAL_VARIABLE_TYPE_TABLE) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(1); + lvt.writeTo(b); + } + }); + default -> cob.with(coe); + } + }))); +// ClassRecord.assertEqualsDeep( +// ClassRecord.ofClassModel(ClassModel.of(Files.readAllBytes(root.resolve(targetClassFile)))), +// ClassRecord.ofClassModel(ClassModel.of(Files.readAllBytes(root.resolve(sourceClassFile))))); +// ClassPrinter.toYaml(ClassModel.of(Files.readAllBytes(root.resolve(targetClassFile))), ClassPrinter.Verbosity.TRACE_ALL, System.out::print); + } + + static Path[] corpus() throws IOException, URISyntaxException { + splitTableAttributes("testdata/Pattern2.class", "testdata/Pattern2-split.class"); + return Stream.of( + Files.walk(JRT.getPath("modules/java.base/java")), + Files.walk(JRT.getPath("modules"), 2).filter(p -> p.endsWith("module-info.class")), + Files.walk(Paths.get(URI.create(CorpusTest.class.getResource("CorpusTest.class").toString())).getParent())) + .flatMap(p -> p) + .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class") && !p.endsWith("DeadCodePattern.class")) + .filter(p -> testFilter == null || p.toString().equals(testFilter)) + .toArray(Path[]::new); + } + + + @ParameterizedTest + @MethodSource("corpus") + void testNullAdaptations(Path path) throws Exception { + byte[] bytes = Files.readAllBytes(path); + + Optional oldRecord; + Optional newRecord; + Map errors = new HashMap<>(); + Map baseDups = findDups(bytes); + + for (Transforms.NoOpTransform m : Transforms.NoOpTransform.values()) { + if (m == Transforms.NoOpTransform.ARRAYCOPY + || m == Transforms.NoOpTransform.SHARED_3_NO_STACKMAP + || m.name().startsWith("ASM")) + continue; + + try { + byte[] transformed = m.shared && m.classTransform != null + ? Classfile.parse(bytes, Classfile.Option.generateStackmap(false)) + .transform(m.classTransform) + : m.transform.apply(bytes); + Map newDups = findDups(transformed); + oldRecord = m.classRecord(bytes); + newRecord = m.classRecord(transformed); + if (oldRecord.isPresent() && newRecord.isPresent()) + assertEqualsDeep(newRecord.get(), oldRecord.get(), + "Class[%s] with %s".formatted(path, m.name())); + switch (m) { + case SHARED_1, SHARED_2, SHARED_3, SHARED_3L, SHARED_3P: + if (newDups.size() > baseDups.size()) { + System.out.println(String.format("Incremental dups in file %s (%s): %s / %s", path, m, baseDups, newDups)); + } + compareCp(bytes, transformed); + break; + case UNSHARED_1, UNSHARED_2, UNSHARED_3: + if (!newDups.isEmpty()) { + System.out.println(String.format("Dups in file %s (%s): %s", path, m, newDups)); + } + break; + } + } + catch (Exception ex) { + System.err.printf("Error processing %s with %s: %s.%s%n", path, m.name(), + ex.getClass(), ex.getMessage()); + ex.printStackTrace(System.err); + errors.put(m, ex); + } + } + + if (!errors.isEmpty()) { + String msg = String.format("Failures for %s:%n", path) + + errors.entrySet().stream() + .map(e -> { + Exception exception = e.getValue(); + StackTraceElement[] trace = exception.getStackTrace(); + return String.format(" Mode %s: %s (%s:%d)", + e.getKey(), exception.toString(), + trace.length > 0 ? trace[0].getClassName() : "unknown", + trace.length > 0 ? trace[0].getLineNumber() : 0); + }) + .collect(joining("\n")); + fail(String.format("Errors in testNullAdapt: %s", msg)); + } + } + + @ParameterizedTest + @MethodSource("corpus") + void testReadAndTransform(Path path) throws IOException { + byte[] bytes = Files.readAllBytes(path); + + var classModel = Classfile.parse(bytes); + assertEqualsDeep(ClassRecord.ofClassModel(classModel), ClassRecord.ofStreamingElements(classModel), + "ClassModel (actual) vs StreamingElements (expected)"); + + byte[] newBytes = Classfile.build( + classModel.thisClass().asSymbol(), + classModel::forEachElement); + var newModel = Classfile.parse(newBytes); + assertEqualsDeep(ClassRecord.ofClassModel(newModel, CompatibilityFilter.By_ClassBuilder), + ClassRecord.ofClassModel(classModel, CompatibilityFilter.By_ClassBuilder), + "ClassModel[%s] transformed by ClassBuilder (actual) vs ClassModel before transformation (expected)".formatted(path)); + + assertEmpty(newModel.verify(null)); + } + +// @Test(enabled = false) +// public void checkDups() { + // Checks input files for dups -- and there are. Not clear this test has value. + // Tests above +// Map dups = findDups(bytes); +// if (!dups.isEmpty()) { +// String dupsString = dups.entrySet().stream() +// .map(e -> String.format("%d -> %d", e.getKey(), e.getValue())) +// .collect(joining(", ")); +// System.out.println(String.format("Duplicate entries in input file %s: %s", path, dupsString)); +// } +// } + + private void compareCp(byte[] orig, byte[] transformed) { + var cp1 = Classfile.parse(orig).constantPool(); + var cp2 = Classfile.parse(transformed).constantPool(); + + for (int i = 1; i < cp1.entryCount(); i += cp1.entryByIndex(i).width()) { + assertEquals(cpiToString(cp1.entryByIndex(i)), cpiToString(cp2.entryByIndex(i))); + } + + if (cp1.entryCount() != cp2.entryCount()) { + StringBuilder failMsg = new StringBuilder("Extra entries in constant pool (" + (cp2.entryCount() - cp1.entryCount()) + "): "); + for (int i = cp1.entryCount(); i < cp2.entryCount(); i += cp2.entryByIndex(i).width()) + failMsg.append("\n").append(cp2.entryByIndex(i)); + fail(failMsg.toString()); + } + } + + private static String cpiToString(PoolEntry e) { + String s = e.toString(); + if (e instanceof Utf8Entry ue) + s = "CONSTANT_Utf8_info[value: \"%s\"]".formatted(ue.stringValue()); + return s; + } + + private static Map findDups(byte[] bytes) { + Map dups = new HashMap<>(); + var cf = Classfile.parse(bytes); + var pool = cf.constantPool(); + Set entryStrings = new HashSet<>(); + for (int i = 1; i < pool.entryCount(); i += pool.entryByIndex(i).width()) { + String s = cpiToString(pool.entryByIndex(i)); + if (entryStrings.contains(s)) { + for (int j=1; j> deadLabelFragments() { + return List.of( + cob -> cob.exceptionCatchAll(cob.newLabel(), cob.startLabel(), cob.endLabel()), + cob -> cob.exceptionCatchAll(cob.startLabel(), cob.newLabel(), cob.endLabel()), + cob -> cob.exceptionCatchAll(cob.startLabel(), cob.endLabel(), cob.newLabel()), + cob -> cob.localVariable(0, "v", ConstantDescs.CD_int, cob.startLabel(), cob.newLabel()), + cob -> cob.localVariable(0, "v", ConstantDescs.CD_int, cob.newLabel(), cob.endLabel()), + cob -> cob.localVariableType(0, "v", Signature.of(ConstantDescs.CD_int), cob.startLabel(), cob.newLabel()), + cob -> cob.localVariableType(0, "v", Signature.of(ConstantDescs.CD_int), cob.newLabel(), cob.endLabel()), + cob -> cob.characterRange(cob.startLabel(), cob.newLabel(), 0, 0, 0), + cob -> cob.characterRange(cob.newLabel(), cob.endLabel(), 0, 0, 0)); + } + + @Test + void testFilterDeadLabels() { + var code = Classfile.parse(Classfile.build(ClassDesc.of("cls"), List.of(Classfile.Option.filterDeadLabels(true)), clb -> + clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_void), 0, cob -> { + cob.return_(); + deadLabelFragments().forEach(f -> f.accept(cob)); + }))).methods().get(0).code().get(); + + assertTrue(code.exceptionHandlers().isEmpty()); + code.findAttribute(Attributes.LOCAL_VARIABLE_TABLE).ifPresent(a -> assertTrue(a.localVariables().isEmpty())); + code.findAttribute(Attributes.LOCAL_VARIABLE_TYPE_TABLE).ifPresent(a -> assertTrue(a.localVariableTypes().isEmpty())); + code.findAttribute(Attributes.CHARACTER_RANGE_TABLE).ifPresent(a -> assertTrue(a.characterRangeTable().isEmpty())); + } + + @ParameterizedTest + @MethodSource("deadLabelFragments") + void testThrowOnDeadLabels(Consumer fragment) { + assertThrows(IllegalStateException.class, () -> Classfile.build(ClassDesc.of("cls"), clb -> + clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_void), 0, cob -> { + cob.return_(); + fragment.accept(cob); + }))); + } + +} diff --git a/test/jdk/jdk/classfile/LDCTest.java b/test/jdk/jdk/classfile/LDCTest.java new file mode 100644 index 0000000000000..482f677fdfef7 --- /dev/null +++ b/test/jdk/jdk/classfile/LDCTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile LDC instructions. + * @run junit LDCTest + */ +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.StringEntry; +import java.lang.reflect.AccessFlag; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import static helpers.TestConstants.MTD_VOID; +import static jdk.internal.classfile.Opcode.*; +import static jdk.internal.classfile.TypeKind.VoidType; +import jdk.internal.classfile.instruction.ConstantInstruction; + +class LDCTest { + @Test + void testLDCisConvertedToLDCW() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withVersion(52, 0); + cb.withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + ) + + .withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(c0 -> { + ConstantPoolBuilder cpb = cb.constantPool(); + for (int i = 0; i <= 256/2 + 2; i++) { // two entries per String + StringEntry s = cpb.stringEntry("string" + i); + } + c0.constantInstruction(LDC, "string0") + .constantInstruction(LDC, "string131") + .constantInstruction(LDC, "string50") + .constantInstruction(-0.0f) + .constantInstruction(-0.0d) + //non-LDC test cases + .constantInstruction(0.0f) + .constantInstruction(0.0d) + .returnInstruction(VoidType); + })); + }); + + var model = Classfile.parse(bytes); + var code = model.elementStream() + .filter(e -> e instanceof MethodModel) + .map(e -> (MethodModel) e) + .filter(e -> e.methodName().stringValue().equals("main")) + .flatMap(MethodModel::elementStream) + .filter(e -> e instanceof CodeModel) + .map(e -> (CodeModel) e) + .findFirst() + .orElseThrow(); + var opcodes = code.elementList().stream() + .filter(e -> e instanceof Instruction) + .map(e -> (Instruction)e) + .toList(); + + assertEquals(opcodes.size(), 8); + assertEquals(opcodes.get(0).opcode(), LDC); + assertEquals(opcodes.get(1).opcode(), LDC_W); + assertEquals(opcodes.get(2).opcode(), LDC); + assertEquals( + Float.floatToRawIntBits((float)((ConstantInstruction)opcodes.get(3)).constantValue()), + Float.floatToRawIntBits(-0.0f)); + assertEquals( + Double.doubleToRawLongBits((double)((ConstantInstruction)opcodes.get(4)).constantValue()), + Double.doubleToRawLongBits(-0.0d)); + assertEquals(opcodes.get(5).opcode(), FCONST_0); + assertEquals(opcodes.get(6).opcode(), DCONST_0); + assertEquals(opcodes.get(7).opcode(), RETURN); + } + + // TODO test for explicit LDC_W? +} \ No newline at end of file diff --git a/test/jdk/jdk/classfile/LimitsTest.java b/test/jdk/jdk/classfile/LimitsTest.java new file mode 100644 index 0000000000000..ec33f9f27af54 --- /dev/null +++ b/test/jdk/jdk/classfile/LimitsTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile limits. + * @run junit LimitsTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import jdk.internal.classfile.Classfile; +import org.junit.jupiter.api.Test; + +class LimitsTest { + + @Test + void testCPSizeLimit() { + Classfile.build(ClassDesc.of("BigClass"), cb -> { + for (int i = 1; i < 65000; i++) { + cb.withField("field" + i, ConstantDescs.CD_int, fb -> {}); + } + }); + } + +} diff --git a/test/jdk/jdk/classfile/LowAdaptTest.java b/test/jdk/jdk/classfile/LowAdaptTest.java new file mode 100644 index 0000000000000..a50514789f0f6 --- /dev/null +++ b/test/jdk/jdk/classfile/LowAdaptTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile low adaptation. + * @run junit LowAdaptTest + */ +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.constant.MethodTypeDesc; +import java.net.URI; +import java.nio.file.Paths; + +import jdk.internal.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.classfile.impl.DirectClassBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LowAdaptTest { + + static final String test = "LowAdaptTest$TestClass"; + + @Test + void testAdapt() throws Exception { + ClassModel cl = Classfile.parse(Paths.get(URI.create(LowAdaptTest.class.getResource(test + ".class").toString()))); + + DirectMethodHandleDesc bsm = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC, + ClassDesc.of("java.lang.invoke.LambdaMetafactory"), + "metafactory", + MethodTypeDesc.ofDescriptor("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;" + + "Ljava/lang/invoke/MethodType;" + + "Ljava/lang/invoke/MethodType;" + + "Ljava/lang/invoke/MethodHandle;" + + "Ljava/lang/invoke/MethodType;" + + ")Ljava/lang/invoke/CallSite;")); + DynamicCallSiteDesc indy = DynamicCallSiteDesc.of(bsm, + "applyAsInt", MethodTypeDesc.ofDescriptor("()Ljava/util/function/IntUnaryOperator;"), + MethodTypeDesc.ofDescriptor("(I)I"), + MethodHandleDesc.of(DirectMethodHandleDesc.Kind.STATIC, ClassDesc.of(test), "fib", "(I)I"), + MethodTypeDesc.ofDescriptor("(I)I")); + + byte[] clazz = Classfile.build(ClassDesc.of(test), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.with(SourceFileAttribute.of("/some/madeup/TestClass.java")); + cl.methods().forEach(m -> ((DirectClassBuilder) cb).withMethod(m)); + + cb.withMethod("doit", MethodTypeDesc.of(CD_int, CD_int), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(xb -> { + xb.invokeDynamicInstruction(indy); + xb.storeInstruction(TypeKind.ReferenceType, 1); + xb.loadInstruction(TypeKind.ReferenceType, 1); + xb.loadInstruction(TypeKind.IntType, 0); + xb.invokeInstruction(Opcode.INVOKEINTERFACE, ClassDesc.of("java.util.function.IntUnaryOperator"), + "applyAsInt", MethodTypeDesc.ofDescriptor("(I)I"), true); + xb.storeInstruction(TypeKind.IntType, 2); + xb.loadInstruction(TypeKind.IntType, 2); + xb.returnInstruction(TypeKind.IntType); + })); + }); + + + int result = (Integer) + new ByteArrayClassLoader(LowAdaptTest.class.getClassLoader(), test, clazz) + .getMethod(test,"doit") + .invoke(null, 10); + assertEquals(result, 55); + } + + public static class TestClass { + + static int fib(int n) { + if (n <= 1) + return n; + return fib(n - 1) + fib(n - 2); + } + } +} diff --git a/test/jdk/jdk/classfile/LowJCovAttributeTest.java b/test/jdk/jdk/classfile/LowJCovAttributeTest.java new file mode 100644 index 0000000000000..7767d94592065 --- /dev/null +++ b/test/jdk/jdk/classfile/LowJCovAttributeTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile low JCov attributes. + * @compile -Xjcov LowJCovAttributeTest.java + * @run junit LowJCovAttributeTest + */ +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.constantpool.Utf8Entry; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * LowJCovAttributeTest + */ +class LowJCovAttributeTest { + + private static final boolean VERBOSE = false; + + // (isolated and) compiled with -Xjcov + private static final String TEST_FILE = "LowJCovAttributeTest.class"; + + private final Path path; + private final ClassModel classLow; + + LowJCovAttributeTest() throws IOException { + this.path = Paths.get(URI.create(LowJCovAttributeTest.class.getResource(TEST_FILE).toString())); + this.classLow = Classfile.parse(path); + } + + @Test + void testRead() { + try { + testRead0(); + } catch(Exception ex) { + System.err.printf("%nLowJCovAttributeTest: FAIL %s%n", ex); + ex.printStackTrace(System.err); + throw ex; + } + } + + private void testRead0() { + int[] mask = new int[1]; + for (Attribute attr : classLow.attributes()) { + switch (attr.attributeName()) { + case Attributes.NAME_COMPILATION_ID: { + CompilationIDAttribute cid = (CompilationIDAttribute) attr; + Utf8Entry v = cid.compilationId(); + printf("CompilationID %s%n", v); + mask[0] |= 1; + break; + } + case Attributes.NAME_SOURCE_ID: { + SourceIDAttribute cid = (SourceIDAttribute) attr; + Utf8Entry v = cid.sourceId(); + printf("SourceID %s%n", v); + mask[0] |= 2; + break; + } + } + } + for (MethodModel m : classLow.methods()) { + m.findAttribute(Attributes.CODE).ifPresent(code -> + ((CodeModel) code).findAttribute(Attributes.CHARACTER_RANGE_TABLE).ifPresent(attr -> { + for (CharacterRangeInfo cr : attr.characterRangeTable()) { + printf(" %d-%d -> %d/%d-%d/%d (%x)%n", cr.startPc(), cr.endPc(), + cr.characterRangeStart() >> 10, cr.characterRangeStart() & 0x3FF, + cr.characterRangeEnd() >> 10, cr.characterRangeEnd() & 0x3FF, + cr.flags()); + } + mask[0] |= 4; + } + )); + } + assertEquals(mask[0], 7, "Not all JCov attributes seen"); + } + + private void printf(String format, Object... args) { + if (VERBOSE) { + System.out.printf(format, args); + } + } + + private void println() { + if (VERBOSE) { + System.out.println(); + } + } + +// @Test +// void testWrite() { +// try { +// testWrite0(); +// } catch(Exception ex) { +// System.err.printf("%nLowJCovAttributeTest: FAIL %s - %s%n", path, ex); +// ex.printStackTrace(System.err); +// throw ex; +// } +// } + +// private void testWrite0() { +// ConstantPoolLow cp = classLow.constantPool(); +// ConstantPoolLow cp2 = cp.clonedPool(); +// int sz = cp.size(); +// // check match of constant pools +// assertEquals(cp2.size(), cp.size(), "Cloned size should match"); +// for (int i = 1; i < sz; ) { +// ConstantPoolInfo info = cp.get(i); +// ConstantPoolInfo info2 = cp2.get(i); +// assertNotNull(info2, "Test set up failure -- Null CP entry copy of " + info.tag() + " [" + info.index() + "] @ " +// + i +// ); +// assertEquals(info2.index(), info.index(), +// "Test set up failure -- copying constant pool (index). \n" +// + "Orig: " + info.tag() + " [" + info.index() + "].\n" +// + "Copy: " + info2.tag() + " [" + info2.index() + "]."); +// assertEquals(info2.tag(), info.tag(), +// "Test set up failure -- copying constant pool (tag). \n" +// + "Orig: " + info.tag() + " [" + info.index() + "].\n" +// + "Copy: " + info2.tag() + " [" + info2.index() + "]."); +// i += info.tag().poolEntries; +// } +// writeAndCompareAttributes(classLow, cp); +// for (MethodLow m : classLow.methodsLow()) { +// m.findAttribute(Attributes.CODE).ifPresent(code -> +// writeAndCompareAttributes(code, cp)); +// } +// } + +// private void writeAndCompareAttributes(AttributeHolder ah, ConstantPoolLow cp) { +// for (AttributeLow attr : ah.attributes()) { +// if (attr instanceof UnknownAttribute) { +// System.err.printf("Unknown attribute %s - in %s%n", attr.attributeName(), path); +// } else if (attr instanceof BootstrapMethodsAttribute +// || attr instanceof StackMapTableAttribute) { +// // ignore +// } else { +// BufWriter gbb = new BufWriter(cp); +// BufWriter gbb2 = new BufWriter(cp); +// attr.writer().build(gbb); +// attr.writer().build(gbb2); +// assertEquals(gbb.bytes(), gbb2.bytes(), +// "Mismatched written attributes - " + attr); +// } +// } +// } +} diff --git a/test/jdk/jdk/classfile/LowModuleTest.java b/test/jdk/jdk/classfile/LowModuleTest.java new file mode 100644 index 0000000000000..5886ada824c5e --- /dev/null +++ b/test/jdk/jdk/classfile/LowModuleTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile low module attribute. + * @run junit LowModuleTest + */ +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; + +import jdk.internal.classfile.Attribute; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.constantpool.ModuleEntry; +import jdk.internal.classfile.constantpool.PackageEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * LowModuleTest + */ +class LowModuleTest { + + private static final boolean VERBOSE = false; + + static Path[] corpus() throws IOException { + return Files.walk(FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/")) + .filter(p -> Files.isRegularFile(p)) + .filter(p -> p.endsWith("module-info.class")) + .toArray(Path[]::new); + } + + @ParameterizedTest + @MethodSource("corpus") + void testRead(Path path, TestInfo test) throws Exception { + try { + printf("%nCHECK %s%n", test.getDisplayName()); + ClassModel classLow = Classfile.parse(path); + testRead0(classLow); + } catch(Exception ex) { + System.err.printf("%nFAIL %s - %s%n", path, ex); + ex.printStackTrace(System.err); + throw ex; + } + } + + private void testRead0(ClassModel classLow) { + for (Attribute attr : classLow.attributes()) { + switch (attr.attributeName()) { + case Attributes.NAME_SOURCE_FILE: { + SourceFileAttribute sfa = (SourceFileAttribute) attr; + Utf8Entry sf = sfa.sourceFile(); + printf("SourceFile %s%n", sf); + break; + } + case Attributes.NAME_MODULE: { + ModuleAttribute mal = (ModuleAttribute) attr; + ModuleEntry mni = mal.moduleName(); + int mf = mal.moduleFlagsMask(); + Utf8Entry mv = mal.moduleVersion().orElse(null); + printf("Module %s [%d] %s%n", mni, mf, mv); + for (ModuleRequireInfo r : mal.requires()) { + ModuleEntry rm = r.requires(); + int ri = r.requiresFlagsMask(); + Utf8Entry rv = r.requiresVersion().orElse(null); + printf(" Requires %s [%d] %s%n", rm, ri, rv); + } + for (ModuleExportInfo e : mal.exports()) { + printf(" Export %s [%d] - ", + e.exportedPackage(), e.exportsFlags()); + for (ModuleEntry mi : e.exportsTo()) { + printf("%s ", mi); + } + println(); + } + for (ModuleOpenInfo o : mal.opens()) { + printf(" Open %s [%d] - ", + o.openedPackage(), o.opensFlags()); + for (ModuleEntry mi : o.opensTo()) { + printf("%s ", mi); + } + println(); + } + for (ClassEntry u : mal.uses()) { + printf(" Use %s%n", u); + } + for (ModuleProvideInfo provide : mal.provides()) { + printf(" Provide %s - ", provide.provides()); + for (ClassEntry ci : provide.providesWith()) { + printf("%s ", ci); + } + println(); + } + break; + } + case Attributes.NAME_MODULE_PACKAGES: { + ModulePackagesAttribute mp = (ModulePackagesAttribute) attr; + printf("ModulePackages%n"); + for (PackageEntry pi : mp.packages()) { + printf(" %s%n", pi); + } + break; + } + case Attributes.NAME_MODULE_TARGET: { + ModuleTargetAttribute mt = (ModuleTargetAttribute) attr; + printf("ModuleTarget %s%n", mt.targetPlatform()); + break; + } + case Attributes.NAME_MODULE_RESOLUTION: { + ModuleResolutionAttribute mr = (ModuleResolutionAttribute) attr; + printf("ModuleResolution %d%n", mr.resolutionFlags()); + break; + } + case Attributes.NAME_MODULE_HASHES: { + ModuleHashesAttribute mh = (ModuleHashesAttribute) attr; + printf("ModuleHashes %s%n", mh.algorithm()); + for (ModuleHashInfo hi : mh.hashes()) { + printf(" %s: %n", hi.moduleName()); + for (byte b : hi.hash()) { + printf("%2x", b); + } + println(); + } + break; + } + } + } + } + + private void printf(String format, Object... args) { + if (VERBOSE) { + System.out.printf(format, args); + } + } + + private void println() { + if (VERBOSE) { + System.out.println(); + } + } + +// @Test +// public void testWrite() { +// try { +// testWrite0(); +// } catch(Exception ex) { +// System.err.printf("%nFAIL %s - %s%n", path, ex); +// ex.printStackTrace(System.err); +// throw ex; +// } +// } +// +// private void testWrite0() { +// ConstantPoolLow cp = classLow.constantPool(); +// ConstantPoolLow cp2 = cp.clonedPool(); +// int sz = cp.size(); +// // check match of constant pools +// assertEquals(cp2.size(), cp.size(), "Cloned size should match"); +// for (int i = 1; i < sz; ) { +// ConstantPoolInfo info = cp.get(i); +// ConstantPoolInfo info2 = cp2.get(i); +// assertNotNull(info2, "Test set up failure -- Null CP entry copy of " + info.tag() + " [" + info.index() + "] @ " +// + i +// + " -- " + getTestName() +// ); +// assertEquals(info2.index(), info.index(), +// "Test set up failure -- copying constant pool (index). \n" +// + "Orig: " + info.tag() + " [" + info.index() + "].\n" +// + "Copy: " + info2.tag() + " [" + info2.index() + "]."); +// assertEquals(info2.tag(), info.tag(), +// "Test set up failure -- copying constant pool (tag). \n" +// + "Orig: " + info.tag() + " [" + info.index() + "].\n" +// + "Copy: " + info2.tag() + " [" + info2.index() + "]."); +// i += info.tag().poolEntries; +// } +// for (Attribute attr : classLow.attributes()) { +// if (attr instanceof UnknownAttribute) { +// System.err.printf("Unknown attribute %s - in %s%n", attr.attributeName(), path); +// } else { +// BufWriter gbb = new BufWriter(cp); +// BufWriter gbb2 = new BufWriter(cp); +// attr.writeTo(gbb); +// attr.writeTo(gbb2); +// assertEquals(gbb2.bytes(), gbb.bytes(), +// "Mismatched written attributes - " + attr); +// } +// } +// } +} diff --git a/test/jdk/jdk/classfile/LvtTest.java b/test/jdk/jdk/classfile/LvtTest.java new file mode 100644 index 0000000000000..41231c6434efa --- /dev/null +++ b/test/jdk/jdk/classfile/LvtTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile local variable table. + * @compile -g testdata/Lvt.java + * @run junit LvtTest + */ +import helpers.ClassRecord; +import helpers.Transforms; +import jdk.internal.classfile.*; + +import java.io.*; +import java.lang.constant.ClassDesc; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jdk.internal.classfile.AccessFlags; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; +import java.lang.reflect.AccessFlag; +import org.junit.jupiter.api.Test; + +import static helpers.TestConstants.CD_ArrayList; +import static helpers.TestConstants.CD_PrintStream; +import static helpers.TestConstants.CD_System; +import static helpers.TestConstants.MTD_INT_VOID; +import static helpers.TestConstants.MTD_VOID; +import static helpers.TestUtil.ExpectedLvRecord; +import static helpers.TestUtil.ExpectedLvtRecord; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import static jdk.internal.classfile.Opcode.*; +import static jdk.internal.classfile.Opcode.INVOKEVIRTUAL; +import static jdk.internal.classfile.TypeKind.VoidType; +import static org.junit.jupiter.api.Assertions.*; + +class LvtTest { + static byte[] fileBytes; + + static { + try { + fileBytes = Files.readAllBytes(Paths.get(URI.create(testdata.Lvt.class.getResource("Lvt.class").toString()))); + } + catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Test + void getLVTEntries() { + ClassModel c = Classfile.parse(fileBytes); + CodeModel co = c.methods().stream() + .filter(mm -> mm.methodName().stringValue().equals("m")) + .map(MethodModel::code) + .findFirst() + .get() + .orElseThrow(); + + List lvs = new ArrayList<>(); + co.forEachElement(e -> { + if (e instanceof LocalVariable l) lvs.add(l); + }); + + List expected = List.of( + ExpectedLvRecord.of(5, "j", "I", 9, 21), + ExpectedLvRecord.of(0, "this", "Ltestdata/Lvt;", 0, 31), + ExpectedLvRecord.of(1, "a", "Ljava/lang/String;", 0, 31), + ExpectedLvRecord.of(4, "d", "[C", 6, 25)); + + // Exploits non-symmetric "equals" in ExpectedLvRecord + assertTrue(expected.equals(lvs)); + } + + @Test + void buildLVTEntries() throws Exception { + ClassModel c = Classfile.parse(fileBytes); + + // Compare transformed model and original with CodeBuilder filter + byte[] newClass = c.transform(Transforms.threeLevelNoop); + ClassRecord orig = ClassRecord.ofClassModel(Classfile.parse(fileBytes), ClassRecord.CompatibilityFilter.By_ClassBuilder); + ClassRecord transformed = ClassRecord.ofClassModel(Classfile.parse(newClass), ClassRecord.CompatibilityFilter.By_ClassBuilder); + ClassRecord.assertEqualsDeep(transformed, orig); + } + + @Test + void testCreateLoadLVT() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withVersion(52, 0); + cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + ) + .withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb + .withCode(c0 -> { + ConstantPoolBuilder cpb = cb.constantPool(); + Utf8Entry slotName = cpb.utf8Entry("this"); + Utf8Entry desc = cpb.utf8Entry("LMyClass;"); + Utf8Entry i1n = cpb.utf8Entry("res"); + Utf8Entry i2 = cpb.utf8Entry("i"); + Utf8Entry intSig = cpb.utf8Entry("I"); + Label start = c0.newLabel(); + Label end = c0.newLabel(); + Label i1 = c0.newLabel(); + Label preEnd = c0.newLabel(); + Label loopTop = c0.newLabel(); + Label loopEnd = c0.newLabel(); + c0.localVariable(1, i1n, intSig, i1, preEnd) // LV Entries can be added before the labels + .localVariable(2, i2, intSig, loopTop, preEnd) + .labelBinding(start) + .constantInstruction(ICONST_1, 1) // 0 + .storeInstruction(TypeKind.IntType, 1) // 1 + .labelBinding(i1) + .constantInstruction(ICONST_1, 1) // 2 + .storeInstruction(TypeKind.IntType, 2) // 3 + .labelBinding(loopTop) + .loadInstruction(TypeKind.IntType, 2) // 4 + .constantInstruction(BIPUSH, 10) // 5 + .branchInstruction(IF_ICMPGE, loopEnd) // 6 + .loadInstruction(TypeKind.IntType, 1) // 7 + .loadInstruction(TypeKind.IntType, 2) // 8 + .operatorInstruction(IMUL) // 9 + .storeInstruction(TypeKind.IntType, 1) // 10 + .incrementInstruction(2, 1) // 11 + .branchInstruction(GOTO, loopTop) // 12 + .labelBinding(loopEnd) + .fieldInstruction(GETSTATIC, CD_System, "out", CD_PrintStream) // 13 + .loadInstruction(TypeKind.IntType, 1) + .invokeInstruction(INVOKEVIRTUAL, CD_PrintStream, "println", MTD_INT_VOID, false) // 15 + .labelBinding(preEnd) + .returnInstruction(VoidType) + .labelBinding(end) + .localVariable(0, slotName, desc, start, end); // and lv entries can be added after the labels + })); + }); + + var c = Classfile.parse(bytes); + var main = c.methods().get(1); + var lvt = main.code().get().findAttribute(Attributes.LOCAL_VARIABLE_TABLE).get(); + var lvs = lvt.localVariables(); + + assertEquals(lvs.size(), 3); + List expected = List.of( + ExpectedLvRecord.of(1, "res", "I", 2, 25), + ExpectedLvRecord.of(2, "i", "I", 4, 23), + ExpectedLvRecord.of(0, "this", "LMyClass;", 0, 28)); + + // Exploits non-symmetric "equals" in ExpectedLvRecord + assertTrue(expected.equals(lvs)); + } + + @Test + void getLVTTEntries() { + ClassModel c = Classfile.parse(fileBytes); + CodeModel co = c.methods().stream() + .filter(mm -> mm.methodName().stringValue().equals("n")) + .map(MethodModel::code) + .findFirst() + .get() + .orElseThrow(); + + List lvts = new ArrayList<>(); + co.forEachElement(e -> { + if (e instanceof LocalVariableType l) lvts.add(l); + }); + + /* From javap: + + LocalVariableTypeTable: + Start Length Slot Name Signature + 51 8 6 f Ljava/util/List<*>; + 0 64 1 u TU; + 0 64 2 z Ljava/lang/Class<+Ljava/util/List<*>;>; + 8 56 3 v Ljava/util/ArrayList; + 17 47 4 s Ljava/util/Set<-Ljava/util/Set;>; + */ + + List expected = List.of( + ExpectedLvtRecord.of(6, "f", "Ljava/util/List<*>;", 51, 8), + ExpectedLvtRecord.of(1, "u", "TU;", 0, 64), + ExpectedLvtRecord.of(2, "z", "Ljava/lang/Class<+Ljava/util/List<*>;>;", 0, 64), + ExpectedLvtRecord.of(3, "v", "Ljava/util/ArrayList;", 8, 56), + ExpectedLvtRecord.of(4, "s", "Ljava/util/Set<-Ljava/util/Set<*>;>;", 17, 47) + ); + + // Exploits non-symmetric "equals" in ExpectedLvRecord + for (int i = 0; i < lvts.size(); i++) { + assertTrue(expected.get(i).equals(lvts.get(i))); + } + } + + @Test + void testCreateLoadLVTT() throws Exception { + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withVersion(52, 0); + cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + ) + + .withMethod("m", MethodTypeDesc.of(CD_Object, CD_Object.arrayType()), + Classfile.ACC_PUBLIC, + mb -> mb.withFlags(AccessFlag.PUBLIC) + .withCode(c0 -> { + ConstantPoolBuilder cpb = cb.constantPool(); + Utf8Entry slotName = cpb.utf8Entry("this"); + Utf8Entry desc = cpb.utf8Entry("LMyClass;"); + Utf8Entry juList = cpb.utf8Entry("Ljava/util/List;"); + Utf8Entry TU = cpb.utf8Entry("TU;"); + Utf8Entry sig = cpb.utf8Entry("Ljava/util/List<+Ljava/lang/Object;>;"); + Utf8Entry l = cpb.utf8Entry("l"); + Utf8Entry jlObject = cpb.utf8Entry("Ljava/lang/Object;"); + Utf8Entry u = cpb.utf8Entry("u"); + + Label start = c0.newLabel(); + Label end = c0.newLabel(); + Label beforeRet = c0.newLabel(); + + c0.localVariable(2, l, juList, beforeRet, end) + .localVariableType(1, u, TU, start, end) + .labelBinding(start) + .newObjectInstruction(ClassDesc.of("java.util.ArrayList")) + .stackInstruction(DUP) + .invokeInstruction(INVOKESPECIAL, CD_ArrayList, "", MTD_VOID, false) + .storeInstruction(TypeKind.ReferenceType, 2) + .labelBinding(beforeRet) + .localVariableType(2, l, sig, beforeRet, end) + .loadInstruction(TypeKind.ReferenceType, 1) + .returnInstruction(TypeKind.ReferenceType) + .labelBinding(end) + .localVariable(0, slotName, desc, start, end) + .localVariable(1, u, jlObject, start, end); + })); + }); + var c = Classfile.parse(bytes); + var main = c.methods().get(1); + var lvtt = main.code().get().findAttribute(Attributes.LOCAL_VARIABLE_TYPE_TABLE).get(); + var lvts = lvtt.localVariableTypes(); + + /* From javap: + + LocalVariableTypeTable: + Start Length Slot Name Signature + 0 10 1 u TU; + 8 2 2 l Ljava/util/List<+Ljava/lang/Object;>; + */ + + List expected = List.of( + ExpectedLvtRecord.of(1, "u", "TU;", 0, 10), + ExpectedLvtRecord.of(2, "l", "Ljava/util/List<+Ljava/lang/Object;>;", 8, 2) + ); + + // Exploits non-symmetric "equals" in ExpectedLvRecord + for (int i = 0; i < lvts.size(); i++) { + assertTrue(expected.get(i).equals(lvts.get(i))); + } + } + + @Test + void skipDebugSkipsLVT() { + ClassModel c = Classfile.parse(fileBytes, Classfile.Option.processDebug(false)); + + c.forEachElement(e -> { + if (e instanceof MethodModel m) { + m.forEachElement(el -> { + if (el instanceof CodeModel cm) { + cm.forEachElement(elem -> { + assertFalse(elem instanceof LocalVariable); + assertFalse(elem instanceof LocalVariableType); + }); + } + }); + } + }); + } +} diff --git a/test/jdk/jdk/classfile/MassAdaptCopyCodeTest.java b/test/jdk/jdk/classfile/MassAdaptCopyCodeTest.java new file mode 100644 index 0000000000000..9cbba5e9d89a3 --- /dev/null +++ b/test/jdk/jdk/classfile/MassAdaptCopyCodeTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile massive class adaptation. + * @run junit MassAdaptCopyCodeTest + */ +import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.MethodModel; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class MassAdaptCopyCodeTest { + + //final static String testClasses = "target/classes"; // "/w/basejdk/build/linux-x86_64-server-release/jdk/modules/java.base" + + final Map classNameToClass = new HashMap<>(); + String base; + + @Test + void testInstructionAdapt() throws Exception { + File root = Paths.get(URI.create(MassAdaptCopyCodeTest.class.getResource("MassAdaptCopyCodeTest.class").toString())).getParent().toFile(); + base = root.getCanonicalPath(); + copy(root); + load(); + } + + void copy(File f) throws Exception { + if (f.isDirectory()) { + for (File lf : f.listFiles()) { + copy(lf); + } + } + else { + String n = f.getCanonicalPath().substring(base.length() + 1); + if (n.endsWith(".class") && !n.endsWith("module-info.class") && !n.endsWith("-split.class")) { + copy(n.substring(0, n.length() - 6).replace(File.separatorChar, '.'), + Files.readAllBytes(f.toPath())); + } + } + } + + void copy(String name, byte[] bytes) throws Exception { + byte[] newBytes = adaptCopy(Classfile.parse(bytes)); + classNameToClass.put(name, new ByteArrayClassLoader.ClassData(name, newBytes)); + if (name.contains("/")) throw new RuntimeException(name); + } + + public byte[] adaptCopy(ClassModel cm) { + return cm.transform((cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, CodeTransform.ACCEPT_ALL); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }); + } + + public void load() throws Exception { + new ByteArrayClassLoader(MassAdaptCopyCodeTest.class.getClassLoader(), classNameToClass) + .loadAll(); + } +} diff --git a/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java b/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java new file mode 100644 index 0000000000000..d43a79cbf64a1 --- /dev/null +++ b/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile massive class adaptation. + * @run junit MassAdaptCopyPrimitiveMatchCodeTest + */ +import helpers.InstructionModelToCodeBuilder; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.attribute.CodeAttribute; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.instruction.InvokeInstruction; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * MassAdaptCopyPrimitiveMatchCodeTest. + */ +class MassAdaptCopyPrimitiveMatchCodeTest { + + final static List testClasses(Path which) { + try { + return Files.walk(which) + .filter(p -> Files.isRegularFile(p)) + .filter(p -> p.toString().endsWith(".class")) + .toList(); + } catch (IOException ex) { + throw new AssertionError("Test failed in set-up - " + ex.getMessage(), ex); + } + } + + final static List testClasses = testClasses( + //FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util") + //Path.of("target", "classes") + Paths.get(URI.create(MassAdaptCopyPrimitiveMatchCodeTest.class.getResource("MassAdaptCopyPrimitiveMatchCodeTest.class").toString())).getParent() + ); + + String base; + boolean failure; + + @Test + @Disabled("for a reason...") + public void testCodeMatch() throws Exception { + for (Path path : testClasses) { + try { + copy(path.toString(), + Files.readAllBytes(path)); + if (failure) { + fail("Copied bytecode does not match: " + path); + } + } catch(Throwable ex) { + System.err.printf("FAIL: MassAdaptCopyPrimitiveMatchCodeTest - %s%n", ex.getMessage()); + ex.printStackTrace(System.err); + throw ex; + } + } + } + + void copy(String name, byte[] bytes) throws Exception { + //System.err.printf("MassAdaptCopyPrimitiveMatchCodeTest - %s%n", name); + ClassModel cm =(Classfile.parse(bytes)); + Map m2b = new HashMap<>(); + Map m2c = new HashMap<>(); + byte[] resultBytes = + cm.transform((cb, e) -> { + if (e instanceof MethodModel mm) { + Optional code = mm.code(); + if (code.isPresent()) { + CodeAttribute cal = (CodeAttribute) code.get(); + byte[] mbytes = cal.codeArray(); + String key = methodToKey(mm); + m2b.put(key, mbytes); + m2c.put(key, cal); + } + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) + mb.transformCode(xm, (xr, xe) -> InstructionModelToCodeBuilder.toBuilder(xe, xr)); + else + mb.with(me); + }); + } + else + cb.with(e); + }); + //TODO: work-around to compiler bug generating multiple constant pool entries within records + if (cm.findAttribute(Attributes.RECORD).isPresent()) { + System.err.printf("MassAdaptCopyPrimitiveMatchCodeTest: Ignored because it is a record%n - %s%n", name); + return; + } + ClassModel rcm = Classfile.parse(resultBytes); + for (MethodModel rmm : rcm.methods()) { + Optional code = rmm.code(); + if (code.isPresent()) { + CodeModel codeModel = code.get(); + CodeAttribute rcal = (CodeAttribute) codeModel; + String key = methodToKey(rmm); + byte[] rbytes = rcal.codeArray(); + byte[] obytes = m2b.get(key); + if (!Arrays.equals(rbytes, obytes)) { + System.err.printf("Copy has mismatched bytecode -- Method: %s.%s%n", name, rmm.methodName().stringValue()); + boolean secondFailure = false; + failure = true; + int rlen = rcal.codeLength(); + CodeAttribute ocal = m2c.get(key); + int olen = ocal.codeLength(); + if (rlen != olen) { + System.err.printf(" Lengths do not match: orig != copy: %d != %d%n", olen, rlen); + } + int len = Math.max(rlen, olen); + // file instructions + CodeElement[] rima = new Instruction[len]; + CodeElement[] oima = new Instruction[len]; + int bci = 0; + for (CodeElement im : codeModel) { + if (im instanceof Instruction i) { + rima[bci] = im; + bci += i.sizeInBytes(); + } + } + bci = 0; + for (CodeElement im : ((CodeModel) ocal)) { + if (im instanceof Instruction i) { + oima[bci] = im; + bci += i.sizeInBytes(); + } + } + // find first bad BCI and instruction + int bciCurrentInstruction = -1; + int bciFirstBadInstruction = -1; + for (int i = 0; i < len; ++i) { + if (oima[i] != null) { + bciCurrentInstruction = i; + } + if (obytes[i] != rbytes[i]) { + if (bciFirstBadInstruction < 0) { + System.err.printf(" bytecode differs firstly at BCI [%d]; expected value is <%d> but was <%d>. Instruction BCI %d%n", + i, obytes[i], rbytes[i], bciCurrentInstruction); + bciFirstBadInstruction = bciCurrentInstruction; + } else { + secondFailure = true; + break; + } + } + } + System.err.printf(" BCI Orig Copy Original ---> Copy%n"); + for (int i = 0; i < len; ++i) { + System.err.printf(" %4d ", i); + if (i < olen) + System.err.printf("%4d ", obytes[i] & 0xFF); + else + System.err.printf(" "); + if (i < rlen) + System.err.printf("%4d ", rbytes[i] & 0xFF); + else + System.err.printf(" "); + CodeElement oim = oima[i]; + if (oim != null) + System.err.printf("%s ", oim); + CodeElement rim = rima[i]; + if (rim != null) + System.err.printf("---> %s ", rim); + System.err.printf("%n"); + if (bciFirstBadInstruction == i + && oim instanceof InvokeInstruction oii + && rim instanceof InvokeInstruction rii) { + if (oii.isInterface() == rii.isInterface() + && oii.name().stringValue().equals(rii.name().stringValue()) + && oii.owner().asInternalName().equals(rii.owner().asInternalName()) + && oii.type().stringValue().equals(rii.type().stringValue()) + && oii.count() == rii.count() + && oii.sizeInBytes() == rii.sizeInBytes() + && oii.opcode() == rii.opcode()) { + // they match, so was duplicate CP entries, e.g Object.clone() + // get a pass if this was the only failure + System.err.printf("NVM - duplicate CP entry -- ignored%n"); + failure = secondFailure; + } + } + } + } + } + } + } + + String methodToKey(MethodModel mm) { + return mm.methodName().stringValue() + "@" + mm.methodType().stringValue() + (mm.flags().has(AccessFlag.STATIC) ? "$" : "!"); + } + + +} diff --git a/test/jdk/jdk/classfile/ModuleBuilderTest.java b/test/jdk/jdk/classfile/ModuleBuilderTest.java new file mode 100644 index 0000000000000..015c8c494306d --- /dev/null +++ b/test/jdk/jdk/classfile/ModuleBuilderTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile building module. + * @run junit ModuleBuilderTest + */ +import jdk.internal.classfile.*; + +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleExportInfo; +import jdk.internal.classfile.attribute.ModuleMainClassAttribute; +import jdk.internal.classfile.attribute.ModuleOpenInfo; +import jdk.internal.classfile.attribute.ModulePackagesAttribute; +import jdk.internal.classfile.attribute.ModuleProvideInfo; +import jdk.internal.classfile.attribute.ModuleRequireInfo; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import org.junit.jupiter.api.Test; + +import java.lang.constant.ClassDesc; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class ModuleBuilderTest { + private final ModuleDesc modName = ModuleDesc.of("some.module.structure"); + private final String modVsn = "ab75"; + private final ModuleDesc require1 = ModuleDesc.of("1require.some.mod"); String vsn1 = "1the.best.version"; + private final ModuleDesc require2 = ModuleDesc.of("2require.some.mod"); String vsn2 = "2the.best.version"; + private final ModuleDesc[] et1 = new ModuleDesc[] {ModuleDesc.of("1t1"), ModuleDesc.of("1t2")}; + private final ModuleDesc[] et2 = new ModuleDesc[] {ModuleDesc.of("2t1")}; + private final ModuleDesc[] et3 = new ModuleDesc[] {ModuleDesc.of("3t1"), ModuleDesc.of("3t2"), ModuleDesc.of("3t3")}; + private final ModuleDesc[] ot3 = new ModuleDesc[] {ModuleDesc.of("t1"), ModuleDesc.of("t2")}; + + private final ClassModel moduleModel; + private final ModuleAttribute attr; + + public ModuleBuilderTest() { + byte[] modInfo = Classfile.buildModule( + ModuleAttribute.of(modName, mb -> mb + .moduleVersion(modVsn) + + .requires(require1, 77, vsn1) + .requires(require2, 99, vsn2) + + .exports(PackageDesc.of("0"), 0, et1) + .exports(PackageDesc.of("1"), 1, et2) + .exports(PackageDesc.of("2"), 2, et3) + .exports(PackageDesc.of("3"), 3) + .exports(PackageDesc.of("4"), 4) + + .opens(PackageDesc.of("o0"), 0) + .opens(PackageDesc.of("o1"), 1) + .opens(PackageDesc.of("o2"), 2, ot3) + + .uses(ClassDesc.of("some.Service")) + .uses(ClassDesc.of("another.Service")) + + .provides(ClassDesc.of("some.nice.Feature"), ClassDesc.of("impl"), ClassDesc.of("another.impl"))), + List.of(PackageDesc.of("foo.bar.baz"), PackageDesc.of("quux"), PackageDesc.of("foo.bar.baz"), PackageDesc.of("quux")), + clb -> clb.with(ModuleMainClassAttribute.of(ClassDesc.of("main.Class"))) + .with(ModuleMainClassAttribute.of(ClassDesc.of("overwritten.main.Class")))); + moduleModel = Classfile.parse(modInfo); + attr = ((ModuleAttribute) moduleModel.attributes().stream() + .filter(a -> a.attributeMapper() == Attributes.MODULE) + .findFirst() + .orElseThrow()); + } + + @Test + void testCreateModuleInfo() { + // Build the module-info.class bytes + byte[] modBytes = Classfile.buildModule(ModuleAttribute.of(modName, mb -> mb.moduleVersion(modVsn))); + + // Verify + var cm = Classfile.parse(modBytes); + + var attr =cm.findAttribute(Attributes.MODULE).get(); + assertEquals(attr.moduleName().name().stringValue(), modName.moduleName()); + assertEquals(attr.moduleFlagsMask(), 0); + assertEquals(attr.moduleVersion().get().stringValue(), modVsn); + } + + @Test + void testAllAttributes() { + assertEquals(moduleModel.attributes().size(), 3); + } + + @Test + void testVerifyRequires() { + assertEquals(attr.requires().size(), 2); + ModuleRequireInfo r = attr.requires().get(0); + assertEquals(r.requires().name().stringValue(), require1.moduleName()); + assertEquals(r.requiresVersion().get().stringValue(), vsn1); + assertEquals(r.requiresFlagsMask(), 77); + + r = attr.requires().get(1); + assertEquals(r.requires().name().stringValue(), require2.moduleName()); + assertEquals(r.requiresVersion().get().stringValue(), vsn2); + assertEquals(r.requiresFlagsMask(), 99); + } + + @Test + void testVerifyExports() { + List exports = attr.exports(); + assertEquals(exports.size(),5); + for (int i = 0; i < 5; i++) { + assertEquals(exports.get(i).exportsFlagsMask(), i); + assertEquals(exports.get(i).exportedPackage().name().stringValue(), String.valueOf(i)); + } + assertEquals(exports.get(0).exportsTo().size(), 2); + for (int i = 0; i < 2; i++) + assertEquals(exports.get(0).exportsTo().get(i).name().stringValue(), et1[i].moduleName()); + + assertEquals(exports.get(1).exportsTo().size(), 1); + assertEquals(exports.get(1).exportsTo().get(0).name().stringValue(), et2[0].moduleName()); + + assertEquals(exports.get(2).exportsTo().size(), 3); + for (int i = 0; i < 3; i++) + assertEquals(exports.get(2).exportsTo().get(i).name().stringValue(), et3[i].moduleName()); + + assertEquals(exports.get(3).exportsTo().size(), 0); + assertEquals(exports.get(4).exportsTo().size(), 0); + } + + @Test + void testVerifyOpens() { + List opens = attr.opens(); + assertEquals(opens.size(), 3); + assertEquals(opens.get(0).opensTo().size(), 0); + assertEquals(opens.get(1).opensTo().size(), 0); + assertEquals(opens.get(2).opensTo().size(), 2); + assertEquals(opens.get(2).opensFlagsMask(), 2); + assertEquals(opens.get(2).opensTo().get(1).name().stringValue(), ot3[1].moduleName()); + } + + @Test + void testVerifyUses() { + var uses = attr.uses(); + assertEquals(uses.size(), 2); + assertEquals(uses.get(1).asInternalName(), "another/Service"); + } + + @Test + void testVerifyProvides() { + var provides = attr.provides(); + assertEquals(provides.size(), 1); + ModuleProvideInfo p = provides.get(0); + assertEquals(p.provides().asInternalName(), "some/nice/Feature"); + assertEquals(p.providesWith().size(), 2); + assertEquals(p.providesWith().get(1).asInternalName(), "another/impl"); + } + + @Test + void verifyPackages() { + ModulePackagesAttribute a = moduleModel.findAttribute(Attributes.MODULE_PACKAGES).orElseThrow(); + assertEquals(a.packages().stream().map(pe -> pe.asSymbol().packageName()).toList(), List.of("0", "1", "2", "3", "4", "o0", "o1", "o2", "foo.bar.baz", "quux")); + } + + @Test + void verifyMainclass() { + ModuleMainClassAttribute a = moduleModel.findAttribute(Attributes.MODULE_MAIN_CLASS).orElseThrow(); + assertEquals(a.mainClass().asInternalName(), "overwritten/main/Class"); + } + + @Test + void verifyIsModuleInfo() throws Exception { + assertTrue(moduleModel.isModuleInfo()); + + ClassModel m = Classfile.parse(Paths.get(URI.create(ModuleBuilderTest.class.getResource("ModuleBuilderTest.class").toString()))); + assertFalse(m.isModuleInfo()); + } +} diff --git a/test/jdk/jdk/classfile/ModuleDescTest.java b/test/jdk/jdk/classfile/ModuleDescTest.java new file mode 100644 index 0000000000000..bbf0fffddc6f0 --- /dev/null +++ b/test/jdk/jdk/classfile/ModuleDescTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing ModuleDesc. + * @run junit ModuleDescTest + */ +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.junit.jupiter.api.Assertions.*; + +class ModuleDescTest { + + @ParameterizedTest + @ValueSource(strings = {"abc\\", "ab\\c", "\u0000", "\u0001", "\u001e", "\u001f"}) + public void testInvalidModuleNames(String mdl) { + assertThrows(IllegalArgumentException.class, () -> ModuleDesc.of(mdl)); + } + + @ParameterizedTest + @ValueSource(strings = {"a\\\\b", "a.b/c", "a\\@b\\: c"}) + public void testValidModuleNames(String mdl) { + assertEquals(ModuleDesc.of(mdl), ModuleDesc.of(mdl)); + assertEquals(ModuleDesc.of(mdl).moduleName(), mdl); + } +} diff --git a/test/jdk/jdk/classfile/OneToOneTest.java b/test/jdk/jdk/classfile/OneToOneTest.java new file mode 100644 index 0000000000000..6e85b4046cbe6 --- /dev/null +++ b/test/jdk/jdk/classfile/OneToOneTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile class writing and reading. + * @run junit OneToOneTest + */ +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import java.util.List; + +import jdk.internal.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.OperatorInstruction; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; + +import static helpers.TestConstants.CD_PrintStream; +import static helpers.TestConstants.CD_System; +import static helpers.TestConstants.MTD_INT_VOID; +import static helpers.TestConstants.MTD_VOID; +import static jdk.internal.classfile.Opcode.*; + +class OneToOneTest { + + @Test + void testClassWriteRead() { + + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withVersion(52, 0); + cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(TypeKind.VoidType) + ) + ) + .withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.STATIC, AccessFlag.PUBLIC).flagsMask(), + mb -> mb.withCode(c0 -> { + Label loopTop = c0.newLabel(); + Label loopEnd = c0.newLabel(); + int fac = 1; + int i = 2; + c0.constantInstruction(ICONST_1, 1) // 0 + .storeInstruction(TypeKind.IntType, fac) // 1 + .constantInstruction(ICONST_1, 1) // 2 + .storeInstruction(TypeKind.IntType, i) // 3 + .labelBinding(loopTop) + .loadInstruction(TypeKind.IntType, i) // 4 + .constantInstruction(BIPUSH, 10) // 5 + .branchInstruction(IF_ICMPGE, loopEnd) // 6 + .loadInstruction(TypeKind.IntType, fac) // 7 + .loadInstruction(TypeKind.IntType, i) // 8 + .operatorInstruction(IMUL) // 9 + .storeInstruction(TypeKind.IntType, fac) // 10 + .incrementInstruction(i, 1) // 11 + .branchInstruction(GOTO, loopTop) // 12 + .labelBinding(loopEnd) + .fieldInstruction(GETSTATIC, CD_System, "out", CD_PrintStream) // 13 + .loadInstruction(TypeKind.IntType, fac) + .invokeInstruction(INVOKEVIRTUAL, CD_PrintStream, "println", MTD_INT_VOID, false) // 15 + .returnInstruction(TypeKind.VoidType); + } + ) + ); + } + ); + + ClassModel cm = Classfile.parse(bytes); + List ms = cm.methods(); + assertEquals(ms.size(), 2); + boolean found = false; + for (MethodModel mm : ms) { + if (mm.methodName().stringValue().equals("main") && mm.code().isPresent()) { + found = true; + var code = mm.code().get(); + var instructions = code.elementList().stream() + .filter(e -> e instanceof Instruction) + .map(e -> (Instruction)e) + .toList(); + assertEquals(instructions.size(), 17); + + assertEquals(instructions.get(0).opcode(), ICONST_1); + + var i1 = (StoreInstruction) instructions.get(1); + assertEquals(i1.opcode(), ISTORE_1); + int lv1 = i1.slot(); + assertEquals(lv1, 1); + + ConstantInstruction i5 = (ConstantInstruction) instructions.get(5); + assertEquals(i5.opcode(), BIPUSH); + assertEquals(i5.constantValue(), 10); + + BranchInstruction i6 = (BranchInstruction) instructions.get(6); + assertEquals(i6.opcode(), IF_ICMPGE); + // assertEquals(code.instructionOffset(i6.target()), 14); //FIXME: CodeModel gives BCI, should give instruction offset + + LoadInstruction i7 = (LoadInstruction) instructions.get(7); + assertEquals(i7.opcode(), ILOAD_1); + + OperatorInstruction i9 = (OperatorInstruction) instructions.get(9); + assertEquals(i9.opcode(), IMUL); + + FieldInstruction i13 = (FieldInstruction) instructions.get(13); + assertEquals(i13.opcode(), GETSTATIC); + assertEquals(i13.owner().asInternalName(), "java/lang/System"); + assertEquals(i13.name().stringValue(), "out"); + assertEquals(i13.type().stringValue(), "Ljava/io/PrintStream;"); + + InvokeInstruction i15 = (InvokeInstruction) instructions.get(15); + assertEquals(i15.opcode(), INVOKEVIRTUAL); + assertEquals(i15.owner().asInternalName(), "java/io/PrintStream"); + assertEquals(i15.name().stringValue(), "println"); + assertEquals(i15.type().stringValue(), "(I)V"); + } + } + assertTrue(found); + } +} diff --git a/test/jdk/jdk/classfile/OpcodesValidationTest.java b/test/jdk/jdk/classfile/OpcodesValidationTest.java new file mode 100644 index 0000000000000..43e2ec630d596 --- /dev/null +++ b/test/jdk/jdk/classfile/OpcodesValidationTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile constant instruction opcodes. + * @run junit OpcodesValidationTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import static java.lang.constant.ConstantDescs.CD_void; +import java.lang.constant.MethodTypeDesc; + +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Opcode; +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.Assertions.*; +import static jdk.internal.classfile.Opcode.*; +import java.util.stream.Stream; + +public class OpcodesValidationTest { + + record Case(Opcode opcode, Object constant) {} + + static Stream positiveCases() { + return Stream.of( + new Case(ACONST_NULL, null), + new Case(SIPUSH, (int)Short.MIN_VALUE), + new Case(SIPUSH, (int)Short.MAX_VALUE), + new Case(BIPUSH, (int)Byte.MIN_VALUE), + new Case(BIPUSH, (int)Byte.MAX_VALUE), + new Case(ICONST_M1, -1), + new Case(ICONST_0, 0), + new Case(ICONST_1, 1), + new Case(ICONST_2, 2), + new Case(ICONST_3, 3), + new Case(ICONST_4, 4), + new Case(ICONST_5, 5), + new Case(LCONST_0, 0l), + new Case(LCONST_0, 0), + new Case(LCONST_1, 1l), + new Case(LCONST_1, 1), + new Case(FCONST_0, 0.0f), + new Case(FCONST_1, 1.0f), + new Case(FCONST_2, 2.0f), + new Case(DCONST_0, 0.0d), + new Case(DCONST_1, 1.0d) + ); + } + + static Stream negativeCases() { + return Stream.of( + new Case(ACONST_NULL, 0), + new Case(SIPUSH, (int)Short.MIN_VALUE - 1), + new Case(SIPUSH, (int)Short.MAX_VALUE + 1), + new Case(BIPUSH, (int)Byte.MIN_VALUE - 1), + new Case(BIPUSH, (int)Byte.MAX_VALUE + 1), + new Case(ICONST_M1, -1l), + new Case(ICONST_0, 0l), + new Case(ICONST_1, 1l), + new Case(ICONST_2, 2l), + new Case(ICONST_3, 3l), + new Case(ICONST_4, 4l), + new Case(ICONST_5, 5l), + new Case(LCONST_0, null), + new Case(LCONST_0, 1l), + new Case(LCONST_1, 1.0d), + new Case(LCONST_1, 0), + new Case(FCONST_0, 0.0d), + new Case(FCONST_1, 1.01f), + new Case(FCONST_2, 2), + new Case(DCONST_0, 0.0f), + new Case(DCONST_1, 1.0f), + new Case(DCONST_1, 1) + ); + } + + @TestFactory + Stream testPositiveCases() { + return positiveCases().map(c -> dynamicTest(c.toString(), () -> testPositiveCase(c.opcode, c.constant))); + } + + private void testPositiveCase(Opcode opcode, Object constant) { + Classfile.build(ClassDesc.of("MyClass"), + cb -> cb.withFlags(AccessFlag.PUBLIC) + .withMethod("", MethodTypeDesc.of(CD_void), 0, + mb -> mb.withCode( + codeb -> codeb.constantInstruction(opcode, (ConstantDesc) constant)))); + } + + + @TestFactory + Stream testNegativeCases() { + return negativeCases().map(c -> dynamicTest( + c.toString(), + () -> assertThrows(IllegalArgumentException.class, () -> testNegativeCase(c.opcode, c.constant)) + )); + } + + private void testNegativeCase(Opcode opcode, Object constant) { + Classfile.build(ClassDesc.of("MyClass"), + cb -> cb.withFlags(AccessFlag.PUBLIC) + .withMethod("", MethodTypeDesc.of(CD_void), 0, + mb -> mb .withCode( + codeb -> codeb.constantInstruction(opcode, (ConstantDesc)constant)))); + } +} diff --git a/test/jdk/jdk/classfile/PackageDescTest.java b/test/jdk/jdk/classfile/PackageDescTest.java new file mode 100644 index 0000000000000..3d52a2153f108 --- /dev/null +++ b/test/jdk/jdk/classfile/PackageDescTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing PackageDesc. + * @run junit PackageDescTest + */ +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PackageDescTest { + @ParameterizedTest + @ValueSource(strings = {"a/b.d", "a[]", "a;"}) + void testInvalidPackageNames(String pkg) { + assertThrows(IllegalArgumentException.class, () -> PackageDesc.of(pkg)); + } + + @ParameterizedTest + @ValueSource(strings = {"a/b.d", "a[]", "a;"}) + void testInvalidInternalPackageNames(String pkg) { + assertThrows(IllegalArgumentException.class, () -> PackageDesc.ofInternalName(pkg)); + } + + @Test + void testValidPackageNames() { + assertEquals(PackageDesc.of("a"), PackageDesc.ofInternalName("a")); + assertEquals(PackageDesc.of("a.b"), PackageDesc.ofInternalName("a/b")); + assertEquals(PackageDesc.of("a.b.c"), PackageDesc.ofInternalName("a/b/c")); + assertEquals(PackageDesc.of("a").packageName(), PackageDesc.ofInternalName("a").packageName()); + assertEquals(PackageDesc.of("a.b").packageName(), PackageDesc.ofInternalName("a/b").packageName()); + assertEquals(PackageDesc.of("a.b.c").packageName(), PackageDesc.ofInternalName("a/b/c").packageName()); + assertEquals(PackageDesc.of("a").packageInternalName(), PackageDesc.ofInternalName("a").packageInternalName()); + assertEquals(PackageDesc.of("a.b").packageInternalName(), PackageDesc.ofInternalName("a/b").packageInternalName()); + assertEquals(PackageDesc.of("a.b.c").packageInternalName(), PackageDesc.ofInternalName("a/b/c").packageInternalName()); + } +} diff --git a/test/jdk/jdk/classfile/ShortJumpsFixTest.java b/test/jdk/jdk/classfile/ShortJumpsFixTest.java new file mode 100644 index 0000000000000..63219041951c4 --- /dev/null +++ b/test/jdk/jdk/classfile/ShortJumpsFixTest.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile short to long jumps extension. + * @run junit ShortJumpsFixTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.LinkedList; +import java.util.List; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.Opcode; +import static jdk.internal.classfile.Opcode.*; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.NopInstruction; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +class ShortJumpsFixTest { + + record Sample(Opcode jumpCode, Opcode... expected) { + @Override + public String toString() { + return jumpCode.name(); + } + } + + static Sample[] provideFwd() { + return new Sample[]{ + //first is transformed opcode, followed by constant instructions and expected output + new Sample(GOTO, GOTO_W, NOP, ATHROW, RETURN), + new Sample(IFEQ, ICONST_0, IFNE, GOTO_W, NOP, RETURN), + new Sample(IFNE, ICONST_0, IFEQ, GOTO_W, NOP, RETURN), + new Sample(IFLT, ICONST_0, IFGE, GOTO_W, NOP, RETURN), + new Sample(IFGE, ICONST_0, IFLT, GOTO_W, NOP, RETURN), + new Sample(IFGT, ICONST_0, IFLE, GOTO_W, NOP, RETURN), + new Sample(IFLE, ICONST_0, IFGT, GOTO_W, NOP, RETURN), + new Sample(IF_ICMPEQ, ICONST_0, ICONST_1, IF_ICMPNE, GOTO_W, NOP, RETURN), + new Sample(IF_ICMPNE, ICONST_0, ICONST_1, IF_ICMPEQ, GOTO_W, NOP, RETURN), + new Sample(IF_ICMPLT, ICONST_0, ICONST_1, IF_ICMPGE, GOTO_W, NOP, RETURN), + new Sample(IF_ICMPGE, ICONST_0, ICONST_1, IF_ICMPLT, GOTO_W, NOP, RETURN), + new Sample(IF_ICMPGT, ICONST_0, ICONST_1, IF_ICMPLE, GOTO_W, NOP, RETURN), + new Sample(IF_ICMPLE, ICONST_0, ICONST_1, IF_ICMPGT, GOTO_W, NOP, RETURN), + new Sample(IF_ACMPEQ, ICONST_0, ICONST_1, IF_ACMPNE, GOTO_W, NOP, RETURN), + new Sample(IF_ACMPNE, ICONST_0, ICONST_1, IF_ACMPEQ, GOTO_W, NOP, RETURN), + new Sample(IFNULL, ACONST_NULL, IFNONNULL, GOTO_W, NOP, RETURN), + new Sample(IFNONNULL, ACONST_NULL, IFNULL, GOTO_W, NOP, RETURN), + }; + } + + static Sample[] provideBack() { + return new Sample[]{ + new Sample(GOTO, GOTO_W, NOP, RETURN, GOTO_W, ATHROW), + new Sample(IFEQ, GOTO_W, NOP, RETURN, ICONST_0, IFNE, GOTO_W, RETURN), + new Sample(IFNE, GOTO_W, NOP, RETURN, ICONST_0, IFEQ, GOTO_W, RETURN), + new Sample(IFLT, GOTO_W, NOP, RETURN, ICONST_0, IFGE, GOTO_W, RETURN), + new Sample(IFGE, GOTO_W, NOP, RETURN, ICONST_0, IFLT, GOTO_W, RETURN), + new Sample(IFGT, GOTO_W, NOP, RETURN, ICONST_0, IFLE, GOTO_W, RETURN), + new Sample(IFLE, GOTO_W, NOP, RETURN, ICONST_0, IFGT, GOTO_W, RETURN), + new Sample(IF_ICMPEQ, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ICMPNE, GOTO_W, RETURN), + new Sample(IF_ICMPNE, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ICMPEQ, GOTO_W, RETURN), + new Sample(IF_ICMPLT, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ICMPGE, GOTO_W, RETURN), + new Sample(IF_ICMPGE, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ICMPLT, GOTO_W, RETURN), + new Sample(IF_ICMPGT, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ICMPLE, GOTO_W, RETURN), + new Sample(IF_ICMPLE, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ICMPGT, GOTO_W, RETURN), + new Sample(IF_ACMPEQ, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ACMPNE, GOTO_W, RETURN), + new Sample(IF_ACMPNE, GOTO_W, NOP, RETURN, ICONST_0, ICONST_1, IF_ACMPEQ, GOTO_W, RETURN), + new Sample(IFNULL, GOTO_W, NOP, RETURN, ACONST_NULL, IFNONNULL, GOTO_W, RETURN), + new Sample(IFNONNULL, GOTO_W, NOP, RETURN, ACONST_NULL, IFNULL, GOTO_W, RETURN), + }; + } + + + @ParameterizedTest + @MethodSource("provideFwd") + void testFixFwdJumpsDirectGen(Sample sample) throws Exception { + assertFixed(sample, generateFwd(sample, true, Classfile.Option.fixShortJumps(true))); + } + + @ParameterizedTest + @MethodSource("provideBack") + void testFixBackJumpsDirectGen(Sample sample) throws Exception { + assertFixed(sample, generateBack(sample, true, Classfile.Option.fixShortJumps(true))); + } + + @ParameterizedTest + @MethodSource("provideFwd") + void testFailFwdJumpsDirectGen(Sample sample) throws Exception { + assertThrows(IllegalStateException.class, () -> generateFwd(sample, true, Classfile.Option.fixShortJumps(false))); + } + + @ParameterizedTest + @MethodSource("provideBack") + void testFailBackJumpsDirectGen(Sample sample) throws Exception { + assertThrows(IllegalStateException.class, () -> generateBack(sample, true, Classfile.Option.fixShortJumps(false))); + } + + @ParameterizedTest + @MethodSource("provideFwd") + void testFixFwdJumpsTransform(Sample sample) throws Exception { + assertFixed(sample, Classfile.parse( + generateFwd(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(true)) + .transform(overflow())); + } + + @ParameterizedTest + @MethodSource("provideBack") + void testFixBackJumpsTransform(Sample sample) throws Exception { + assertFixed(sample, Classfile.parse( + generateBack(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(true)) + .transform(overflow())); + } + + @ParameterizedTest + @MethodSource("provideFwd") + void testFailFwdJumpsTransform(Sample sample) throws Exception { + assertThrows(IllegalStateException.class, () -> + Classfile.parse( + generateFwd(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(false)) + .transform(overflow())); + } + + @ParameterizedTest + @MethodSource("provideBack") + void testFailBackJumpsTransform(Sample sample) throws Exception { + assertThrows(IllegalStateException.class, () -> + Classfile.parse( + generateBack(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(false)) + .transform(overflow())); + } + + @ParameterizedTest + @MethodSource("provideFwd") + void testFixFwdJumpsChainedTransform(Sample sample) throws Exception { + assertFixed(sample, Classfile.parse( + generateFwd(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(true)) + .transform(ClassTransform.ACCEPT_ALL.andThen(overflow()))); //involve BufferedCodeBuilder here + } + + @ParameterizedTest + @MethodSource("provideBack") + void testFixBackJumpsChainedTransform(Sample sample) throws Exception { + assertFixed(sample, Classfile.parse( + generateBack(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(true)) + .transform(ClassTransform.ACCEPT_ALL.andThen(overflow()))); //involve BufferedCodeBuilder here + } + + @ParameterizedTest + @MethodSource("provideFwd") + void testFailFwdJumpsChainedTransform(Sample sample) throws Exception { + assertThrows(IllegalStateException.class, () -> + Classfile.parse( + generateFwd(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(false)) + .transform(ClassTransform.ACCEPT_ALL.andThen(overflow()))); //involve BufferedCodeBuilder here + } + + @ParameterizedTest + @MethodSource("provideBack") + void testFailBackJumpsChainedTransform(Sample sample) throws Exception { + assertThrows(IllegalStateException.class, () -> + Classfile.parse( + generateBack(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(false)) + .transform(ClassTransform.ACCEPT_ALL.andThen(overflow()))); //involve BufferedCodeBuilder here + } + + private static byte[] generateFwd(Sample sample, boolean overflow, Classfile.Option... options) { + return Classfile.build(ClassDesc.of("WhateverClass"), List.of(options), + cb -> cb.withMethod("whateverMethod", MethodTypeDesc.of(ConstantDescs.CD_void), 0, + mb -> mb.withCode(cob -> { + for (int i = 0; i < sample.expected.length - 4; i++) //cherry-pick XCONST_ instructions from expected output + cob.with(ConstantInstruction.ofIntrinsic(sample.expected[i])); + var target = cob.newLabel(); + cob.branchInstruction(sample.jumpCode, target); + for (int i = overflow ? 40000 : 1; i > 0; i--) + cob.nopInstruction(); + cob.labelBinding(target); + cob.return_(); + }))); + } + + private static byte[] generateBack(Sample sample, boolean overflow, Classfile.Option... options) { + return Classfile.build(ClassDesc.of("WhateverClass"), List.of(options), + cb -> cb.withMethod("whateverMethod", MethodTypeDesc.of(ConstantDescs.CD_void), 0, + mb -> mb.withCode(cob -> { + var target = cob.newLabel(); + var fwd = cob.newLabel(); + cob.goto_w(fwd); + cob.labelBinding(target); + for (int i = overflow ? 40000 : 1; i > 0; i--) + cob.nopInstruction(); + cob.return_(); + cob.labelBinding(fwd); + for (int i = 3; i < sample.expected.length - 3; i++) //cherry-pick XCONST_ instructions from expected output + cob.with(ConstantInstruction.ofIntrinsic(sample.expected[i])); + cob.branchInstruction(sample.jumpCode, target); + cob.return_(); + }))); + } + + private static ClassTransform overflow() { + return ClassTransform.transformingMethods( + MethodTransform.transformingCode( + (cob, coe) -> { + if (coe instanceof NopInstruction) + for (int i = 0; i < 40000; i++) //cause label overflow during transform + cob.nopInstruction(); + cob.with(coe); + })); + } + + private static void assertFixed(Sample sample, byte[] classFile) { + var found = new LinkedList(); + for (var e : Classfile.parse(classFile).methods().get(0).code().get()) + if (e instanceof Instruction i && found.peekLast() != i.opcode()) //dedup subsequent (NOPs) + found.add(i.opcode()); + assertEquals(found, List.of(sample.expected)); + } +} diff --git a/test/jdk/jdk/classfile/SignaturesTest.java b/test/jdk/jdk/classfile/SignaturesTest.java new file mode 100644 index 0000000000000..2827ce177c79e --- /dev/null +++ b/test/jdk/jdk/classfile/SignaturesTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Signatures. + * @run junit SignaturesTest + */ +import java.lang.constant.ClassDesc; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import jdk.internal.classfile.ClassSignature; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.MethodSignature; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.Signature.*; +import jdk.internal.classfile.Attributes; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static helpers.ClassRecord.assertEqualsDeep; +import static java.lang.constant.ConstantDescs.*; + +class SignaturesTest { + + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + @Test + void testBuildingSignatures() { + assertEqualsDeep( + ClassSignature.of( + ClassTypeSig.of( + ClassTypeSig.of(ClassDesc.of("java.util.LinkedHashMap"), TypeArg.of(TypeVarSig.of("K")), TypeArg.of(TypeVarSig.of("V"))), + ClassDesc.of("LinkedHashIterator")), + ClassTypeSig.of(ClassDesc.of("java.util.Iterator"), + TypeArg.of(ClassTypeSig.of(ClassDesc.of("java.util.Map$Entry"), TypeArg.of(TypeVarSig.of("K")), TypeArg.of(TypeVarSig.of("V")))))), + ClassSignature.parseFrom("Ljava/util/LinkedHashMap.LinkedHashIterator;Ljava/util/Iterator;>;")); + + assertEqualsDeep( + ClassSignature.of( + List.of( + TypeParam.of("K", ClassTypeSig.of(CD_Object)), + TypeParam.of("V", ClassTypeSig.of(CD_Object))), + ClassTypeSig.of(ClassDesc.of("java.util.AbstractMap"), TypeArg.of(TypeVarSig.of("K")), TypeArg.of(TypeVarSig.of("V"))), + ClassTypeSig.of(ClassDesc.of("java.util.concurrent.ConcurrentMap"), TypeArg.of(TypeVarSig.of("K")), TypeArg.of(TypeVarSig.of("V"))), + ClassTypeSig.of(ClassDesc.of("java.io.Serializable"))), + ClassSignature.parseFrom("Ljava/util/AbstractMap;Ljava/util/concurrent/ConcurrentMap;Ljava/io/Serializable;")); + + assertEqualsDeep( + MethodSignature.of( + ClassTypeSig.of( + CD_Map, + TypeArg.of(ClassTypeSig.of( + CD_Class, + TypeArg.extendsOf( + ClassTypeSig.of(ClassDesc.of("java.lang.annotation.Annotation"))))), + TypeArg.of(ClassTypeSig.of(ClassDesc.of("java.lang.annotation.Annotation")))), + Signature.of(CD_byte.arrayType()), + ClassTypeSig.of(ClassDesc.of("jdk.internal.reflect.ConstantPool")), + ClassTypeSig.of(CD_Class, TypeArg.unbounded()), + ArrayTypeSig.of( + ClassTypeSig.of(CD_Class, + TypeArg.extendsOf( + ClassTypeSig.of(ClassDesc.of("java.lang.annotation.Annotation")))))), + MethodSignature.parseFrom("([BLjdk/internal/reflect/ConstantPool;Ljava/lang/Class<*>;[Ljava/lang/Class<+Ljava/lang/annotation/Annotation;>;)Ljava/util/Map;Ljava/lang/annotation/Annotation;>;")); + + assertEqualsDeep( + MethodSignature.of( + List.of( + TypeParam.of("T", Optional.empty(), ClassTypeSig.of(ClassDesc.of("java.lang.annotation.Annotation")))), + List.of( + ClassTypeSig.of(ClassDesc.of("java.lang.IOException")), + ClassTypeSig.of(ClassDesc.of("java.lang.IllegalAccessError"))), + ArrayTypeSig.of(TypeVarSig.of("T")), + ClassTypeSig.of(CD_Class, TypeArg.of(TypeVarSig.of("T")))), + MethodSignature.parseFrom("(Ljava/lang/Class;)[TT;^Ljava/lang/IOException;^Ljava/lang/IllegalAccessError;")); + + assertEqualsDeep( + ClassTypeSig.of( + CD_Set, + TypeArg.extendsOf( + ClassTypeSig.of(ClassDesc.of("java.nio.file.WatchEvent$Kind"), TypeArg.unbounded()))), + Signature.parseFrom("Ljava/util/Set<+Ljava/nio/file/WatchEvent$Kind<*>;>;")); + + assertEqualsDeep( + ArrayTypeSig.of(2, TypeVarSig.of("E")), + Signature.parseFrom("[[TE;")); + } + + @Test + void testParseAndPrintSignatures() throws Exception { + var csc = new AtomicInteger(); + var msc = new AtomicInteger(); + var fsc = new AtomicInteger(); + var rsc = new AtomicInteger(); + Stream.of( + Files.walk(JRT.getPath("modules/java.base")), + Files.walk(JRT.getPath("modules"), 2).filter(p -> p.endsWith("module-info.class")), + Files.walk(Path.of(SignaturesTest.class.getProtectionDomain().getCodeSource().getLocation().toURI()))) + .flatMap(p -> p) + .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")).forEach(path -> { + try { + var cm = Classfile.parse(path); + cm.findAttribute(Attributes.SIGNATURE).ifPresent(csig -> { + assertEquals( + ClassSignature.parseFrom(csig.signature().stringValue()).signatureString(), + csig.signature().stringValue(), + cm.thisClass().asInternalName()); + csc.incrementAndGet(); + }); + for (var m : cm.methods()) { + m.findAttribute(Attributes.SIGNATURE).ifPresent(msig -> { + assertEquals( + MethodSignature.parseFrom(msig.signature().stringValue()).signatureString(), + msig.signature().stringValue(), + cm.thisClass().asInternalName() + "::" + m.methodName().stringValue() + m.methodType().stringValue()); + msc.incrementAndGet(); + }); + } + for (var f : cm.fields()) { + f.findAttribute(Attributes.SIGNATURE).ifPresent(fsig -> { + assertEquals( + Signature.parseFrom(fsig.signature().stringValue()).signatureString(), + fsig.signature().stringValue(), + cm.thisClass().asInternalName() + "." + f.fieldName().stringValue()); + fsc.incrementAndGet(); + }); + } + cm.findAttribute(Attributes.RECORD).ifPresent(reca + -> reca.components().forEach(rc -> rc.findAttribute(Attributes.SIGNATURE).ifPresent(rsig -> { + assertEquals( + Signature.parseFrom(rsig.signature().stringValue()).signatureString(), + rsig.signature().stringValue(), + cm.thisClass().asInternalName() + "." + rc.name().stringValue()); + rsc.incrementAndGet(); + }))); + } catch (Exception e) { + throw new AssertionError(path.toString(), e); + } + }); + System.out.println("SignaturesTest - tested signatures of " + csc + " classes, " + msc + " methods, " + fsc + " fields and " + rsc + " record components"); + } +} diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java new file mode 100644 index 0000000000000..69e8eb0e62c9b --- /dev/null +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile stack maps generator. + * @build testdata.* + * @run junit StackMapsTest + */ + +import jdk.internal.classfile.Classfile; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static helpers.TestUtil.assertEmpty; +import static jdk.internal.classfile.Classfile.ACC_STATIC; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import java.lang.reflect.AccessFlag; + +/** + * StackMapsTest + */ +class StackMapsTest { + + private byte[] buildDeadCode() { + return Classfile.build( + ClassDesc.of("DeadCodePattern"), + List.of(Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + clb -> clb.withMethodBody( + "twoReturns", + MethodTypeDesc.of(ConstantDescs.CD_void), + 0, + cob -> cob.return_().return_()) + .withMethodBody( + "deadJumpInExceptionBlocks", + MethodTypeDesc.of(ConstantDescs.CD_void), + 0, + cob -> { + var deadEnd = cob.newLabel(); + cob.goto_(deadEnd); + var deadStart = cob.newBoundLabel(); + cob.return_(); //dead code + cob.labelBinding(deadEnd); + cob.return_(); + var handler = cob.newBoundLabel(); + cob.athrow(); + //exception block before dead code to stay untouched + cob.exceptionCatch(cob.startLabel(), deadStart, handler, ConstantDescs.CD_Throwable); + //exception block after dead code to stay untouched + cob.exceptionCatch(deadEnd, handler, handler, ConstantDescs.CD_Throwable); + //exception block overlapping dead code to cut from right + cob.exceptionCatch(cob.startLabel(), deadEnd, handler, ConstantDescs.CD_Throwable); + //exception block overlapping dead code to from left + cob.exceptionCatch(deadStart, handler, handler, ConstantDescs.CD_Throwable); + //exception block matching dead code to remove + cob.exceptionCatch(deadStart, deadEnd, handler, ConstantDescs.CD_Throwable); + //exception block around dead code to split + cob.exceptionCatch(cob.startLabel(), handler, handler, ConstantDescs.CD_Throwable); + })); + } + + @Test + void testDeadCodePatternPatch() throws Exception { + testTransformedStackMaps(buildDeadCode()); + } + + @Test + void testDeadCodePatternFail() throws Exception { + var error = assertThrows(IllegalStateException.class, () -> testTransformedStackMaps(buildDeadCode(), Classfile.Option.patchDeadCode(false))); + assertLinesMatch( + """ + Unable to generate stack map frame for dead code at bytecode offset 1 of method twoReturns() + >> more lines >> + 0: {opcode: RETURN} + 1: {opcode: RETURN} + """.lines(), + error.getMessage().lines(), + error.getMessage() + ); + } + + @Test + void testUnresolvedPermission() throws Exception { + testTransformedStackMaps("modules/java.base/java/security/UnresolvedPermission.class"); + } + + @Test + void testURL() throws Exception { + testTransformedStackMaps("modules/java.base/java/net/URL.class"); + } + + @Test + void testPattern1() throws Exception { + testTransformedStackMaps("/testdata/Pattern1.class"); + } + + @Test + void testPattern2() throws Exception { + testTransformedStackMaps("/testdata/Pattern2.class"); + } + + @Test + void testPattern3() throws Exception { + testTransformedStackMaps("/testdata/Pattern3.class"); + } + + @Test + void testPattern4() throws Exception { + testTransformedStackMaps("/testdata/Pattern4.class"); + } + + @Test + void testPattern5() throws Exception { + testTransformedStackMaps("/testdata/Pattern5.class"); + } + + @Test + void testPattern6() throws Exception { + testTransformedStackMaps("/testdata/Pattern6.class"); + } + + @Test + void testPattern7() throws Exception { + testTransformedStackMaps("/testdata/Pattern7.class"); + } + + @Test + void testPattern8() throws Exception { + testTransformedStackMaps("/testdata/Pattern8.class"); + } + + @Test + void testPattern9() throws Exception { + testTransformedStackMaps("/testdata/Pattern9.class"); + } + + @Test + void testPattern10() throws Exception { + testTransformedStackMaps("/testdata/Pattern10.class"); + } + + @Test + void testFrameOutOfBytecodeRange() { + var error = assertThrows(IllegalStateException.class, () -> + Classfile.parse( + Classfile.build(ClassDesc.of("TestClass"), clb -> + clb.withMethodBody("frameOutOfRangeMethod", MethodTypeDesc.of(ConstantDescs.CD_void), 0, cob -> { + var l = cob.newLabel(); + cob.goto_(l);//jump to the end of method body triggers invalid frame creation + cob.labelBinding(l); + })))); + assertLinesMatch( + """ + Detected branch target out of bytecode range at bytecode offset 0 of method frameOutOfRangeMethod() + >> more lines >> + 0: {opcode: GOTO, target: 3} + """.lines(), + error.getMessage().lines(), + error.getMessage() + ); + } + + @Test + void testMethodSwitchFromStatic() { + assertThrows(IllegalArgumentException.class, () -> + Classfile.build(ClassDesc.of("TestClass"), clb -> + clb.withMethod("testMethod", MethodTypeDesc.of(ConstantDescs.CD_Object, ConstantDescs.CD_int), + ACC_STATIC, + mb -> mb.withCode(cob -> { + var t = cob.newLabel(); + cob.aload(0).goto_(t).labelBinding(t).areturn(); + }) + .withFlags()))); + } + + @Test + void testMethodSwitchToStatic() { + assertThrows(IllegalArgumentException.class, () -> + Classfile.build(ClassDesc.of("TestClass"), clb -> + clb.withMethod("testMethod", MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int), + 0, mb -> + mb.withCode(cob -> { + var t = cob.newLabel(); + cob.iload(0).goto_(t).labelBinding(t).ireturn(); + }) + .withFlags(AccessFlag.STATIC)))); + } + + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + private static void testTransformedStackMaps(String classPath, Classfile.Option... options) throws Exception { + testTransformedStackMaps( + classPath.startsWith("/") + ? StackMapsTest.class.getResourceAsStream(classPath).readAllBytes() + : Files.readAllBytes(JRT.getPath(classPath)), + options); + } + + private static void testTransformedStackMaps(byte[] originalBytes, Classfile.Option... options) throws Exception { + //transform the class model + var classModel = Classfile.parse(originalBytes, options); + var transformedBytes = Classfile.build(classModel.thisClass().asSymbol(), List.of(options), + cb -> { +// classModel.superclass().ifPresent(cb::withSuperclass); +// cb.withInterfaces(classModel.interfaces()); +// cb.withVersion(classModel.majorVersion(), classModel.minorVersion()); + classModel.forEachElement(cb); + }); + + //then verify transformed bytecode + assertEmpty(Classfile.parse(transformedBytes).verify(null)); + } +} diff --git a/test/jdk/jdk/classfile/StackTrackerTest.java b/test/jdk/jdk/classfile/StackTrackerTest.java new file mode 100644 index 0000000000000..bfa2b033ff560 --- /dev/null +++ b/test/jdk/jdk/classfile/StackTrackerTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing CodeStackTracker in CodeBuilder. + * @run junit StackTrackerTest + */ +import java.util.List; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.constant.ConstantDescs; +import jdk.internal.classfile.*; +import jdk.internal.classfile.components.CodeStackTracker; +import static jdk.internal.classfile.TypeKind.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * StackTrackerTest + */ +class StackTrackerTest { + + @Test + void testStackTracker() { + Classfile.build(ClassDesc.of("Foo"), clb -> + clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_Void), 0, cob -> { + var stackTracker = CodeStackTracker.of(DoubleType, FloatType); //initial stack tracker pre-set + cob.transforming(stackTracker, stcb -> { + assertIterableEquals(stackTracker.stack().get(), List.of(DoubleType, FloatType)); + stcb.aload(0); + assertIterableEquals(stackTracker.stack().get(), List.of(ReferenceType, DoubleType, FloatType)); + stcb.lconst_0(); + assertIterableEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); + stcb.trying(tryb -> { + assertIterableEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); + tryb.iconst_1(); + assertIterableEquals(stackTracker.stack().get(), List.of(IntType, LongType, ReferenceType, DoubleType, FloatType)); + tryb.ifThen(thb -> { + assertIterableEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); + thb.constantInstruction(ClassDesc.of("Phee")); + assertIterableEquals(stackTracker.stack().get(), List.of(ReferenceType, LongType, ReferenceType, DoubleType, FloatType)); + thb.athrow(); + assertFalse(stackTracker.stack().isPresent()); + }); + assertIterableEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); + tryb.return_(); + assertFalse(stackTracker.stack().isPresent()); + }, catchb -> catchb.catching(ClassDesc.of("Phee"), cb -> { + assertIterableEquals(stackTracker.stack().get(), List.of(ReferenceType)); + cb.athrow(); + assertFalse(stackTracker.stack().isPresent()); + })); + }); + assertTrue(stackTracker.maxStackSize().isPresent()); + assertEquals((int)stackTracker.maxStackSize().get(), 7); + })); + } + + @Test + void testTrackingLost() { + Classfile.build(ClassDesc.of("Foo"), clb -> + clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_Void), 0, cob -> { + var stackTracker = CodeStackTracker.of(); + cob.transforming(stackTracker, stcb -> { + assertIterableEquals(stackTracker.stack().get(), List.of()); + var l1 = stcb.newLabel(); + stcb.goto_(l1); //forward jump + assertFalse(stackTracker.stack().isPresent()); //no stack + assertTrue(stackTracker.maxStackSize().isPresent()); //however still tracking + var l2 = stcb.newBoundLabel(); //back jump target + assertFalse(stackTracker.stack().isPresent()); //no stack + assertTrue(stackTracker.maxStackSize().isPresent()); //however still tracking + stcb.constantInstruction(ClassDesc.of("Phee")); //stack instruction on unknown stack cause tracking lost + assertFalse(stackTracker.stack().isPresent()); //no stack + assertFalse(stackTracker.maxStackSize().isPresent()); //because tracking lost + stcb.athrow(); + stcb.labelBinding(l1); //forward jump target + assertTrue(stackTracker.stack().isPresent()); //stack known here + assertFalse(stackTracker.maxStackSize().isPresent()); //no max stack size because tracking lost in back jump + stcb.goto_(l2); //back jump + assertFalse(stackTracker.stack().isPresent()); //no stack + assertFalse(stackTracker.maxStackSize().isPresent()); //still no max stack size because tracking previously lost + }); + })); + } +} diff --git a/test/jdk/jdk/classfile/StreamedVsListTest.java b/test/jdk/jdk/classfile/StreamedVsListTest.java new file mode 100644 index 0000000000000..8b01481037974 --- /dev/null +++ b/test/jdk/jdk/classfile/StreamedVsListTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile streaming versus model. + * @run junit StreamedVsListTest + */ +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.Instruction; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.impl.DirectCodeBuilder; +import jdk.internal.classfile.instruction.BranchInstruction; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.FieldInstruction; +import jdk.internal.classfile.instruction.IncrementInstruction; +import jdk.internal.classfile.instruction.InvokeDynamicInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.classfile.instruction.LookupSwitchInstruction; +import jdk.internal.classfile.instruction.NewMultiArrayInstruction; +import jdk.internal.classfile.instruction.NewObjectInstruction; +import jdk.internal.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.internal.classfile.instruction.NewReferenceArrayInstruction; +import jdk.internal.classfile.instruction.StoreInstruction; +import jdk.internal.classfile.instruction.TableSwitchInstruction; +import jdk.internal.classfile.instruction.TypeCheckInstruction; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +class StreamedVsListTest { + static byte[] fileBytes; + + static { + try { + fileBytes = DirectCodeBuilder.class.getResourceAsStream("DirectCodeBuilder.class").readAllBytes(); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + + @Test + void testStreamed() throws Exception { + Vs vs = new Vs(); + vs.test(); + if (vs.failed) { + throw new AssertionError("assertions failed"); + } + } + + private class Vs { + boolean failed; + ClassModel cm = Classfile.parse(fileBytes); + String meth; + CodeElement iim; + CodeElement mim; + int n; + + void test() { + for (MethodModel mm : cm.methods()) { + try { + mm.code().ifPresent(code -> { + meth = mm.methodName().stringValue(); + List insts = code.elementList(); + n = 0; + for (CodeElement element : code) { + iim = element; + mim = insts.get(n++); + if (iim instanceof Instruction) + testInstruction(); + } + }); + } catch (Throwable ex) { + failed = true; + System.err.printf("%s.%s #%d[%s]: ", cm.thisClass().asInternalName(), meth, n - 1, iim instanceof Instruction i ? i.opcode() : "<->"); + System.err.printf("Threw: %s" + "%n", ex); + throw ex; + } + } + } + + void testInstruction() { + assertEquals(((Instruction)iim).opcode(), ((Instruction)mim).opcode(), "Opcodes don't match"); + switch (((Instruction)iim).opcode().kind()) { + case LOAD: { + LoadInstruction i = (LoadInstruction) iim; + LoadInstruction x = (LoadInstruction) mim; + assertEquals(i.slot(), x.slot(), "variable"); + break; + } + case STORE: { + StoreInstruction i = (StoreInstruction) iim; + StoreInstruction x = (StoreInstruction) mim; + assertEquals(i.slot(), x.slot(), "variable"); + break; + } + case INCREMENT: { + IncrementInstruction i = (IncrementInstruction) iim; + IncrementInstruction x = (IncrementInstruction) mim; + assertEquals(i.slot(), x.slot(), "variable"); + assertEquals(i.constant(), x.constant(), "constant"); + break; + } + case BRANCH: { + BranchInstruction i = (BranchInstruction) iim; + BranchInstruction x = (BranchInstruction) mim; + //TODO: test labels + break; + } + case TABLE_SWITCH: { + TableSwitchInstruction i = (TableSwitchInstruction) iim; + TableSwitchInstruction x = (TableSwitchInstruction) mim; + assertEquals(i.lowValue(), x.lowValue(), "lowValue"); + assertEquals(i.highValue(), x.highValue(), "highValue"); + assertEquals(i.cases().size(), x.cases().size(), "cases().size"); + //TODO: test labels + break; + } + case LOOKUP_SWITCH: { + LookupSwitchInstruction i = (LookupSwitchInstruction) iim; + LookupSwitchInstruction x = (LookupSwitchInstruction) mim; + assertEquals(i.cases(), (Object) x.cases(), "matches: "); + /** + var ipairs = i.pairs(); + var xpairs = x.pairs(); + assertEquals("pairs().size", ipairs.size(), xpairs.size()); + for (int k = 0; k < xpairs.size(); ++k) { + assertEquals("pair #" + k, ipairs.get(k).caseMatch(), xpairs.get(k).caseMatch()); + } + **/ + //TODO: test labels + break; + } + case RETURN: + case THROW_EXCEPTION: + break; + case FIELD_ACCESS: { + FieldInstruction i = (FieldInstruction) iim; + FieldInstruction x = (FieldInstruction) mim; + assertEquals(i.owner().asInternalName(), (Object) x.owner().asInternalName(), "owner"); + assertEquals(i.name().stringValue(), (Object) x.name().stringValue(), "name"); + assertEquals(i.type().stringValue(), (Object) x.type().stringValue(), "type"); + break; + } + case INVOKE: { + InvokeInstruction i = (InvokeInstruction) iim; + InvokeInstruction x = (InvokeInstruction) mim; + assertEquals(i.owner().asInternalName(), (Object) x.owner().asInternalName(), "owner"); + assertEquals(i.name().stringValue(), (Object) x.name().stringValue(), "name"); + assertEquals(i.type().stringValue(), (Object) x.type().stringValue(), "type"); + assertEquals(i.isInterface(), (Object) x.isInterface(), "isInterface"); + assertEquals(i.count(), x.count(), "count"); + break; + } + case INVOKE_DYNAMIC: { + InvokeDynamicInstruction i = (InvokeDynamicInstruction) iim; + InvokeDynamicInstruction x = (InvokeDynamicInstruction) mim; + assertEquals(i.bootstrapMethod(), x.bootstrapMethod(), "bootstrapMethod"); + assertEquals(i.bootstrapArgs(), (Object) x.bootstrapArgs(), "bootstrapArgs"); + assertEquals(i.name().stringValue(), (Object) x.name().stringValue(), "name"); + assertEquals(i.type().stringValue(), (Object) x.type().stringValue(), "type"); + break; + } + case NEW_OBJECT: { + NewObjectInstruction i = (NewObjectInstruction) iim; + NewObjectInstruction x = (NewObjectInstruction) mim; + assertEquals(i.className().asInternalName(), (Object) x.className().asInternalName(), "type"); + break; + } + case NEW_PRIMITIVE_ARRAY: + { + NewPrimitiveArrayInstruction i = (NewPrimitiveArrayInstruction) iim; + NewPrimitiveArrayInstruction x = (NewPrimitiveArrayInstruction) mim; + assertEquals(i.typeKind(), x.typeKind(), "type"); + break; + } + + case NEW_REF_ARRAY:{ + NewReferenceArrayInstruction i = (NewReferenceArrayInstruction) iim; + NewReferenceArrayInstruction x = (NewReferenceArrayInstruction) mim; + assertEquals(i.componentType().asInternalName(), (Object) x.componentType().asInternalName(), "type"); + break; + } + + case NEW_MULTI_ARRAY:{ + NewMultiArrayInstruction i = (NewMultiArrayInstruction) iim; + NewMultiArrayInstruction x = (NewMultiArrayInstruction) mim; + assertEquals(i.arrayType().asInternalName(), (Object) x.arrayType().asInternalName(), "type"); + assertEquals(i.dimensions(), x.dimensions(), "dimensions"); + break; + } + + case TYPE_CHECK: { + TypeCheckInstruction i = (TypeCheckInstruction) iim; + TypeCheckInstruction x = (TypeCheckInstruction) mim; + assertEquals(i.type().asInternalName(), (Object) x.type().asInternalName(), "type"); + break; + } + case ARRAY_LOAD: + case ARRAY_STORE: + case STACK: + case CONVERT: + case OPERATOR: + break; + case CONSTANT: { + ConstantInstruction i = (ConstantInstruction) iim; + ConstantInstruction x = (ConstantInstruction) mim; + assertEquals(i.constantValue(), x.constantValue(), "constantValue"); + } + break; + case MONITOR: + case NOP: + break; + + } + } + } +} diff --git a/test/jdk/jdk/classfile/SwapTest.java b/test/jdk/jdk/classfile/SwapTest.java new file mode 100644 index 0000000000000..ebf2335d0234c --- /dev/null +++ b/test/jdk/jdk/classfile/SwapTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 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. + */ + +/* + * @test + * @summary Testing swap instruction + * @run junit SwapTest + */ + +import jdk.internal.classfile.AccessFlags; +import jdk.internal.classfile.Classfile; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import static java.lang.reflect.AccessFlag.PUBLIC; +import static java.lang.reflect.AccessFlag.STATIC; + +class SwapTest { + @Test + void testTryCatchCatchAll() throws Throwable { + MethodType mt = MethodType.methodType(String.class, String.class, String.class); + MethodTypeDesc mtd = mt.describeConstable().get(); + + byte[] bytes = Classfile.build(ClassDesc.of("C"), cb -> { + cb.withMethodBody("m", mtd, AccessFlags.ofMethod(PUBLIC, STATIC).flagsMask(), xb -> { + xb.aload(0); // 0 + xb.aload(1); // 1, 0 + xb.swap(); // 0, 1 + xb.pop(); // 1 + xb.areturn(); + }); + }); + + MethodHandles.Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + MethodHandle m = lookup.findStatic(lookup.lookupClass(), "m", mt); + assertEquals(m.invoke("A", "B"), "B"); + } +} diff --git a/test/jdk/jdk/classfile/TEST.properties b/test/jdk/jdk/classfile/TEST.properties new file mode 100644 index 0000000000000..9215ccc3ce4b7 --- /dev/null +++ b/test/jdk/jdk/classfile/TEST.properties @@ -0,0 +1,14 @@ +maxOutputSize = 500000 +enablePreview = true +modules = \ + java.base/jdk.internal.classfile \ + java.base/jdk.internal.classfile.attribute \ + java.base/jdk.internal.classfile.constantpool \ + java.base/jdk.internal.classfile.instruction \ + java.base/jdk.internal.classfile.impl \ + java.base/jdk.internal.classfile.impl.verifier \ + java.base/jdk.internal.classfile.java.lang.constant \ + java.base/jdk.internal.classfile.components \ + java.base/jdk.internal.classfile.util \ + java.base/jdk.internal.org.objectweb.asm \ + java.base/jdk.internal.org.objectweb.asm.tree \ No newline at end of file diff --git a/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java b/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java new file mode 100644 index 0000000000000..a1881932cbf3d --- /dev/null +++ b/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile TempConstantPoolBuilder. + * @run junit TempConstantPoolBuilderTest + */ +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import java.lang.reflect.AccessFlag; +import org.junit.jupiter.api.Test; + +import java.lang.constant.ClassDesc; + +import static helpers.TestConstants.MTD_VOID; +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.constant.ConstantDescs.CD_void; +import java.lang.constant.MethodTypeDesc; +import static jdk.internal.classfile.Opcode.INVOKESPECIAL; +import static jdk.internal.classfile.TypeKind.VoidType; + +class TempConstantPoolBuilderTest { + + public static final ClassDesc INTERFACE = ClassDesc.ofDescriptor("Ljava/lang/FunctionalInterface;"); + + @Test + void createAnno() { + Annotation a = Annotation.of(INTERFACE, + AnnotationElement.ofString("foo", "bar")); + } + + @Test + void addAnno() { + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC) + .with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + .with(RuntimeVisibleAnnotationsAttribute.of(Annotation.of(INTERFACE, + AnnotationElement.ofString("foo", "bar")))) + ); + }); + ClassModel m = Classfile.parse(bytes); + //ClassPrinter.toJson(m, ClassPrinter.Verbosity.TRACE_ALL, System.out::println); + } +} diff --git a/test/jdk/jdk/classfile/TestRecordComponent.java b/test/jdk/jdk/classfile/TestRecordComponent.java new file mode 100644 index 0000000000000..4aeb4f65b4c40 --- /dev/null +++ b/test/jdk/jdk/classfile/TestRecordComponent.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile RecordComponent. + * @run junit TestRecordComponent + */ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import helpers.ClassRecord; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.attribute.RecordAttribute; +import jdk.internal.classfile.attribute.RecordComponentInfo; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +class TestRecordComponent { + + static final String testClassName = "TestRecordComponent$TestRecord"; + static final Path testClassPath = Paths.get(URI.create(ArrayTest.class.getResource(testClassName + ".class").toString())); + + @Test + void testAdapt() throws Exception { + ClassModel cm = Classfile.parse(Files.readAllBytes(testClassPath)); + ClassTransform xform = (cb, ce) -> { + if (ce instanceof RecordAttribute rm) { + List components = rm.components(); + components = components.stream() + .map(c -> RecordComponentInfo.of(c.name(), c.descriptor(), c.attributes())) + .toList(); + cb.with(RecordAttribute.of(components)); + } else + cb.with(ce); + }; + ClassModel newModel = Classfile.parse(cm.transform(xform)); + ClassRecord.assertEquals(newModel, cm); + } + + @Test + void testPassThrough() throws Exception { + ClassModel cm = Classfile.parse(Files.readAllBytes(testClassPath)); + ClassTransform xform = (cb, ce) -> cb.with(ce); + ClassModel newModel = Classfile.parse(cm.transform(xform)); + ClassRecord.assertEquals(newModel, cm); + } + + @Test + void testChagne() throws Exception { + ClassModel cm = Classfile.parse(Files.readAllBytes(testClassPath)); + ClassTransform xform = (cb, ce) -> { + if (ce instanceof RecordAttribute ra) { + List components = ra.components(); + components = components.stream().map(c -> RecordComponentInfo.of(TemporaryConstantPool.INSTANCE.utf8Entry(c.name().stringValue() + "XYZ"), c.descriptor(), List.of())) + .toList(); + cb.with(RecordAttribute.of(components)); + } + else + cb.with(ce); + }; + ClassModel newModel = Classfile.parse(cm.transform(xform)); + RecordAttribute ra = newModel.findAttribute(Attributes.RECORD).orElseThrow(); + assertEquals(ra.components().size(), 2, "Should have two components"); + assertEquals(ra.components().get(0).name().stringValue(), "fooXYZ"); + assertEquals(ra.components().get(1).name().stringValue(), "barXYZ"); + assertTrue(ra.components().get(0).attributes().isEmpty()); + assertEquals(newModel.attributes().size(), cm.attributes().size()); + } + + @Test + void testOptions() throws Exception { + AtomicInteger count = new AtomicInteger(0); + ClassModel cm = Classfile.parse(Files.readAllBytes(testClassPath)); + cm.forEachElement((ce) -> { + if (ce instanceof RecordAttribute rm) { + count.addAndGet(rm.components().size()); + }}); + assertEquals(count.get(), 2); + assertEquals(cm.findAttribute(Attributes.RECORD).orElseThrow().components().size(), 2); + + count.set(0); + } + + public static record TestRecord(@RC String foo, int bar) {} + + @Target(ElementType.RECORD_COMPONENT) + public @interface RC {} +} diff --git a/test/jdk/jdk/classfile/TransformTests.java b/test/jdk/jdk/classfile/TransformTests.java new file mode 100644 index 0000000000000..f4dce1425988f --- /dev/null +++ b/test/jdk/jdk/classfile/TransformTests.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile transformations. + * @run junit TransformTests + */ +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.instruction.ConstantInstruction; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * TransformTests + */ +class TransformTests { + static final String testClassName = "TransformTests$TestClass"; + static final Path testClassPath = Paths.get(URI.create(ArrayTest.class.getResource(testClassName + ".class").toString())); + static CodeTransform + foo2foo = swapLdc("foo", "foo"), + foo2bar = swapLdc("foo", "bar"), + bar2baz = swapLdc("bar", "baz"), + baz2quux = swapLdc("baz", "quux"), + baz2foo = swapLdc("baz", "foo"); + + static CodeTransform swapLdc(String x, String y) { + return (b, e) -> { + if (e instanceof ConstantInstruction ci && ci.constantValue().equals(x)) { + b.constantInstruction(y); + } + else + b.with(e); + }; + } + + static ClassTransform transformCode(CodeTransform x) { + return (cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, x); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }; + } + + static String invoke(byte[] bytes) throws Exception { + return (String) + new ByteArrayClassLoader(AdaptCodeTest.class.getClassLoader(), testClassName, bytes) + .getMethod(testClassName, "foo") + .invoke(null); + } + + @Test + void testSingleTransform() throws Exception { + + byte[] bytes = Files.readAllBytes(testClassPath); + ClassModel cm = Classfile.parse(bytes); + + assertEquals(invoke(bytes), "foo"); + assertEquals(invoke(cm.transform(transformCode(foo2foo))), "foo"); + assertEquals(invoke(cm.transform(transformCode(foo2bar))), "bar"); + } + + @Test + void testSeq2() throws Exception { + + byte[] bytes = Files.readAllBytes(testClassPath); + ClassModel cm = Classfile.parse(bytes); + + assertEquals(invoke(bytes), "foo"); + ClassTransform transform = transformCode(foo2bar.andThen(bar2baz)); + assertEquals(invoke(cm.transform(transform)), "baz"); + } + + @Test + void testSeqN() throws Exception { + + byte[] bytes = Files.readAllBytes(testClassPath); + ClassModel cm = Classfile.parse(bytes); + + assertEquals(invoke(bytes), "foo"); + assertEquals(invoke(cm.transform(transformCode(foo2bar.andThen(bar2baz).andThen(baz2foo)))), "foo"); + assertEquals(invoke(cm.transform(transformCode(foo2bar.andThen(bar2baz).andThen(baz2quux)))), "quux"); + assertEquals(invoke(cm.transform(transformCode(foo2foo.andThen(foo2bar).andThen(bar2baz)))), "baz"); + } + + public static class TestClass { + static public String foo() { + return "foo"; + } + } +} diff --git a/test/jdk/jdk/classfile/Utf8EntryTest.java b/test/jdk/jdk/classfile/Utf8EntryTest.java new file mode 100644 index 0000000000000..c13d27d39b278 --- /dev/null +++ b/test/jdk/jdk/classfile/Utf8EntryTest.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile CP Utf8Entry. + * @run junit Utf8EntryTest + */ +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.constantpool.ConstantPool; +import jdk.internal.classfile.constantpool.PoolEntry; +import jdk.internal.classfile.constantpool.StringEntry; +import jdk.internal.classfile.constantpool.Utf8Entry; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import java.util.function.UnaryOperator; + +import static java.lang.constant.ConstantDescs.CD_void; +import static jdk.internal.classfile.TypeKind.VoidType; + +class Utf8EntryTest { + + @ParameterizedTest + @ValueSource( + strings = { + "ascii", + + "prefix\u0080\u0080\u0080postfix", + "prefix\u0080\u0080\u0080", + "\u0080\u0080\u0080postfix", + "\u0080\u0080\u0080", + + "prefix\u07FF\u07FF\u07FFpostfix", + "prefix\u07FF\u07FF\u07FF", + "\u07FF\u07FF\u07FFpostfix", + "\u07FF\u07FF\u07FF", + + "prefix\u0800\u0800\u0800postfix", + "prefix\u0800\u0800\u0800", + "\u0800\u0800\u0800postfix", + "\u0800\u0800\u0800", + + "prefix\uFFFF\uFFFF\uFFFFpostfix", + "prefix\uFFFF\uFFFF\uFFFF", + "\uFFFF\uFFFF\uFFFFpostfix", + "\uFFFF\uFFFF\uFFFF", + "\ud83d\ude01" + } + ) + void testParse(String s) { + byte[] classfile = createClassfile(s); + + ClassModel cm = Classfile.parse(classfile); + StringEntry se = obtainStringEntry(cm.constantPool()); + + Utf8Entry utf8Entry = se.utf8(); + // Inflate to byte[] or char[] + assertTrue(utf8Entry.equalsString(s)); + + // Create string + assertEquals(utf8Entry.stringValue(), s); + } + + static Stream> malformedStringsProvider() { + List> l = new ArrayList<>(); + + l.add(withByte(0b1010_0000)); + l.add(withByte(0b1000_0000)); + + l.add(withByte(0b1101_0000)); + l.add(withByte(0b1100_0000)); + l.add(withByte(0b1001_0000)); + l.add(withByte(0b1000_0000)); + + l.add(withString("#X", s -> { + byte[] c = new String("\u0080").getBytes(StandardCharsets.UTF_8); + + s[0] = c[0]; + s[1] = (byte) ((c[1] & 0xFF) & 0b0111_1111); + + return s; + })); + l.add(withString("#X#", s -> { + byte[] c = new String("\u0800").getBytes(StandardCharsets.UTF_8); + + s[0] = c[0]; + s[1] = (byte) ((c[1] & 0xFF) & 0b0111_1111); + s[2] = c[2]; + + return s; + })); + l.add(withString("##X", s -> { + byte[] c = new String("\u0800").getBytes(StandardCharsets.UTF_8); + + s[0] = c[0]; + s[1] = c[1]; + s[2] = (byte) ((c[2] & 0xFF) & 0b0111_1111); + + return s; + })); + + return l.stream(); + } + + static UnaryOperator withByte(int b) { + return withString(Integer.toBinaryString(b), s -> { + s[0] = (byte) b; + return s; + }); + } + + static UnaryOperator withString(String name, UnaryOperator u) { + return new UnaryOperator() { + @Override + public byte[] apply(byte[] bytes) { + return u.apply(bytes); + } + + @Override + public String toString() { + return name; + } + }; + } + + @ParameterizedTest + @MethodSource("malformedStringsProvider") + void testMalformedInput(UnaryOperator f) { + String marker = "XXXXXXXX"; + byte[] classfile = createClassfile(marker); + replace(classfile, marker, f); + + ClassModel cm = Classfile.parse(classfile); + StringEntry se = obtainStringEntry(cm.constantPool()); + + assertThrows(RuntimeException.class, () -> { + String s = se.utf8().stringValue(); + }); + } + + static void replace(byte[] b, String s, UnaryOperator f) { + replace(b, s.getBytes(StandardCharsets.UTF_8), f); + } + + static void replace(byte[] b, byte[] s, UnaryOperator f) { + for (int i = 0; i < b.length - s.length; i++) { + if (Arrays.equals(b, i, i + s.length, s, 0, s.length)) { + s = f.apply(s); + System.arraycopy(s, 0, b, i, s.length); + return; + } + } + throw new AssertionError(); + } + + static StringEntry obtainStringEntry(ConstantPool cp) { + for (int i = 1; i < cp.entryCount(); i++) { + PoolEntry entry = cp.entryByIndex(i); + if (entry instanceof StringEntry se) { + return se; + } + } + throw new AssertionError(); + } + + static byte[] createClassfile(String s) { + return Classfile.build(ClassDesc.of("C"), + clb -> clb.withMethod("m", MethodTypeDesc.of(CD_void), 0, + mb -> mb.withCode(cb -> cb.constantInstruction(s) + .returnInstruction(VoidType)))); + } +} diff --git a/test/jdk/jdk/classfile/UtilTest.java b/test/jdk/jdk/classfile/UtilTest.java new file mode 100644 index 0000000000000..421aa5cf5130f --- /dev/null +++ b/test/jdk/jdk/classfile/UtilTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile Util. + * @run junit UtilTest + */ +import jdk.internal.classfile.impl.Util; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * UtilTest + */ +class UtilTest { + @Test + void testFindParams() { + assertEquals(Util.findParams("(IIII)V").cardinality(), 4); + assertEquals(Util.findParams("([I[I[I[I)V").cardinality(), 4); + assertEquals(Util.findParams("(IJLFoo;IJ)V").cardinality(), 5); + assertEquals(Util.findParams("([[[[I)V").cardinality(), 1); + assertEquals(Util.findParams("([[[[LFoo;)V").cardinality(), 1); + assertEquals(Util.findParams("([I[LFoo;)V").cardinality(), 2); + assertEquals(Util.findParams("()V").cardinality(), 0); + } + + @Test + void testParameterSlots() { + assertEquals(Util.parameterSlots("(IIII)V"), 4); + assertEquals(Util.parameterSlots("([I[I[I[I)V"), 4); + assertEquals(Util.parameterSlots("(IJLFoo;IJ)V"), 7); + assertEquals(Util.parameterSlots("([[[[I)V"), 1); + assertEquals(Util.parameterSlots("([[[[LFoo;)V"), 1); + assertEquals(Util.parameterSlots("([I[LFoo;)V"), 2); + assertEquals(Util.parameterSlots("()V"), 0); + assertEquals(Util.parameterSlots("(I)V"), 1); + assertEquals(Util.parameterSlots("(S)V"), 1); + assertEquals(Util.parameterSlots("(C)V"), 1); + assertEquals(Util.parameterSlots("(B)V"), 1); + assertEquals(Util.parameterSlots("(Z)V"), 1); + assertEquals(Util.parameterSlots("(F)V"), 1); + assertEquals(Util.parameterSlots("(LFoo;)V"), 1); + assertEquals(Util.parameterSlots("(J)V"), 2); + assertEquals(Util.parameterSlots("(D)V"), 2); + assertEquals(Util.parameterSlots("([J)V"), 1); + assertEquals(Util.parameterSlots("([D)V"), 1); + } +} diff --git a/test/jdk/jdk/classfile/VerifierSelfTest.java b/test/jdk/jdk/classfile/VerifierSelfTest.java new file mode 100644 index 0000000000000..3259be76d3753 --- /dev/null +++ b/test/jdk/jdk/classfile/VerifierSelfTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile Verifier. + * @run junit VerifierSelfTest + */ +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.MethodModel; +import org.junit.jupiter.api.Test; + +class VerifierSelfTest { + + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + @Test + void testVerify() throws IOException { + Stream.of( + Files.walk(JRT.getPath("modules/java.base")), + Files.walk(JRT.getPath("modules"), 2).filter(p -> p.endsWith("module-info.class"))) + .flatMap(p -> p) + .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")).forEach(path -> { + try { + Classfile.parse(path).verify(null); + } catch (IOException e) { + throw new AssertionError(e); + } + }); + } + + @Test + void testFailedDump() throws IOException { + Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class"); + var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver(className -> null)); + byte[] brokenClassBytes = classModel.transform( + (clb, cle) -> { + if (cle instanceof MethodModel mm) { + clb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel cm) { + mb.withCode(cob -> cm.forEachElement(cob)); + } + else + mb.with(me); + }); + } + else + clb.with(cle); + }); + StringBuilder sb = new StringBuilder(); + if (Classfile.parse(brokenClassBytes).verify(sb::append).isEmpty()) { + throw new AssertionError("expected verification failure"); + } + String output = sb.toString(); + if (!output.contains("- method name: ")) { + System.out.println(output); + throw new AssertionError("failed method not dumped to output"); + } + } +} diff --git a/test/jdk/jdk/classfile/WriteTest.java b/test/jdk/jdk/classfile/WriteTest.java new file mode 100644 index 0000000000000..6402c3d3775c0 --- /dev/null +++ b/test/jdk/jdk/classfile/WriteTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile class building. + * @run junit WriteTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; + +import helpers.TestConstants; +import jdk.internal.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import org.junit.jupiter.api.Test; + +import static helpers.TestConstants.MTD_VOID; +import static java.lang.constant.ConstantDescs.*; +import static jdk.internal.classfile.Opcode.*; +import static jdk.internal.classfile.TypeKind.IntType; +import static jdk.internal.classfile.TypeKind.ReferenceType; +import static jdk.internal.classfile.TypeKind.VoidType; + +class WriteTest { + + @Test + void testJavapWrite() { + + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", + MethodTypeDesc.ofDescriptor("()V"), false) + .returnInstruction(VoidType) + ) + ) + .withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(c0 -> { + Label loopTop = c0.newLabel(); + Label loopEnd = c0.newLabel(); + c0 + .constantInstruction(ICONST_1, 1) // 0 + .storeInstruction(TypeKind.IntType, 1) // 1 + .constantInstruction(ICONST_1, 1) // 2 + .storeInstruction(TypeKind.IntType, 2) // 3 + .labelBinding(loopTop) + .loadInstruction(TypeKind.IntType, 2) // 4 + .constantInstruction(BIPUSH, 10) // 5 + .branchInstruction(IF_ICMPGE, loopEnd) // 6 + .loadInstruction(TypeKind.IntType, 1) // 7 + .loadInstruction(TypeKind.IntType, 2) // 8 + .operatorInstruction(IMUL) // 9 + .storeInstruction(TypeKind.IntType, 1) // 10 + .incrementInstruction(2, 1) // 11 + .branchInstruction(GOTO, loopTop) // 12 + .labelBinding(loopEnd) + .fieldInstruction(GETSTATIC, TestConstants.CD_System, "out", TestConstants.CD_PrintStream) // 13 + .loadInstruction(TypeKind.IntType, 1) + .invokeInstruction(INVOKEVIRTUAL, TestConstants.CD_PrintStream, "println", TestConstants.MTD_INT_VOID, false) // 15 + .returnInstruction(VoidType); + })); + }); + } + + @Test + void testPrimitiveWrite() { + + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC) + .with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + ) + .withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.PUBLIC, AccessFlag.STATIC).flagsMask(), + mb -> mb.withCode(c0 -> { + Label loopTop = c0.newLabel(); + Label loopEnd = c0.newLabel(); + c0 + .constantInstruction(ICONST_1, 1) // 0 + .storeInstruction(IntType, 1) // 1 + .constantInstruction(ICONST_1, 1) // 2 + .storeInstruction(IntType, 2) // 3 + .labelBinding(loopTop) + .loadInstruction(IntType, 2) // 4 + .constantInstruction(BIPUSH, 10) // 5 + .branchInstruction(IF_ICMPGE, loopEnd) // 6 + .loadInstruction(IntType, 1) // 7 + .loadInstruction(IntType, 2) // 8 + .operatorInstruction(IMUL) // 9 + .storeInstruction(IntType, 1) // 10 + .incrementInstruction(2, 1) // 11 + .branchInstruction(GOTO, loopTop) // 12 + .labelBinding(loopEnd) + .fieldInstruction(GETSTATIC, TestConstants.CD_System, "out", TestConstants.CD_PrintStream) // 13 + .loadInstruction(IntType, 1) + .invokeInstruction(INVOKEVIRTUAL, TestConstants.CD_PrintStream, "println", TestConstants.MTD_INT_VOID, false) // 15 + .returnInstruction(VoidType); + })); + }); + } +} diff --git a/test/jdk/jdk/classfile/examples/AnnotationsExamples.java b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java new file mode 100644 index 0000000000000..e371d559f5080 --- /dev/null +++ b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile AnnotationsExamples compilation. + * @compile AnnotationsExamples.java + */ +import java.lang.constant.ClassDesc; +import java.util.ArrayList; +import java.util.List; + +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.components.ClassPrinter; + +public class AnnotationsExamples { + + /** Add a single annotation to a class using a builder convenience */ + public byte[] addAnno(ClassModel m) { + // @@@ Not correct + List annos = List.of(Annotation.of(ClassDesc.of("java.lang.FunctionalInterface"))); + return m.transform(ClassTransform.endHandler(cb -> cb.with(RuntimeVisibleAnnotationsAttribute.of(annos)))); + } + + /** + * Find classes with annotations of a certain type + */ + public void findAnnotation(ClassModel m) { + if (m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent()) { + RuntimeVisibleAnnotationsAttribute a = m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).get(); + for (Annotation ann : a.annotations()) { + if (ann.className().stringValue().equals("Ljava/lang/FunctionalInterface;")) + System.out.println(m.thisClass().asInternalName()); + } + } + } + + /** + * Find classes with a specific annotation and create a new byte[] with that annotation swapped for @Deprecated. + */ + public void swapAnnotation(ClassModel m) { + ClassModel m2 = m; + + if (m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent()) { + RuntimeVisibleAnnotationsAttribute a = m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).get(); + for (Annotation ann : a.annotations()) { + if (ann.className().stringValue().equals("Ljava/lang/annotation/Documented;")) { + m2 = Classfile.parse(m.transform(SWAP_ANNO_TRANSFORM)); + } + } + } + + if (m2.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent()) { + RuntimeVisibleAnnotationsAttribute a = m2.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).get(); + for (Annotation ann : a.annotations()) { + if (ann.className().stringValue().equals("Ljava/lang/annotation/Documented;")) + throw new RuntimeException(); + } + } + } + + //where + private static final ClassTransform SWAP_ANNO_TRANSFORM = (cb, ce) -> { + switch (ce) { + case RuntimeVisibleAnnotationsAttribute attr -> { + List old = attr.annotations(); + List newAnnos = new ArrayList<>(old.size()); + for (Annotation ann : old) { + if (ann.className().stringValue().equals("Ljava/lang/annotation/Documented;")) { + newAnnos.add(Annotation.of(ClassDesc.of("java.lang.Deprecated"), List.of())); + } + else + newAnnos.add(ann); + } + cb.with(RuntimeVisibleAnnotationsAttribute.of(newAnnos)); + } + default -> cb.with(ce); + } + }; + + /** + * Find classes with a specific annotation and create a new byte[] with the same content except also adding a new annotation + */ + public void addAnnotation(ClassModel m) { + ClassModel m2 = m; + + if (m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent()) { + RuntimeVisibleAnnotationsAttribute a = m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).get(); + for (Annotation ann : a.annotations()) { + if (ann.className().stringValue().equals("Ljava/lang/FunctionalInterface;")) { + m2 = Classfile.parse(m.transform((cb, ce) -> { + if (ce instanceof RuntimeVisibleAnnotationsAttribute ra) { + var oldAnnos = ra.annotations(); + List newAnnos = new ArrayList<>(oldAnnos.size() + 1); + for (Annotation aa :oldAnnos) + newAnnos.add(aa); + ConstantPoolBuilder cpb = cb.constantPool(); + newAnnos.add(Annotation.of(ClassDesc.of("java.lang.Deprecated"), List.of())); + cb.with(RuntimeVisibleAnnotationsAttribute.of(newAnnos)); + } else { + cb.with(ce); + } + })); + } + } + } + + int size = m2.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).orElseThrow().annotations().size(); + if (size !=2) { + StringBuilder sb = new StringBuilder(); + ClassPrinter.toJson(m2, ClassPrinter.Verbosity.TRACE_ALL, sb::append); + System.err.println(sb.toString()); + } + } + + public byte[] viaEndHandlerClassBuilderEdition(ClassModel m) { + return m.transform(ClassTransform.ofStateful(() -> new ClassTransform() { + boolean found = false; + + @Override + public void accept(ClassBuilder cb, ClassElement ce) { + switch (ce) { + case RuntimeVisibleAnnotationsAttribute rvaa -> { + found = true; + List newAnnotations = new ArrayList<>(rvaa.annotations().size() + 1); + newAnnotations.addAll(rvaa.annotations()); + newAnnotations.add(Annotation.of(ClassDesc.of("Foo"))); + cb.with(RuntimeVisibleAnnotationsAttribute.of(newAnnotations)); + } + default -> cb.with(ce); + } + } + + @Override + public void atEnd(ClassBuilder builder) { + if (!found) { + builder.with(RuntimeVisibleAnnotationsAttribute.of(List.of(Annotation.of(ClassDesc.of("Foo"))))); + } + } + })); + } + + public byte[] viaEndHandlerClassTransformEdition(ClassModel m) { + return m.transform(ClassTransform.ofStateful(() -> new ClassTransform() { + boolean found = false; + + @Override + public void accept(ClassBuilder cb, ClassElement ce) { + if (ce instanceof RuntimeVisibleAnnotationsAttribute rvaa) { + found = true; + List newAnnotations = new ArrayList<>(rvaa.annotations().size() + 1); + newAnnotations.addAll(rvaa.annotations()); + newAnnotations.add(Annotation.of(ClassDesc.of("Foo"))); + + cb.with(RuntimeVisibleAnnotationsAttribute.of(newAnnotations)); + } + else + cb.with(ce); + } + + @Override + public void atEnd(ClassBuilder builder) { + if (!found) { + builder.with(RuntimeVisibleAnnotationsAttribute.of(List.of(Annotation.of(ClassDesc.of("Foo"))))); + } + } + })); + } +} \ No newline at end of file diff --git a/test/jdk/jdk/classfile/examples/ExampleGallery.java b/test/jdk/jdk/classfile/examples/ExampleGallery.java new file mode 100644 index 0000000000000..4975fe064c58a --- /dev/null +++ b/test/jdk/jdk/classfile/examples/ExampleGallery.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile ExampleGallery compilation. + * @compile ExampleGallery.java + */ +import java.lang.constant.ClassDesc; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import jdk.internal.classfile.AccessFlags; +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassSignature; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.ClassfileVersion; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.Interfaces; +import jdk.internal.classfile.MethodBuilder; +import jdk.internal.classfile.MethodElement; +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.Signature; +import jdk.internal.classfile.Signature.ClassTypeSig; +import jdk.internal.classfile.Signature.TypeArg; +import jdk.internal.classfile.Superclass; +import jdk.internal.classfile.attribute.ExceptionsAttribute; +import jdk.internal.classfile.attribute.SignatureAttribute; +import jdk.internal.classfile.constantpool.ClassEntry; +import jdk.internal.classfile.instruction.ConstantInstruction; +import jdk.internal.classfile.instruction.InvokeInstruction; + +/** + * ExampleGallery + */ +public class ExampleGallery { + public byte[] changeClassVersion(ClassModel cm) { + return cm.transform((cb, ce) -> { + switch (ce) { + case ClassfileVersion cv -> cb.withVersion(57, 0); + default -> cb.with(ce); + } + }); + } + + public byte[] incrementClassVersion(ClassModel cm) { + return cm.transform((cb, ce) -> { + switch (ce) { + case ClassfileVersion cv -> cb.withVersion(cv.majorVersion() + 1, 0); + default -> cb.with(ce); + } + }); + } + + public byte[] changeSuperclass(ClassModel cm, ClassDesc superclass) { + return cm.transform((cb, ce) -> { + switch (ce) { + case Superclass sc -> cb.withSuperclass(superclass); + default -> cb.with(ce); + } + }); + } + + public byte[] overrideSuperclass(ClassModel cm, ClassDesc superclass) { + return cm.transform(ClassTransform.endHandler(cb -> cb.withSuperclass(superclass))); + } + + public byte[] removeInterface(ClassModel cm, String internalName) { + return cm.transform((cb, ce) -> { + switch (ce) { + case Interfaces i -> cb.withInterfaces(i.interfaces().stream() + .filter(e -> !e.asInternalName().equals(internalName)) + .toList()); + default -> cb.with(ce); + } + }); + } + + public byte[] addInterface(ClassModel cm, ClassDesc newIntf) { + return cm.transform(ClassTransform.ofStateful(() -> new ClassTransform() { + boolean seen = false; + + @Override + public void accept(ClassBuilder builder, ClassElement element) { + switch (element) { + case Interfaces i: + List interfaces = Stream.concat(i.interfaces().stream(), + Stream.of(builder.constantPool().classEntry(newIntf))) + .distinct() + .toList(); + builder.withInterfaces(interfaces); + seen = true; + break; + + default: + builder.with(element); + } + } + + @Override + public void atEnd(ClassBuilder builder) { + if (!seen) + builder.withInterfaceSymbols(newIntf); + } + })); + + } + public byte[] addInterface1(ClassModel cm, ClassDesc newIntf) { + return cm.transform(ClassTransform.ofStateful(() -> new ClassTransform() { + Interfaces interfaces; + + @Override + public void accept(ClassBuilder builder, ClassElement element) { + switch (element) { + case Interfaces i -> interfaces = i; + default -> builder.with(element); + } + } + + @Override + public void atEnd(ClassBuilder builder) { + if (interfaces != null) { + builder.withInterfaces(Stream.concat(interfaces.interfaces().stream(), + Stream.of(builder.constantPool().classEntry(newIntf))) + .distinct() + .toList()); + } + else { + builder.withInterfaceSymbols(newIntf); + } + } + })); + } + + public byte[] removeSignature(ClassModel cm) { + return cm.transform(ClassTransform.dropping(e -> e instanceof SignatureAttribute)); + } + + public byte[] changeSignature(ClassModel cm) { + return cm.transform((cb, ce) -> { + switch (ce) { + case SignatureAttribute sa -> { + String result = sa.signature().stringValue(); + cb.with(SignatureAttribute.of(ClassSignature.parseFrom(result.replace("this/", "that/")))); + } + default -> cb.with(ce); + } + }); + } + + public byte[] setSignature(ClassModel cm) { + return cm.transform(ClassTransform.dropping(e -> e instanceof SignatureAttribute) + .andThen(ClassTransform.endHandler(b -> b.with(SignatureAttribute.of( + ClassSignature.of( + ClassTypeSig.of(ClassDesc.of("impl.Fox"), + TypeArg.of(ClassTypeSig.of(ClassDesc.of("impl.Cow")))), + ClassTypeSig.of(ClassDesc.of("api.Rat")))))))); + } + + // @@@ strip annos (class, all) + + public byte[] stripFields(ClassModel cm, Predicate filter) { + return cm.transform(ClassTransform.dropping(e -> e instanceof FieldModel fm + && filter.test(fm.fieldName().stringValue()))); + } + + public byte[] addField(ClassModel cm) { + return cm.transform(ClassTransform.endHandler(cb -> cb.withField("cool", ClassDesc.ofDescriptor("(I)D"), Classfile.ACC_PUBLIC))); + } + + public byte[] changeFieldSig(ClassModel cm) { + return cm.transform(ClassTransform.transformingFields((fb, fe) -> { + if (fe instanceof SignatureAttribute sa) + fb.with(SignatureAttribute.of(Signature.parseFrom(sa.signature().stringValue().replace("this/", "that/")))); + else + fb.with(fe); + })); + } + + public byte[] changeFieldFlags(ClassModel cm) { + return cm.transform(ClassTransform.transformingFields((fb, fe) -> { + switch (fe) { + case AccessFlags a -> fb.with(AccessFlags.ofField(a.flagsMask() & ~Classfile.ACC_PUBLIC & ~Classfile.ACC_PROTECTED)); + default -> fb.with(fe); + } + })); + } + + public byte[] addException(ClassModel cm, ClassDesc ex) { + return cm.transform(ClassTransform.transformingMethods( + MethodTransform.ofStateful(() -> new MethodTransform() { + ExceptionsAttribute attr; + + @Override + public void accept(MethodBuilder builder, MethodElement element) { + switch (element) { + case ExceptionsAttribute a -> attr = a; + default -> builder.with(element); + } + } + + @Override + public void atEnd(MethodBuilder builder) { + if (attr == null) { + builder.with(ExceptionsAttribute.ofSymbols(ex)); + } + else { + ClassEntry newEx = builder.constantPool().classEntry(ex); + if (!attr.exceptions().contains(newEx)) { + attr = ExceptionsAttribute.of(Stream.concat(attr.exceptions().stream(), + Stream.of(newEx)) + .toList()); + } + builder.with(attr); + } + } + }))); + } + + public byte[] addInstrumentation(ClassModel cm) { + CodeTransform transform = CodeTransform.ofStateful(() -> new CodeTransform() { + boolean found = true; + + @Override + public void accept(CodeBuilder codeB, CodeElement codeE) { + if (found) { + codeB.nopInstruction(); + found = false; + } + codeB.with(codeE); + } + }); + + return cm.transform(ClassTransform.transformingMethodBodies(transform)); + } + + public byte[] addInstrumentationBeforeInvoke(ClassModel cm) { + return cm.transform(ClassTransform.transformingMethodBodies((codeB, codeE) -> { + switch (codeE) { + case InvokeInstruction i -> { + codeB.nopInstruction(); + codeB.with(codeE); + } + default -> codeB.with(codeE); + } + })); + } + + public byte[] replaceIntegerConstant(ClassModel cm) { + return cm.transform(ClassTransform.transformingMethodBodies((codeB, codeE) -> { + switch (codeE) { + case ConstantInstruction ci -> { + if (ci.constantValue() instanceof Integer i) codeB.constantInstruction(i + 1); + else codeB.with(codeE); + } + default -> codeB.with(codeE); + } + })); + } +} + diff --git a/test/jdk/jdk/classfile/examples/ExperimentalTransformExamples.java b/test/jdk/jdk/classfile/examples/ExperimentalTransformExamples.java new file mode 100644 index 0000000000000..c26e8622b26e4 --- /dev/null +++ b/test/jdk/jdk/classfile/examples/ExperimentalTransformExamples.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile ExperimentalTransformExamples compilation. + * @compile ExperimentalTransformExamples.java + */ +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; + +/** + * ExperimentalTransformExamples + * + */ +public class ExperimentalTransformExamples { + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + static MethodTransform dropMethodAnnos = (mb, me) -> { + if (!(me instanceof RuntimeVisibleAnnotationsAttribute || me instanceof RuntimeInvisibleAnnotationsAttribute)) + mb.with(me); + }; + + static FieldTransform dropFieldAnnos = (fb, fe) -> { + if (!(fe instanceof RuntimeVisibleAnnotationsAttribute || fe instanceof RuntimeInvisibleAnnotationsAttribute)) + fb.with(fe); + }; + + public byte[] deleteAnnotations(ClassModel cm) { + return cm.transform((cb, ce) -> { + switch (ce) { + case MethodModel m -> cb.transformMethod(m, dropMethodAnnos); + case FieldModel f -> cb.transformField(f, dropFieldAnnos); + default -> cb.with(ce); + } + }); + } +} \ No newline at end of file diff --git a/test/jdk/jdk/classfile/examples/ModuleExamples.java b/test/jdk/jdk/classfile/examples/ModuleExamples.java new file mode 100644 index 0000000000000..0ef4f35b7e94b --- /dev/null +++ b/test/jdk/jdk/classfile/examples/ModuleExamples.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile ModuleExamples compilation. + * @compile ModuleExamples.java + */ +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.util.List; +import java.util.function.Consumer; + +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.AnnotationElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.attribute.ModuleAttribute; +import jdk.internal.classfile.attribute.ModuleAttribute.ModuleAttributeBuilder; +import jdk.internal.classfile.attribute.ModuleMainClassAttribute; +import jdk.internal.classfile.attribute.ModulePackagesAttribute; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.internal.classfile.Attributes; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; + +public class ModuleExamples { + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + public void examineModule() throws IOException { + ClassModel cm = Classfile.parse(JRT.getPath("modules/java.base/module-info.class")); + System.out.println("Is JVMS $4.7 compatible module-info: " + cm.isModuleInfo()); + + ModuleAttribute ma = cm.findAttribute(Attributes.MODULE).orElseThrow(); + System.out.println("Module name: " + ma.moduleName().name().stringValue()); + System.out.println("Exports: " + ma.exports()); + + ModuleMainClassAttribute mmca = cm.findAttribute(Attributes.MODULE_MAIN_CLASS).orElse(null); + System.out.println("Does the module have a MainClassAttribte?: " + (mmca != null)); + + ModulePackagesAttribute mmp = cm.findAttribute(Attributes.MODULE_PACKAGES).orElseThrow(); + System.out.println("Packages?: " + mmp.packages()); + } + + public void buildModuleFromScratch() { + var moduleName = ModuleDesc.of("the.very.best.module"); + int moduleFlags = 0; + + Consumer handler = (mb -> {mb + .moduleFlags(moduleFlags) + .exports(PackageDesc.of("export.some.pkg"), 0) + .exports(PackageDesc.of("qualified.export.to") , 0, ModuleDesc.of("to.first.module"), ModuleDesc.of("to.another.module")); + }); + + // Build it + byte[] moduleInfo = Classfile.buildModule(ModuleAttribute.of(moduleName, handler), List.of(), clb -> { + + // Add an annotation to the module + clb.with(RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.ofDescriptor("Ljava/lang/Deprecated;"), + AnnotationElement.ofBoolean("forRemoval", true), + AnnotationElement.ofString("since", "17")))); + }); + + // Examine it + ClassModel mm = Classfile.parse(moduleInfo); + System.out.println("Is module info?: " + mm.isModuleInfo()); + } +} diff --git a/test/jdk/jdk/classfile/examples/TransformExamples.java b/test/jdk/jdk/classfile/examples/TransformExamples.java new file mode 100644 index 0000000000000..d944955b70b26 --- /dev/null +++ b/test/jdk/jdk/classfile/examples/TransformExamples.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @summary Testing Classfile TransformExamples compilation. + * @compile TransformExamples.java + */ +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.Attribute; + +/** + * TransformExamples + */ +public class TransformExamples { + public byte[] noop(ClassModel cm) { + return cm.transform(ClassTransform.ACCEPT_ALL); + } + + public byte[] deleteAllMethods(ClassModel cm) { + return cm.transform((b, e) -> { + if (!(e instanceof MethodModel)) + b.with(e); + }); + } + + public byte[] deleteFieldsWithDollarInName(ClassModel cm) { + return cm.transform((b, e) -> + { + if (!(e instanceof FieldModel fm && fm.fieldName().stringValue().contains("$"))) + b.with(e); + }); + } + + public byte[] deleteAttributes(ClassModel cm) { + return cm.transform((b, e) -> { + if (!(e instanceof Attribute)) + b.with(e); + }); + } + + public byte[] keepMethodsAndFields(ClassModel cm) { + return cm.transform((b, e) -> { + if (e instanceof MethodModel || e instanceof FieldModel) + b.with(e); + }); + } +} diff --git a/test/jdk/jdk/classfile/helpers/ByteArrayClassLoader.java b/test/jdk/jdk/classfile/helpers/ByteArrayClassLoader.java new file mode 100644 index 0000000000000..cbd311783cc40 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/ByteArrayClassLoader.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; + +public class ByteArrayClassLoader extends ClassLoader { + final Map classNameToClass; + + public ByteArrayClassLoader(ClassLoader parent, String name, byte[] bytes) { + this(parent, Collections.singletonMap(name, new ClassData(name, bytes))); + } + + public ByteArrayClassLoader(ClassLoader parent, Map classNameToClass) { + super(parent); + this.classNameToClass = classNameToClass; + } + + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (classNameToClass.containsKey(name)) { + return findClass(name); + } + return super.loadClass(name, resolve); + } + + public Class findClass(String name) throws ClassNotFoundException { + ClassData d = classNameToClass.get(name); + if (d != null) { + if (d.klass != null) { + return d.klass; + } + return d.klass = defineClass(name, d.bytes, 0, d.bytes.length); + } + return super.findClass(name); + } + + public void loadAll() throws Exception { + for (String className : classNameToClass.keySet()) { + loadClass(className); + } + } + + public Method getMethod(String className, String methodName) throws Exception { + for (Method m : loadClass(className).getDeclaredMethods()) { + if (m.getName().equals(methodName)) { + return m; + } + } + throw new IllegalArgumentException("Method name '" + methodName + "' not found in '" + className + "'"); + } + + public static class ClassData { + final String name; + final byte[] bytes; + Class klass; + + public ClassData(String name, byte[] bytes) { + this.name = name; + this.bytes = bytes; + } + } +} diff --git a/test/jdk/jdk/classfile/helpers/ClassRecord.java b/test/jdk/jdk/classfile/helpers/ClassRecord.java new file mode 100644 index 0000000000000..834bdaad2345b --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/ClassRecord.java @@ -0,0 +1,1267 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.RecordComponent; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.constantpool.*; +import jdk.internal.classfile.instruction.*; + +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static jdk.internal.classfile.Classfile.*; +import static jdk.internal.classfile.Attributes.*; +import static helpers.ClassRecord.CompatibilityFilter.By_ClassBuilder; + +/** + * ClassRecord + */ +public record ClassRecord( + int majorVersion, + int minorVersion, + String thisClass, + String superClass, + Set interfaces, + String classFlags, + Map fields, + Map methods, + AttributesRecord attributes) { + + public enum CompatibilityFilter { + Read_all, By_ClassBuilder; + + private T isNotDirectlyComparable(CompatibilityFilter compatibilityFilter[], T value) { + for (CompatibilityFilter p : compatibilityFilter) { + if (p == this) return null; + } + return value; + } + } + + public enum DefinedValue { + DEFINED + } + + public static ClassRecord ofStreamingElements(ClassModel cl, CompatibilityFilter... compatibilityFilter) { + return ofStreamingElements( + cl.majorVersion(), + cl.minorVersion(), + cl.thisClass().asInternalName(), + cl.superclass().map(ClassEntry::asInternalName).orElse(null), + cl.interfaces().stream().map(ClassEntry::asInternalName).collect(toSet()), + cl.flags().flagsMask(), + cl.constantPool(), + cl::elementStream, compatibilityFilter); + } + public static ClassRecord ofStreamingElements(int majorVersion, int minorVersion, String thisClass, String superClass, Set interfaces, int flags, ConstantPool cp, Supplier> elements, CompatibilityFilter... compatibilityFilter) { + return new ClassRecord( + majorVersion, + minorVersion, + thisClass, + superClass, + interfaces, + Flags.toString(flags, false), + elements.get().filter(e -> e instanceof FieldModel).map(e -> (FieldModel)e).collect(toMap( + fm -> fm.fieldName().stringValue() + fm.fieldType().stringValue(), + fm -> FieldRecord.ofStreamingElements(fm.fieldName().stringValue(), fm.fieldType().stringValue(), fm.flags().flagsMask(), fm::elementStream, compatibilityFilter))), + elements.get().filter(e -> e instanceof MethodModel).map(e -> (MethodModel)e).collect(toMap( + mm -> mm.methodName().stringValue() + mm.methodType().stringValue(), + mm -> MethodRecord.ofStreamingElements(mm.methodName().stringValue(), mm.methodType().stringValue(), mm.flags().flagsMask(), mm::elementStream, compatibilityFilter))), + AttributesRecord.ofStreamingElements(elements, cp, compatibilityFilter)); + } + + public static ClassRecord ofClassModel(ClassModel cl, CompatibilityFilter... compatibilityFilter) { + return new ClassRecord( + cl.majorVersion(), + cl.minorVersion(), + cl.thisClass().asInternalName(), + cl.superclass().map(ClassEntry::asInternalName).orElse(null), + cl.interfaces().stream().map(ci -> ci.asInternalName()).collect(toSet()), + Flags.toString(cl.flags().flagsMask(), false), + cl.fields().stream().collect(toMap(f -> f.fieldName().stringValue() + f.fieldType().stringValue(), f -> FieldRecord.ofFieldModel(f, compatibilityFilter))), + cl.methods().stream().collect(toMap(m -> m.methodName().stringValue() + m.methodType().stringValue(), m -> MethodRecord.ofMethodModel(m, compatibilityFilter))), + AttributesRecord.ofAttributes(cl::attributes, compatibilityFilter)); + } + + public record FieldRecord( + String fieldName, + String fieldType, + String fieldFlags, + AttributesRecord fieldAttributes) { + + public static FieldRecord ofStreamingElements(String fieldName, String fieldType, int flags, Supplier> elements, CompatibilityFilter... compatibilityFilter) { + return new FieldRecord( + fieldName, + fieldType, + Flags.toString(flags, false), + AttributesRecord.ofStreamingElements(elements, null, compatibilityFilter)); + } + + public static FieldRecord ofFieldModel(FieldModel f, CompatibilityFilter... compatibilityFilter) { + return new FieldRecord( + f.fieldName().stringValue(), + f.fieldType().stringValue(), + Flags.toString(f.flags().flagsMask(), false), + AttributesRecord.ofAttributes(f::attributes, compatibilityFilter)); + } + } + + public record MethodRecord( + String methodName, + String methodType, + String methodFlags, + AttributesRecord methodAttributes) { + + public static MethodRecord ofStreamingElements(String methodName, String methodType, int flags, Supplier> elements, CompatibilityFilter... compatibilityFilter) { + return new MethodRecord( + methodName, + methodType, + Flags.toString(flags, true), + AttributesRecord.ofStreamingElements(elements, null, compatibilityFilter)); + } + + public static MethodRecord ofMethodModel(MethodModel m, CompatibilityFilter... compatibilityFilter) { + return new MethodRecord( + m.methodName().stringValue(), + m.methodType().stringValue(), + Flags.toString(m.flags().flagsMask(), true), + AttributesRecord.ofAttributes(m::attributes, compatibilityFilter)); + } + } + + private static, U> U mapAttr(Map> attrs, AttributeMapper mapper, Function f) { + return mapAttr(attrs, mapper, f, null); + } + + private static, U> U mapAttr(Map> attrs, AttributeMapper mapper, Function f, U defaultReturn) { + @SuppressWarnings("unchecked") + var attr = (T) attrs.get(mapper.name()); + return map(attr, a -> f.apply(a), defaultReturn); + } + + interface AttributeFinder extends Supplier>> { + + @SuppressWarnings("unchecked") + default , R> R findAndMap(AttributeMapper m, Function mapping) { + for (Attribute a : get()) { + if (a.attributeMapper() == m) { + return mapping.apply((T) a); + } + } + return null; + } + + @SuppressWarnings("unchecked") + default > Stream findAll(AttributeMapper m) { + return get().stream().filter(a -> a.attributeMapper() == m).map(a -> (T)a); + } + } + + public static Collector> toSetOrNull() { + return Collectors.collectingAndThen(Collectors.toSet(), (Set s) -> s.isEmpty() ? null : s); + } + + public record AttributesRecord( + ElementValueRecord annotationDefaultAttribute, + Set bootstrapMethodsAttribute, + CodeRecord codeAttribute, + String compilationIDAttribute, + ConstantPoolEntryRecord constantValueAttribute, + DefinedValue deprecated, + EnclosingMethodRecord enclosingMethodAttribute, + Set exceptionsAttribute, + Map innerClassesAttribute, + List methodParametersAttribute, + ModuleRecord moduleAttribute, + ModuleHashesRecord moduleHashesAttribute, + String moduleMainClassAttribute, + Set modulePackagesAttribute, + Integer moduleResolutionAttribute, + String moduleTargetAttribute, + String nestHostAttribute, + Set nestMembersAttribute, + Set permittedSubclassesAttribute, + List recordAttribute, + Set runtimeVisibleAnnotationsAttribute, + Set runtimeInvisibleAnnotationsAttribute, + List> runtimeVisibleParameterAnnotationsAttribute, + List> runtimeInvisibleParameterAnnotationsAttribute, + Set runtimeVisibleTypeAnnotationsAttribute, + Set runtimeInvisibleTypeAnnotationsAttribute, + String signatureAttribute, + String sourceDebugExtensionAttribute, + String sourceFileAttribute, + String sourceIDAttribute, + DefinedValue syntheticAttribute) { + + public static AttributesRecord ofStreamingElements(Supplier> elements, ConstantPool cp, CompatibilityFilter... cf) { + Map> attrs = elements.get().filter(e -> e instanceof Attribute) + .map(e -> (Attribute) e) + .collect(toMap(Attribute::attributeName, e -> e)); + return new AttributesRecord( + mapAttr(attrs, ANNOTATION_DEFAULT, a -> ElementValueRecord.ofElementValue(a.defaultValue())), + cp == null ? null : IntStream.range(0, cp.bootstrapMethodCount()).mapToObj(i -> BootstrapMethodRecord.ofBootstrapMethodEntry(cp.bootstrapMethodEntry(i))).collect(toSetOrNull()), + mapAttr(attrs, CODE, a -> CodeRecord.ofStreamingElements(a.maxStack(), a.maxLocals(), a.codeLength(), a::elementStream, a, new CodeNormalizerHelper(a.codeArray()), cf)), + mapAttr(attrs, COMPILATION_ID, a -> a.compilationId().stringValue()), + mapAttr(attrs, CONSTANT_VALUE, a -> ConstantPoolEntryRecord.ofCPEntry(a.constant())), + mapAttr(attrs, DEPRECATED, a -> DefinedValue.DEFINED), + mapAttr(attrs, ENCLOSING_METHOD, a -> EnclosingMethodRecord.ofEnclosingMethodAttribute(a)), + mapAttr(attrs, EXCEPTIONS, a -> new HashSet<>(a.exceptions().stream().map(e -> e.asInternalName()).toList())), + mapAttr(attrs, INNER_CLASSES, a -> a.classes().stream().collect(toMap(ic -> ic.innerClass().asInternalName(), ic -> InnerClassRecord.ofInnerClassInfo(ic)))), + mapAttr(attrs, METHOD_PARAMETERS, a -> a.parameters().stream().map(mp -> MethodParameterRecord.ofMethodParameter(mp)).toList()), + mapAttr(attrs, MODULE, a -> ModuleRecord.ofModuleAttribute(a)), + mapAttr(attrs, MODULE_HASHES, a -> ModuleHashesRecord.ofModuleHashesAttribute(a)), + mapAttr(attrs, MODULE_MAIN_CLASS, a -> a.mainClass().asInternalName()), + mapAttr(attrs, MODULE_PACKAGES, a -> a.packages().stream().map(p -> p.name().stringValue()).collect(toSet())), + mapAttr(attrs, MODULE_RESOLUTION, a -> a.resolutionFlags()), + mapAttr(attrs, MODULE_TARGET, a -> a.targetPlatform().stringValue()), + mapAttr(attrs, NEST_HOST, a -> a.nestHost().asInternalName()), + mapAttr(attrs, NEST_MEMBERS, a -> a.nestMembers().stream().map(m -> m.asInternalName()).collect(toSet())), + mapAttr(attrs, PERMITTED_SUBCLASSES, a -> new HashSet<>(a.permittedSubclasses().stream().map(e -> e.asInternalName()).toList())), + mapAttr(attrs, RECORD, a -> a.components().stream().map(rc -> RecordComponentRecord.ofRecordComponent(rc, cf)).toList()), + elements.get().filter(e -> e instanceof RuntimeVisibleAnnotationsAttribute).map(e -> (RuntimeVisibleAnnotationsAttribute) e).flatMap(a -> a.annotations().stream()) + .map(AnnotationRecord::ofAnnotation).collect(toSetOrNull()), + elements.get().filter(e -> e instanceof RuntimeInvisibleAnnotationsAttribute).map(e -> (RuntimeInvisibleAnnotationsAttribute) e).flatMap(a -> a.annotations().stream()) + .map(AnnotationRecord::ofAnnotation).collect(toSetOrNull()), + mapAttr(attrs, RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, a -> a.parameterAnnotations().stream().map(list -> list.stream().map(AnnotationRecord::ofAnnotation).collect(toSet())).toList()), + mapAttr(attrs, RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, a -> a.parameterAnnotations().stream().map(list -> list.stream().map(AnnotationRecord::ofAnnotation).collect(toSet())).toList()), + mapAttr(attrs, RUNTIME_VISIBLE_TYPE_ANNOTATIONS, a -> a.annotations().stream().map(TypeAnnotationRecord::ofTypeAnnotation).collect(toSet())), + mapAttr(attrs, RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, a -> a.annotations().stream().map(TypeAnnotationRecord::ofTypeAnnotation).collect(toSet())), + mapAttr(attrs, SIGNATURE, a -> a.signature().stringValue()), + mapAttr(attrs, SOURCE_DEBUG_EXTENSION, a -> new String(a.contents(), StandardCharsets.UTF_8)), + mapAttr(attrs, SOURCE_FILE, a -> a.sourceFile().stringValue()), + mapAttr(attrs, SOURCE_ID, a -> a.sourceId().stringValue()), + mapAttr(attrs, SYNTHETIC, a -> DefinedValue.DEFINED) + ); + } + + public static AttributesRecord ofAttributes(AttributeFinder af, CompatibilityFilter... cf) { + return new AttributesRecord( + af.findAndMap(Attributes.ANNOTATION_DEFAULT, a -> ElementValueRecord.ofElementValue(a.defaultValue())), + af.findAndMap(Attributes.BOOTSTRAP_METHODS, a -> a.bootstrapMethods().stream().map(bm -> BootstrapMethodRecord.ofBootstrapMethodEntry(bm)).collect(toSet())), + af.findAndMap(Attributes.CODE, a -> CodeRecord.ofCodeAttribute(a, cf)), + af.findAndMap(Attributes.COMPILATION_ID, a -> a.compilationId().stringValue()), + af.findAndMap(Attributes.CONSTANT_VALUE, a -> ConstantPoolEntryRecord.ofCPEntry(a.constant())), + af.findAndMap(Attributes.DEPRECATED, a -> DefinedValue.DEFINED), + af.findAndMap(Attributes.ENCLOSING_METHOD, a -> EnclosingMethodRecord.ofEnclosingMethodAttribute(a)), + af.findAndMap(Attributes.EXCEPTIONS, a -> a.exceptions().stream().map(e -> e.asInternalName()).collect(toSet())), + af.findAndMap(Attributes.INNER_CLASSES, a -> a.classes().stream().collect(toMap(ic -> ic.innerClass().asInternalName(), ic -> InnerClassRecord.ofInnerClassInfo(ic)))), + af.findAndMap(Attributes.METHOD_PARAMETERS, a -> a.parameters().stream().map(mp -> MethodParameterRecord.ofMethodParameter(mp)).toList()), + af.findAndMap(Attributes.MODULE, a -> ModuleRecord.ofModuleAttribute(a)), + af.findAndMap(Attributes.MODULE_HASHES, a -> ModuleHashesRecord.ofModuleHashesAttribute(a)), + af.findAndMap(Attributes.MODULE_MAIN_CLASS, a -> a.mainClass().asInternalName()), + af.findAndMap(Attributes.MODULE_PACKAGES, a -> a.packages().stream().map(p -> p.name().stringValue()).collect(toSet())), + af.findAndMap(Attributes.MODULE_RESOLUTION, a -> a.resolutionFlags()), + af.findAndMap(Attributes.MODULE_TARGET, a -> a.targetPlatform().stringValue()), + af.findAndMap(Attributes.NEST_HOST, a -> a.nestHost().asInternalName()), + af.findAndMap(Attributes.NEST_MEMBERS, a -> a.nestMembers().stream().map(m -> m.asInternalName()).collect(toSet())), + af.findAndMap(Attributes.PERMITTED_SUBCLASSES, a -> a.permittedSubclasses().stream().map(e -> e.asInternalName()).collect(toSet())), + af.findAndMap(RECORD, a -> a.components().stream().map(rc -> RecordComponentRecord.ofRecordComponent(rc, cf)).toList()), + af.findAll(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).flatMap(a -> a.annotations().stream()).map(AnnotationRecord::ofAnnotation).collect(toSetOrNull()), + af.findAll(Attributes.RUNTIME_INVISIBLE_ANNOTATIONS).flatMap(a -> a.annotations().stream()).map(AnnotationRecord::ofAnnotation).collect(toSetOrNull()), + af.findAndMap(Attributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, a -> a.parameterAnnotations().stream().map(list -> list.stream().map(AnnotationRecord::ofAnnotation).collect(toSet())).toList()), + af.findAndMap(Attributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, a -> a.parameterAnnotations().stream().map(list -> list.stream().map(AnnotationRecord::ofAnnotation).collect(toSet())).toList()), + af.findAndMap(Attributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS, a -> a.annotations().stream().map(TypeAnnotationRecord::ofTypeAnnotation).collect(toSet())), + af.findAndMap(Attributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, a -> a.annotations().stream().map(TypeAnnotationRecord::ofTypeAnnotation).collect(toSet())), + af.findAndMap(Attributes.SIGNATURE, a -> a.signature().stringValue()), + af.findAndMap(Attributes.SOURCE_DEBUG_EXTENSION, a -> new String(a.contents(), StandardCharsets.UTF_8)), + af.findAndMap(Attributes.SOURCE_FILE, a -> a.sourceFile().stringValue()), + af.findAndMap(Attributes.SOURCE_ID, a -> a.sourceId().stringValue()), + af.findAndMap(Attributes.SYNTHETIC, a -> DefinedValue.DEFINED)); + } + } + + public record CodeAttributesRecord( + Set characterRangeTableAttribute, + Set lineNumbersTableAttribute, + Set localVariableTableAttribute, + Set localVariableTypeTableAttribute, + Set runtimeVisibleTypeAnnotationsAttribute, + Set runtimeInvisibleTypeAnnotationsAttribute) { + + static CodeAttributesRecord ofStreamingElements(Supplier> elements, CodeAttribute lc, CodeNormalizerHelper code, CompatibilityFilter... cf) { + int[] p = {0}; + var characterRanges = new HashSet(); + var lineNumbers = new HashSet(); + var localVariables = new HashSet(); + var localVariableTypes = new HashSet(); + var visibleTypeAnnos = new HashSet(); + var invisibleTypeAnnos = new HashSet(); + elements.get().forEach(e -> { + switch (e) { + case Instruction ins -> p[0] += ins.sizeInBytes(); + case CharacterRange cr -> characterRanges.add(CharacterRangeRecord.ofCharacterRange(cr, lc, code)); + case LineNumber ln -> lineNumbers.add(new LineNumberRecord(ln.line(), code.targetIndex(p[0]))); + case LocalVariable lv -> localVariables.add(LocalVariableRecord.ofLocalVariable(lv, lc, code)); + case LocalVariableType lvt -> localVariableTypes.add(LocalVariableTypeRecord.ofLocalVariableType(lvt, lc, code)); + case RuntimeVisibleTypeAnnotationsAttribute taa -> taa.annotations().forEach(ann -> visibleTypeAnnos.add(TypeAnnotationRecord.ofTypeAnnotation(ann, lc, code))); + case RuntimeInvisibleTypeAnnotationsAttribute taa -> taa.annotations().forEach(ann -> invisibleTypeAnnos.add(TypeAnnotationRecord.ofTypeAnnotation(ann, lc, code))); + default -> {} + }}); + return new CodeAttributesRecord( + characterRanges.isEmpty() ? null : characterRanges, + lineNumbers.isEmpty() ? null : lineNumbers, + localVariables.isEmpty() ? null : localVariables, + localVariableTypes.isEmpty() ? null : localVariableTypes, + visibleTypeAnnos.isEmpty() ? null : visibleTypeAnnos, + invisibleTypeAnnos.isEmpty() ? null : invisibleTypeAnnos); + } + + static CodeAttributesRecord ofAttributes(AttributeFinder af, CodeNormalizerHelper code, CodeAttribute lr, CompatibilityFilter... cf) { + return new CodeAttributesRecord( + af.findAll(Attributes.CHARACTER_RANGE_TABLE).flatMap(a -> a.characterRangeTable().stream()).map(cr -> CharacterRangeRecord.ofCharacterRange(cr, code)).collect(toSetOrNull()), + af.findAll(Attributes.LINE_NUMBER_TABLE).flatMap(a -> a.lineNumbers().stream()).map(ln -> new LineNumberRecord(ln.lineNumber(), code.targetIndex(ln.startPc()))).collect(toSetOrNull()), + af.findAll(Attributes.LOCAL_VARIABLE_TABLE).flatMap(a -> a.localVariables().stream()).map(lv -> LocalVariableRecord.ofLocalVariableInfo(lv, code)).collect(toSetOrNull()), + af.findAll(Attributes.LOCAL_VARIABLE_TYPE_TABLE).flatMap(a -> a.localVariableTypes().stream()).map(lv -> LocalVariableTypeRecord.ofLocalVariableTypeInfo(lv, code)).collect(toSetOrNull()), + af.findAndMap(Attributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS, a -> a.annotations().stream().map(ann -> TypeAnnotationRecord.ofTypeAnnotation(ann, lr, code)).collect(toSet())), + af.findAndMap(Attributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, a -> a.annotations().stream().map(ann -> TypeAnnotationRecord.ofTypeAnnotation(ann, lr, code)).collect(toSet()))); + } + } + + public record AnnotationRecord( + String type, + Map elementValues) { + + public static AnnotationRecord ofAnnotation(Annotation ann) { + return new AnnotationRecord( + ann.className().stringValue(), + ann.elements().stream().collect(toMap(evp -> evp.name().stringValue(), evp -> ElementValueRecord.ofElementValue(evp.value())))); + } + } + + public record BootstrapMethodRecord( + ConstantPoolEntryRecord methodHandle, + List arguments) { + + public static BootstrapMethodRecord ofBootstrapMethodEntry(BootstrapMethodEntry bm) { + return new BootstrapMethodRecord( + ConstantPoolEntryRecord.ofCPEntry(bm.bootstrapMethod()), + bm.arguments().stream().map(arg -> ConstantPoolEntryRecord.ofCPEntry(arg)).toList()); + } + } + + public record CharacterRangeRecord( + int startIndex, + int endIndex, + int characterRangeStart, + int characterRangeEnd, + int flags) { + + public static CharacterRangeRecord ofCharacterRange(CharacterRange cr, CodeAttribute lc, CodeNormalizerHelper code) { + return new CharacterRangeRecord(code.targetIndex(lc.labelToBci(cr.startScope())), code.targetIndex(lc.labelToBci(cr.endScope())), cr.characterRangeStart(), cr.characterRangeEnd(), cr.flags()); + } + + public static CharacterRangeRecord ofCharacterRange(CharacterRangeInfo cr, CodeNormalizerHelper code) { + return new CharacterRangeRecord( + code.targetIndex(cr.startPc()), + code.targetIndex(cr.endPc() + 1), cr.characterRangeStart(), cr.characterRangeEnd(), cr.flags()); + } + } + + private static String opcodeMask(String opcode) { + return switch (opcode) { + case "BIPUSH", "SIPUSH" -> "IPUSH"; + case "ICONST_M1" -> "IPUSH#fff"; + case "ICONST_0" -> "IPUSH#0"; + case "ICONST_1" -> "IPUSH#1"; + case "ICONST_2" -> "IPUSH#2"; + case "ICONST_3" -> "IPUSH#3"; + case "ICONST_4" -> "IPUSH#4"; + case "ICONST_5" -> "IPUSH#5"; + case "MULTIANEWARRAY" -> "NEWARRAY"; + case "ANEWARRAY" -> "NEWARRAY"; + default -> { + if (opcode.endsWith("_W")) { + yield opcode.substring(0, opcode.length() - 2); + } else if (opcode.contains("LOAD_") || opcode.contains("STORE_")) { + yield opcode.replace('_', '#'); + } else { + yield opcode; + } + } + }; + } + + private static final class CodeNormalizerHelper { + + private static final byte[] LENGTHS = new byte[] { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3, 3, 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 | (6 << 4), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2 | (4 << 4), 0, 0, 1, 1, 1, + 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, 5, 5, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 4, 4, 4, 2, 4, 3, 3, 0, 0, 1, 3, 2, 3, 3, 3, 1, 2, 1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + private static int instrLen(byte[] code, int pc) { + int op = code[pc] & 0xff; + int aligned = (pc + 4) & ~3; + int len = switch (op) { + case WIDE -> LENGTHS[code[pc + 1] & 0xff] >> 4; + case TABLESWITCH -> aligned - pc + (3 + getInt(code, aligned + 2 * 4) - getInt(code, aligned + 1 * 4) + 1) * 4; + case LOOKUPSWITCH -> aligned - pc + (2 + 2 * getInt(code, aligned + 4)) * 4; + default -> LENGTHS[op] & 0xf; + }; + if (len < 1) throw new AssertionError(pc +": " + op); + return len; + } + + private static int getInt(byte[] bytes, int off) { + return bytes[off] << 24 | (bytes[off + 1] & 0xFF) << 16 | (bytes[off + 2] & 0xFF) << 8 | (bytes[off + 3] & 0xFF); + } + + + private final int[] codeToIndexMap; + + CodeNormalizerHelper(byte[] code) { + this.codeToIndexMap = new int[code.length + 1]; + for (int pc = 1; pc < code.length; pc++) codeToIndexMap[pc] = -pc; + int index = 0; + for (int pc = 0; pc < code.length; pc += instrLen(code, pc)) { + codeToIndexMap[pc] = index++; + } + codeToIndexMap[code.length] = index; + } + + int targetIndex(int pc) { + if (pc < 0) return pc; + if (pc > codeToIndexMap.length) return -pc; + return codeToIndexMap[pc]; + } + + int multipleTargetsHash(int pc, int firstOffset, int[] otherOffsets, int... otherHashes) { + int hash = targetIndex(pc + firstOffset); + for (var off : otherOffsets) { + hash = 31*hash + targetIndex(pc + off); + } + for (var other : otherHashes) { + hash = 31*hash + other; + } + return hash; + } + + int hash(int from, int length) { + int result = 1; + for (int i = from; i < length; i++) { + int elementHash = (codeToIndexMap[i] ^ (codeToIndexMap[i] >>> 32)); + result = 31 * result + elementHash; + } + return result; + } + } + + public record CodeRecord( + Integer maxStack, + Integer maxLocals, + Integer codeLength, + List instructionsSequence, + Set exceptionHandlers, + CodeAttributesRecord codeAttributes) { + + private static List instructions(Supplier> elements, CodeNormalizerHelper code, CodeAttribute lr) { + int[] p = {0}; + return elements.get().filter(e -> e instanceof Instruction).map(e -> { + var ins = (Instruction)e; + String opCode = opcodeMask(ins.opcode().name()); + Integer hash = switch (ins) { + case FieldInstruction cins -> + ConstantPoolEntryRecord.ofCPEntry(cins.field()).hashCode(); + case InvokeInstruction cins -> + ConstantPoolEntryRecord.ofCPEntry(cins.method()).hashCode(); + case NewObjectInstruction cins -> + ConstantPoolEntryRecord.ofCPEntry(cins.className()).hashCode(); + case NewReferenceArrayInstruction cins -> { + String type = cins.componentType().asInternalName(); + if (!type.startsWith("[")) + type = "L" + type + ";"; + yield new ConstantPoolEntryRecord.CpClassRecord("[" + type).hashCode() + 1; + } + case NewPrimitiveArrayInstruction cins -> + new ConstantPoolEntryRecord.CpClassRecord("[" + cins.typeKind().descriptor()).hashCode() + 1; + case TypeCheckInstruction cins -> + ConstantPoolEntryRecord.ofCPEntry(cins.type()).hashCode(); + case ConstantInstruction.LoadConstantInstruction cins -> { + var cper = ConstantPoolEntryRecord.ofCPEntry(cins.constantEntry()); + String altOpcode = cper.altOpcode(); + if (altOpcode != null) { + opCode = altOpcode; + yield null; + } + else { + yield cper.hashCode(); + } + } + case InvokeDynamicInstruction cins -> + ConstantPoolEntryRecord.ofCPEntry(cins.invokedynamic()).hashCode(); + case NewMultiArrayInstruction cins -> + ConstantPoolEntryRecord.ofCPEntry(cins.arrayType()).hashCode() + cins.dimensions(); + case BranchInstruction cins -> + code.targetIndex(lr.labelToBci(cins.target())); + case LookupSwitchInstruction cins -> + code.multipleTargetsHash(p[0], lr.labelToBci(cins.defaultTarget()) - p[0], cins.cases().stream().mapToInt(sc -> lr.labelToBci(sc.target()) - p[0]).toArray(), cins.cases().stream().mapToInt(SwitchCase::caseValue).toArray()); + case TableSwitchInstruction cins -> + code.multipleTargetsHash(p[0], lr.labelToBci(cins.defaultTarget()) - p[0], cins.cases().stream().mapToInt(sc -> lr.labelToBci(sc.target()) - p[0]).toArray(), cins.lowValue(), cins.highValue()); + case ConstantInstruction.ArgumentConstantInstruction cins -> + cins.constantValue(); + default -> { + if (ins.sizeInBytes() <= 1) { + yield null; + } + else if ((ins instanceof LoadInstruction local)) { + yield local.slot(); + } + else if ((ins instanceof StoreInstruction local)) { + yield local.slot(); + } + else { + yield code.hash(p[0] + 1, ins.sizeInBytes()); + } + } + }; + p[0] += ins.sizeInBytes(); + return opCode + (hash != null ? '#' + Integer.toHexString(hash & 0xfff) : ""); + }).toList(); + } + + public static CodeRecord ofStreamingElements(int maxStack, int maxLocals, int codeLength, Supplier> elements, CodeAttribute lc, CodeNormalizerHelper codeHelper, CompatibilityFilter... cf) { + return new CodeRecord( + By_ClassBuilder.isNotDirectlyComparable(cf, maxStack), + By_ClassBuilder.isNotDirectlyComparable(cf, maxLocals), + By_ClassBuilder.isNotDirectlyComparable(cf, codeLength), + instructions(elements, codeHelper, lc), + elements.get().filter(e -> e instanceof ExceptionCatch).map(eh -> ExceptionHandlerRecord.ofExceptionCatch((ExceptionCatch)eh, codeHelper, lc)).collect(toSet()), + CodeAttributesRecord.ofStreamingElements(elements, lc, codeHelper, cf)); + } + + public static CodeRecord ofCodeAttribute(CodeAttribute a, CompatibilityFilter... cf) { + var codeHelper = new CodeNormalizerHelper(a.codeArray()); + return new CodeRecord( + By_ClassBuilder.isNotDirectlyComparable(cf, a.maxStack()), + By_ClassBuilder.isNotDirectlyComparable(cf, a.maxLocals()), + By_ClassBuilder.isNotDirectlyComparable(cf, a.codeLength()), + instructions(a::elementStream, codeHelper, a), + a.exceptionHandlers().stream().map(eh -> ExceptionHandlerRecord.ofExceptionCatch(eh, codeHelper, a)).collect(toSet()), + CodeAttributesRecord.ofAttributes(a::attributes, codeHelper, a, cf)); + } + + public record ExceptionHandlerRecord( + int startIndex, + int endIndex, + int handlerIndex, + ConstantPoolEntryRecord catchType) { + + public static ExceptionHandlerRecord ofExceptionCatch(ExceptionCatch et, CodeNormalizerHelper code, CodeAttribute labelContext) { + return new ExceptionHandlerRecord( + code.targetIndex(labelContext.labelToBci(et.tryStart())), + code.targetIndex(labelContext.labelToBci(et.tryEnd())), + code.targetIndex(labelContext.labelToBci(et.handler())), + et.catchType().map(ct -> ConstantPoolEntryRecord.ofCPEntry(ct)).orElse(null)); + } + } + } + + public record EnclosingMethodRecord( + String className, + ConstantPoolEntryRecord method) { + + public static EnclosingMethodRecord ofEnclosingMethodAttribute(EnclosingMethodAttribute ema) { + return new EnclosingMethodRecord( + ema.enclosingClass().asInternalName(), + ema.enclosingMethod().map(m -> ConstantPoolEntryRecord.ofCPEntry(m)).orElse(null)); + } + } + + public record InnerClassRecord( + String innerClass, + String innerName, + String outerClass, + String accessFlags) { + + public static InnerClassRecord ofInnerClassInfo(InnerClassInfo ic) { + return new InnerClassRecord( + ic.innerClass().asInternalName(), + ic.innerName().map(Utf8Entry::stringValue).orElse(null), + ic.outerClass().map(ClassEntry::asInternalName).orElse(null), + Flags.toString(ic.flagsMask(), false)); + } + } + + public record LineNumberRecord( + int lineNumber, + int startIndex) {} + + public record LocalVariableRecord( + int startIndex, + int endIndex, + String name, + String descriptor, + int slot) { + + public static LocalVariableRecord ofLocalVariable(LocalVariable lv, CodeAttribute lc, CodeNormalizerHelper code) { + return new LocalVariableRecord( + code.targetIndex(lc.labelToBci(lv.startScope())), + code.targetIndex(lc.labelToBci(lv.endScope())), + lv.name().stringValue(), + lv.type().stringValue(), + lv.slot()); + } + + public static LocalVariableRecord ofLocalVariableInfo(LocalVariableInfo lv, CodeNormalizerHelper code) { + return new LocalVariableRecord( + code.targetIndex(lv.startPc()), + code.targetIndex(lv.startPc() + lv.length()), + lv.name().stringValue(), + lv.type().stringValue(), + lv.slot()); + } + } + + public record LocalVariableTypeRecord( + int startIndex, + int endIndex, + String name, + String signature, + int index) { + + public static LocalVariableTypeRecord ofLocalVariableType(LocalVariableType lvt, CodeAttribute lc, CodeNormalizerHelper code) { + return new LocalVariableTypeRecord( + code.targetIndex(lc.labelToBci(lvt.startScope())), + code.targetIndex(lc.labelToBci(lvt.endScope())), + lvt.name().stringValue(), + lvt.signature().stringValue(), + lvt.slot()); + } + + public static LocalVariableTypeRecord ofLocalVariableTypeInfo(LocalVariableTypeInfo lvt, CodeNormalizerHelper code) { + return new LocalVariableTypeRecord( + code.targetIndex(lvt.startPc()), + code.targetIndex(lvt.startPc() + lvt.length()), + lvt.name().stringValue(), + lvt.signature().stringValue(), + lvt.slot()); + } + } + + public record MethodParameterRecord( + String name, + int accessFlags) { + + public static MethodParameterRecord ofMethodParameter(MethodParameterInfo mp) { + return new MethodParameterRecord(mp.name().map(Utf8Entry::stringValue).orElse(null), mp.flagsMask()); + } + } + + public record ModuleRecord( + String moduleName, + int moduleFlags, + String moduleVersion, + Set requires, + Set exports, + Set opens, + Set uses, + Set provides) { + + public static ModuleRecord ofModuleAttribute(ModuleAttribute m) { + return new ModuleRecord( + m.moduleName().name().stringValue(), + m.moduleFlagsMask(), + m.moduleVersion().map(mv -> mv.stringValue()).orElse(null), + m.requires().stream().map(r -> RequiresRecord.ofRequire(r)).collect(toSet()), + m.exports().stream().map(e -> ExportsRecord.ofExport(e)).collect(toSet()), + m.opens().stream().map(o -> OpensRecord.ofOpen(o)).collect(toSet()), + m.uses().stream().map(u -> u.asInternalName()).collect(toSet()), + m.provides().stream().map(p -> ProvidesRecord.ofProvide(p)).collect(toSet())); + } + + public record RequiresRecord( + String requires, + int requiresFlags, + String requiresVersion) { + + public static RequiresRecord ofRequire(ModuleRequireInfo r) { + return new RequiresRecord(r.requires().name().stringValue(), r.requiresFlagsMask(), r.requiresVersion().map(v -> v.stringValue()).orElse(null)); + } + } + + public record ExportsRecord( + String exports, + int exportFlag, + Set exportsTo) { + + public static ExportsRecord ofExport(ModuleExportInfo e) { + return new ExportsRecord( + e.exportedPackage().name().stringValue(), + e.exportsFlagsMask(), + e.exportsTo().stream().map(to -> to.name().stringValue()).collect(toSet())); + } + } + + public record OpensRecord( + String opens, + int opensFlag, + Set opensTo) { + + public static OpensRecord ofOpen(ModuleOpenInfo o) { + return new OpensRecord( + o.openedPackage().name().stringValue(), + o.opensFlagsMask(), + o.opensTo().stream().map(to -> to.name().stringValue()).collect(toSet())); + } + } + + public record ProvidesRecord( + String provides, + Set providesWith) { + + public static ProvidesRecord ofProvide(ModuleProvideInfo p) { + return new ProvidesRecord( + p.provides().asInternalName(), + p.providesWith().stream().map(w -> w.asInternalName()).collect(toSet())); + } + } + + } + + public record ModuleHashesRecord( + String algorithm, + Map hashes) { + + public static ModuleHashesRecord ofModuleHashesAttribute(ModuleHashesAttribute mh) { + return new ModuleHashesRecord( + mh.algorithm().stringValue(), + mh.hashes().stream().collect(toMap(e -> e.moduleName().name().stringValue(), e -> new BigInteger(1, e.hash()).toString(16)))); + } + } + + public record RecordComponentRecord( + String name, + String descriptor, + AttributesRecord attributes) { + + public static RecordComponentRecord ofRecordComponent(RecordComponentInfo rc, CompatibilityFilter... compatibilityFilter) { + return new RecordComponentRecord(rc.name().stringValue(), rc.descriptor().stringValue(), + AttributesRecord.ofAttributes(rc::attributes, compatibilityFilter)); + } + } + + public enum FrameTypeEnum { + SAME(0, 63), + SAME_LOCALS_1_STACK_ITEM(64, 127), + RESERVED_FOR_FUTURE_USE(128, 246), + SAME_LOCALS_1_STACK_ITEM_EXTENDED(247, 247), + CHOP(248, 250), + SAME_FRAME_EXTENDED(251, 251), + APPEND(252, 254), + FULL_FRAME(255, 255); + + int start; + int end; + + public static FrameTypeEnum of(int frameType) { + for (var e : FrameTypeEnum.values()) { + if (e.start <= frameType && e.end >= frameType) return e; + } + throw new IllegalArgumentException("Invalid frame type: " + frameType); + } + + FrameTypeEnum(int start, int end) { + this.start = start; + this.end = end; + } + } + + public record TypeAnnotationRecord( + int targetType, + TargetInfoRecord targetInfo, + Set targetPath, + AnnotationRecord annotation) { + + public static TypeAnnotationRecord ofTypeAnnotation(TypeAnnotation ann) { + return ofTypeAnnotation(ann, null, null); + } + + public static TypeAnnotationRecord ofTypeAnnotation(TypeAnnotation ann, CodeAttribute lr, CodeNormalizerHelper code) { + return new TypeAnnotationRecord( + ann.targetInfo().targetType().targetTypeValue(), + TargetInfoRecord.ofTargetInfo(ann.targetInfo(), lr, code), + ann.targetPath().stream().map(tpc -> TypePathRecord.ofTypePathComponent(tpc)).collect(toSet()), + AnnotationRecord.ofAnnotation(ann)); + } + + public interface TargetInfoRecord { + + public static TargetInfoRecord ofTargetInfo(TypeAnnotation.TargetInfo tiu, CodeAttribute lr, CodeNormalizerHelper code) { + if (tiu instanceof TypeAnnotation.CatchTarget ct) { + return new CatchTargetRecord(ct.exceptionTableIndex()); + } else if (tiu instanceof TypeAnnotation.EmptyTarget et) { + return new EmptyTargetRecord(); + } else if (tiu instanceof TypeAnnotation.FormalParameterTarget fpt) { + return new FormalParameterTargetRecord(fpt.formalParameterIndex()); + } else if (tiu instanceof TypeAnnotation.LocalVarTarget lvt) { + return new LocalVarTargetRecord(lvt.table().stream().map(ent + -> new LocalVarTargetRecord.EntryRecord(code.targetIndex(lr.labelToBci(ent.startLabel())), code.targetIndex(lr.labelToBci(ent.endLabel())), ent.index())).collect(toSet())); + } else if (tiu instanceof TypeAnnotation.OffsetTarget ot) { + return new OffsetTargetRecord(code.targetIndex(lr.labelToBci(ot.target()))); + } else if (tiu instanceof TypeAnnotation.SupertypeTarget st) { + return new SupertypeTargetRecord(st.supertypeIndex()); + } else if (tiu instanceof TypeAnnotation.ThrowsTarget tt) { + return new ThrowsTargetRecord(tt.throwsTargetIndex()); + } else if (tiu instanceof TypeAnnotation.TypeArgumentTarget tat) { + return new TypeArgumentTargetRecord(code.targetIndex(lr.labelToBci(tat.target())), tat.typeArgumentIndex()); + } else if (tiu instanceof TypeAnnotation.TypeParameterBoundTarget tpbt) { + return new TypeParameterBoundTargetRecord(tpbt.typeParameterIndex(), tpbt.boundIndex()); + } else if (tiu instanceof TypeAnnotation.TypeParameterTarget tpt) { + return new TypeParameterTargetRecord(tpt.typeParameterIndex()); + } else { + throw new IllegalArgumentException(tiu.getClass().getName()); + } + } + + public record CatchTargetRecord(int exceptionTableIndex) implements TargetInfoRecord{} + + public record EmptyTargetRecord() implements TargetInfoRecord {} + + public record FormalParameterTargetRecord(int formalParameterIndex) implements TargetInfoRecord {} + + public record LocalVarTargetRecord(Set table) implements TargetInfoRecord { + + public record EntryRecord(int startPC, int length, int index) {} + } + + public record OffsetTargetRecord(int offset) implements TargetInfoRecord {} + + public record SupertypeTargetRecord(int supertypeIndex) implements TargetInfoRecord {} + + public record ThrowsTargetRecord(int throwsTargetIndex) implements TargetInfoRecord {} + + public record TypeArgumentTargetRecord(int offset, int typeArgumentIndex) implements TargetInfoRecord {} + + public record TypeParameterBoundTargetRecord(int typeParameterIndex, int boundIndex) implements TargetInfoRecord {} + + public record TypeParameterTargetRecord(int typeParameterIndex) implements TargetInfoRecord {} + } + + public record TypePathRecord( + int typePathKind, + int typeArgumentIndex) { + + public static TypePathRecord ofTypePathComponent(TypeAnnotation.TypePathComponent tpc) { + return new TypePathRecord(tpc.typePathKind().tag(), tpc.typeArgumentIndex()); + } + } + } + + public interface ConstantPoolEntryRecord { + + public static ConstantPoolEntryRecord ofCPEntry(PoolEntry cpInfo) { + return switch (cpInfo.tag()) { + case TAG_UTF8 -> + new CpUTF8Record(((Utf8Entry) cpInfo).stringValue()); + case TAG_INTEGER -> + new CpIntegerRecord(((IntegerEntry) cpInfo).intValue()); + case TAG_FLOAT -> + new CpFloatRecord(((FloatEntry) cpInfo).floatValue()); + case TAG_LONG -> + new CpLongRecord(((LongEntry) cpInfo).longValue()); + case TAG_DOUBLE -> + new CpDoubleRecord(((DoubleEntry) cpInfo).doubleValue()); + case TAG_CLASS -> + new CpClassRecord(((ClassEntry) cpInfo).asInternalName()); + case TAG_STRING -> + new CpStringRecord(((StringEntry) cpInfo).stringValue()); + case TAG_FIELDREF -> + CpFieldRefRecord.ofFieldRefEntry((FieldRefEntry) cpInfo); + case TAG_METHODREF -> + CpMethodRefRecord.ofMethodRefEntry((MethodRefEntry) cpInfo); + case TAG_INTERFACEMETHODREF -> + CpInterfaceMethodRefRecord.ofInterfaceMethodRefEntry((InterfaceMethodRefEntry) cpInfo); + case TAG_NAMEANDTYPE -> + CpNameAndTypeRecord.ofNameAndTypeEntry((NameAndTypeEntry) cpInfo); + case TAG_METHODHANDLE -> + CpMethodHandleRecord.ofMethodHandleEntry((MethodHandleEntry) cpInfo); + case TAG_METHODTYPE -> + new CpMethodTypeRecord(((MethodTypeEntry) cpInfo).descriptor().stringValue()); + case TAG_CONSTANTDYNAMIC -> + CpConstantDynamicRecord.ofConstantDynamicEntry((ConstantDynamicEntry) cpInfo); + case TAG_INVOKEDYNAMIC -> + CpInvokeDynamicRecord.ofInvokeDynamicEntry((InvokeDynamicEntry) cpInfo); + case TAG_MODULE -> + new CpModuleRecord(((ModuleEntry) cpInfo).name().stringValue()); + case TAG_PACKAGE -> + new CpPackageRecord(((PackageEntry) cpInfo).name().stringValue()); + default -> throw new IllegalArgumentException(Integer.toString(cpInfo.tag())); + }; + } + + default String altOpcode() { + return null; + } + + public record CpUTF8Record(String cpUTF8) implements ConstantPoolEntryRecord {} + + public record CpIntegerRecord(int cpInteger) implements ConstantPoolEntryRecord { + @Override + public String altOpcode() { + return "IPUSH#" + Integer.toHexString(cpInteger & 0xfff); + } + } + + public record CpFloatRecord(float cpFloat) implements ConstantPoolEntryRecord { + @Override + public String altOpcode() { + return cpFloat == 0.0f ? "FCONST_0" : + cpFloat == 1.0f ? "FCONST_1" : + cpFloat == 2.0f ? "FCONST_2" : null; + } + } + + public record CpLongRecord(long cpLong) implements ConstantPoolEntryRecord { + @Override + public String altOpcode() { + return cpLong == 0 ? "LCONST_0" : + cpLong == 1 ? "LCONST_1" : null; + } + } + + public record CpDoubleRecord(double cpDouble) implements ConstantPoolEntryRecord { + @Override + public String altOpcode() { + return cpDouble == 0.0 ? "DCONST_0" : + cpDouble == 1.0 ? "DCONST_1" : null; + } + } + + public record CpClassRecord(String cpClass) implements ConstantPoolEntryRecord {} + + public record CpStringRecord(String cpString) implements ConstantPoolEntryRecord {} + + public record CpFieldRefRecord( + String cpFieldRefClass, + String cpFieldRefName, + String cpFieldRefType) implements ConstantPoolEntryRecord { + + public static CpFieldRefRecord ofFieldRefEntry(FieldRefEntry cpInfo) { + return new CpFieldRefRecord(cpInfo.owner().asInternalName(), cpInfo.nameAndType().name().stringValue(), cpInfo.nameAndType().type().stringValue()); + } + } + + public record CpMethodRefRecord( + String cpMethodRefClass, + String cpMethodRefName, + String cpMethodRefType) implements ConstantPoolEntryRecord { + + public static CpMethodRefRecord ofMethodRefEntry(MethodRefEntry cpInfo) { + return new CpMethodRefRecord(cpInfo.owner().asInternalName(), cpInfo.nameAndType().name().stringValue(), cpInfo.nameAndType().type().stringValue()); + } + } + + public record CpInterfaceMethodRefRecord( + String cpInterfaceMethodRefClass, + String cpInterfaceMethodRefName, + String cpInterfaceMethodRefType) implements ConstantPoolEntryRecord { + + public static CpInterfaceMethodRefRecord ofInterfaceMethodRefEntry(InterfaceMethodRefEntry cpInfo) { + return new CpInterfaceMethodRefRecord(cpInfo.owner().asInternalName(), cpInfo.nameAndType().name().stringValue(), cpInfo.nameAndType().type().stringValue()); + } + } + + public record CpNameAndTypeRecord( + String cpNameAndTypeName, + String cpNameAndTypeType) implements ConstantPoolEntryRecord { + + public static CpNameAndTypeRecord ofNameAndTypeEntry(NameAndTypeEntry cpInfo) { + return new CpNameAndTypeRecord(cpInfo.name().stringValue(), cpInfo.type().stringValue()); + } + } + + public record CpMethodHandleRecord( + ConstantPoolEntryRecord cpHandleReference, + int cpHandleKind) implements ConstantPoolEntryRecord { + + public static CpMethodHandleRecord ofMethodHandleEntry(MethodHandleEntry cpInfo) { + return new CpMethodHandleRecord(ConstantPoolEntryRecord.ofCPEntry(cpInfo.reference()), cpInfo.kind()); + } + } + + public record CpMethodTypeRecord(String cpMethodType) implements ConstantPoolEntryRecord {} + + public record CpConstantDynamicRecord( + String cpConstantDynamicName, + String cpConstantDynamicType) implements ConstantPoolEntryRecord { + + public static CpConstantDynamicRecord ofConstantDynamicEntry(ConstantDynamicEntry cpInfo) { + return new CpConstantDynamicRecord(cpInfo.name().stringValue(), cpInfo.type().stringValue()); + } + } + + public record CpInvokeDynamicRecord( + String cpInvokeDynamicName, + String cpInvokeDynamicType) implements ConstantPoolEntryRecord { + + public static CpInvokeDynamicRecord ofInvokeDynamicEntry(InvokeDynamicEntry cpInfo) { + return new CpInvokeDynamicRecord(cpInfo.name().stringValue(), cpInfo.type().stringValue()); + } + } + + public record CpModuleRecord(String cpModule) implements ConstantPoolEntryRecord {} + + public record CpPackageRecord(String cpPackage) implements ConstantPoolEntryRecord {} + + } + + public interface ElementValueRecord { + + public int tag(); + + public static ElementValueRecord ofElementValue(AnnotationValue ev) { + return switch (ev) { + case AnnotationValue.OfConstant evc -> new EvConstRecord(ev.tag(), ConstantPoolEntryRecord.ofCPEntry(evc.constant())); + case AnnotationValue.OfEnum enumVal -> new EvEnumConstRecord(ev.tag(), enumVal.className().stringValue(), enumVal.constantName().stringValue()); + case AnnotationValue.OfClass classVal -> new EvClassRecord(ev.tag(), classVal.className().stringValue()); + case AnnotationValue.OfAnnotation ann -> new EvAnnotationRecord(ev.tag(), AnnotationRecord.ofAnnotation(ann.annotation())); + case AnnotationValue.OfArray evav -> new EvArrayRecord(ev.tag(), evav.values().stream().map(ElementValueRecord::ofElementValue).toList()); + case null, default -> throw new IllegalArgumentException(ev.getClass().getName()); + }; + } + + public record EvAnnotationRecord( + int tag, + AnnotationRecord annotation) implements ElementValueRecord {} + + public record EvArrayRecord( + int tag, + List values) implements ElementValueRecord {} + + public record EvClassRecord( + int tag, + String classInfo) implements ElementValueRecord {} + + public record EvEnumConstRecord( + int tag, + String typeName, + String constName) implements ElementValueRecord {} + + public record EvConstRecord( + int tag, + ConstantPoolEntryRecord constValue) implements ElementValueRecord { + } + } + + private enum Flags { + PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, SUPER ("SYNCHRONIZED"), VOLATILE ("BRIDGE"), TRANSIENT ("VARARGS"), + NATIVE, INTERFACE, ABSTRACT, STRICT, SYNTHETIC, ANNOTATION, ENUM, MODULE ("MANDATED"); + + private String alt; + + Flags() { + this.alt = name(); + } + + Flags(String alt) { + this.alt = alt; + } + + public static String toString(int flags, boolean methodFlags) { + int i=1; + StringBuilder sb = new StringBuilder(); + for (var cf : values()) { + if ((flags & i) != 0) { + if (sb.length() > 0) sb.append(','); + sb.append(methodFlags ? cf.alt : cf.name()); + } + i <<= 1; + } + return sb.toString(); + } + } + + public static void assertEquals(ClassModel actual, ClassModel expected) { + assertEqualsDeep(ClassRecord.ofClassModel(actual, By_ClassBuilder), + ClassRecord.ofClassModel(expected, By_ClassBuilder)); + } + + public static void assertEqualsDeep(Object actual, Object expected) { + assertEqualsDeep(actual, expected, null, true); + } + + public static void assertEqualsDeep(Object actual, Object expected, String message) { + assertEqualsDeep(actual, expected, message, true); + } + + public static void assertEqualsDeep(Object actual, Object expected, String message, boolean printValues) { + assertEqualsDeepImpl(actual, expected, message == null ? "" : message + " ", "$", printValues); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static void assertEqualsDeepImpl(Object actual, Object expected, String message, String path, boolean printValues) { + if (actual instanceof Record && expected instanceof Record) { + assertEqualsDeepImpl(actual.getClass(), expected.getClass(), message, path, printValues); + for (RecordComponent rc : actual.getClass().getRecordComponents()) { + try { + assertEqualsDeepImpl(rc.getAccessor().invoke(actual), rc.getAccessor().invoke(expected), message, path + "." + rc.getName(), printValues); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new AssertionError(message + ex.getLocalizedMessage(), ex); + } + } + } else if (actual instanceof Map actualMap && expected instanceof Map expectedMap) { + assertEqualsDeepImpl(actualMap.keySet(), expectedMap.keySet(), message, path + "(keys)", printValues); + actualMap.forEach((key, actualValue) -> { + assertEqualsDeepImpl(actualValue, expectedMap.get(key), message, path + "." + key, printValues); + }); + } else if (actual instanceof List actualList && expected instanceof List expectedList) { + assertEqualsDeepImpl(actualList.size(), expectedList.size(), message, path + "(size)", printValues); + IntStream.range(0, actualList.size()).forEach(i -> assertEqualsDeepImpl(actualList.get(i), expectedList.get(i), message, path + "[" + i +"]", printValues)); + } else { + if (actual instanceof Set actualSet && expected instanceof Set expectedSet) { + actual = actualSet.stream().filter(e -> !expectedSet.contains(e)).collect(toSet()); + expected = expectedSet.stream().filter(e -> !actualSet.contains(e)).collect(toSet()); + } + if (!Objects.equals(actual, expected)) { + throw new AssertionError(message + "not equal on path [" + path + "]"); +// + (printValues ? "\nexpected: " + prettyPrintToJson(expected) + "\nbut found: " + prettyPrintToJson(actual) : "")); + } + } + } + +// @Override +// public String toString() { +// return prettyPrintToJson(this); +// } +// +// private static final JsonWriterFactory JWF = Json.createWriterFactory(Map.of(JsonGenerator.PRETTY_PRINTING, true)); +// +// public static String prettyPrintToJson(Object o) { +// var stringWriter = new StringWriter(); +// try ( var jsonWriter = JWF.createWriter(stringWriter)) { +// jsonWriter.write(toJson(o)); +// } +// return stringWriter.toString(); +// } +// +// @SuppressWarnings({"unchecked", "rawtypes"}) +// private static JsonValue toJson(Object o) { +// if (o == null) { +// return JsonValue.NULL; +// } else if (o instanceof Record) { +// var b = Json.createObjectBuilder(); +// for (RecordComponent rc : o.getClass().getRecordComponents()) try { +// var val = rc.getAccessor().invoke(o); +// if (val != null) { +// b.add(rc.getName(), toJson(val)); +// } +// } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } +// return b.build(); +// } +// if (o instanceof Map map) { +// var b = Json.createObjectBuilder(); +// map.forEach((k, v) -> b.add(String.valueOf(k), toJson(v))); +// return b.build(); +// } +// if (o instanceof Collection col) { +// var b = Json.createArrayBuilder(); +// col.forEach(e -> b.add(toJson(e))); +// return b.build(); +// } else if (o instanceof String s) { +// return Json.createValue(s); +// } else if (o instanceof Double d) { +// return Json.createValue(d); +// } else if (o instanceof Integer i) { +// return Json.createValue(i); +// } else if (o instanceof Long l) { +// return Json.createValue(l); +// } else { +// return Json.createValue(o.toString()); +// } +// } + + private interface SupplierThrowingException { + R get() throws Exception; + } + + private static R wrapException(SupplierThrowingException supplier) { + try { + return supplier.get(); + } catch (RuntimeException re) { + throw re; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private interface FunctionThrowingException { + R apply(P p) throws Exception; + } + + private static R map(P value, FunctionThrowingException mapper) { + return map(value, mapper, null); + } + + private static R map(P value, FunctionThrowingException mapper, R defaultReturn) { + try { + return value == null ? defaultReturn : mapper.apply(value); + } catch (RuntimeException re) { + throw re; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static Function wrapException(FunctionThrowingException function) { + return p -> { + try { + return function.apply(p); + } catch (RuntimeException re) { + throw re; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }; + } +} diff --git a/test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.java b/test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.java new file mode 100644 index 0000000000000..b75b4c39d059e --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.instruction.*; + +public class InstructionModelToCodeBuilder { + + public static void toBuilder(CodeElement model, CodeBuilder cb) { + switch (model) { + case LoadInstruction im -> + cb.loadInstruction(im.typeKind(), im.slot()); + case StoreInstruction im -> + cb.storeInstruction(im.typeKind(), im.slot()); + case IncrementInstruction im -> + cb.incrementInstruction(im.slot(), im.constant()); + case BranchInstruction im -> + cb.branchInstruction(im.opcode(), im.target()); + case LookupSwitchInstruction im -> + cb.lookupSwitchInstruction(im.defaultTarget(), im.cases()); + case TableSwitchInstruction im -> + cb.tableSwitchInstruction(im.lowValue(), im.highValue(), im.defaultTarget(), im.cases()); + case ReturnInstruction im -> + cb.returnInstruction(im.typeKind()); + case ThrowInstruction im -> + cb.throwInstruction(); + case FieldInstruction im -> + cb.fieldInstruction(im.opcode(), im.owner().asSymbol(), im.name().stringValue(), im.typeSymbol()); + case InvokeInstruction im -> + cb.invokeInstruction(im.opcode(), im.owner().asSymbol(), im.name().stringValue(), im.typeSymbol(), im.isInterface()); + case InvokeDynamicInstruction im -> + cb.invokeDynamicInstruction(DynamicCallSiteDesc.of(im.bootstrapMethod(), im.name().stringValue(), MethodTypeDesc.ofDescriptor(im.type().stringValue()), im.bootstrapArgs().toArray(ConstantDesc[]::new))); + case NewObjectInstruction im -> + cb.newObjectInstruction(im.className().asSymbol()); + case NewPrimitiveArrayInstruction im -> + cb.newPrimitiveArrayInstruction(im.typeKind()); + case NewReferenceArrayInstruction im -> + cb.newReferenceArrayInstruction(im.componentType()); + case NewMultiArrayInstruction im -> + cb.newMultidimensionalArrayInstruction(im.dimensions(), im.arrayType()); + case TypeCheckInstruction im -> + cb.typeCheckInstruction(im.opcode(), im.type().asSymbol()); + case ArrayLoadInstruction im -> + cb.arrayLoadInstruction(im.typeKind()); + case ArrayStoreInstruction im -> + cb.arrayStoreInstruction(im.typeKind()); + case StackInstruction im -> + cb.stackInstruction(im.opcode()); + case ConvertInstruction im -> + cb.convertInstruction(im.fromType(), im.toType()); + case OperatorInstruction im -> + cb.operatorInstruction(im.opcode()); + case ConstantInstruction im -> + cb.constantInstruction(im.opcode(), im.constantValue()); + case MonitorInstruction im -> + cb.monitorInstruction(im.opcode()); + case NopInstruction im -> + cb.nopInstruction(); + case LabelTarget im -> + cb.labelBinding(im.label()); + case ExceptionCatch im -> + cb.exceptionCatch(im.tryStart(), im.tryEnd(), im.handler(), im.catchType()); + default -> + throw new IllegalArgumentException("not yet implemented: " + model); + } + } +} diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java new file mode 100644 index 0000000000000..cd27c2ade602c --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import java.lang.constant.ClassDesc; +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import jdk.internal.classfile.*; +import jdk.internal.classfile.attribute.*; +import jdk.internal.classfile.constantpool.*; +import jdk.internal.classfile.instruction.*; +import jdk.internal.classfile.java.lang.constant.ModuleDesc; +import jdk.internal.classfile.java.lang.constant.PackageDesc; +import jdk.internal.classfile.components.CodeStackTracker; + +class RebuildingTransformation { + + static private Random pathSwitch = new Random(1234); + + static byte[] transform(ClassModel clm) { + return Classfile.build(clm.thisClass().asSymbol(), List.of(Classfile.Option.generateStackmap(false)), clb -> { + for (var cle : clm) { + switch (cle) { + case AccessFlags af -> clb.withFlags(af.flagsMask()); + case Superclass sc -> clb.withSuperclass(sc.superclassEntry().asSymbol()); + case Interfaces i -> clb.withInterfaceSymbols(i.interfaces().stream().map(ClassEntry::asSymbol).toArray(ClassDesc[]::new)); + case ClassfileVersion v -> clb.withVersion(v.majorVersion(), v.minorVersion()); + case FieldModel fm -> + clb.withField(fm.fieldName().stringValue(), fm.fieldTypeSymbol(), fb -> { + for (var fe : fm) { + switch (fe) { + case AccessFlags af -> fb.withFlags(af.flagsMask()); + case ConstantValueAttribute a -> fb.with(ConstantValueAttribute.of(a.constant().constantValue())); + case DeprecatedAttribute a -> fb.with(DeprecatedAttribute.of()); + case RuntimeInvisibleAnnotationsAttribute a -> fb.with(RuntimeInvisibleAnnotationsAttribute.of(transformAnnotations(a.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute a -> fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); + case RuntimeVisibleAnnotationsAttribute a -> fb.with(RuntimeVisibleAnnotationsAttribute.of(transformAnnotations(a.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute a -> fb.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); + case SignatureAttribute a -> fb.with(SignatureAttribute.of(Signature.parseFrom(a.asTypeSignature().signatureString()))); + case SyntheticAttribute a -> fb.with(SyntheticAttribute.of()); + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); + case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName()); + } + } + }); + case MethodModel mm -> { + clb.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), mb -> { + for (var me : mm) { + switch (me) { + case AccessFlags af -> mb.withFlags(af.flagsMask()); + case CodeModel com -> mb.withCode(cb -> cb.transforming(CodeStackTracker.of(), cob -> { + var labels = new HashMap(); + for (var coe : com) { + switch (coe) { + case ArrayLoadInstruction i -> { + switch (i.typeKind()) { + case ByteType -> cob.baload(); + case ShortType -> cob.saload(); + case IntType -> cob.iaload(); + case FloatType -> cob.faload(); + case LongType -> cob.laload(); + case DoubleType -> cob.daload(); + case ReferenceType -> cob.aaload(); + case CharType -> cob.caload(); + default -> throw new AssertionError("Should not reach here"); + } + } + case ArrayStoreInstruction i -> { + switch (i.typeKind()) { + case ByteType -> cob.bastore(); + case ShortType -> cob.sastore(); + case IntType -> cob.iastore(); + case FloatType -> cob.fastore(); + case LongType -> cob.lastore(); + case DoubleType -> cob.dastore(); + case ReferenceType -> cob.aastore(); + case CharType -> cob.castore(); + default -> throw new AssertionError("Should not reach here"); + } + } + case BranchInstruction i -> { + var target = labels.computeIfAbsent(i.target(), l -> cob.newLabel()); + switch (i.opcode()) { + case GOTO -> cob.goto_(target); + case GOTO_W -> cob.goto_w(target); + case IF_ACMPEQ -> cob.if_acmpeq(target); + case IF_ACMPNE -> cob.if_acmpne(target); + case IF_ICMPEQ -> cob.if_icmpeq(target); + case IF_ICMPGE -> cob.if_icmpge(target); + case IF_ICMPGT -> cob.if_icmpgt(target); + case IF_ICMPLE -> cob.if_icmple(target); + case IF_ICMPLT -> cob.if_icmplt(target); + case IF_ICMPNE -> cob.if_icmpne(target); + case IFNONNULL -> cob.if_nonnull(target); + case IFNULL -> cob.if_null(target); + case IFEQ -> cob.ifeq(target); + case IFGE -> cob.ifge(target); + case IFGT -> cob.ifgt(target); + case IFLE -> cob.ifle(target); + case IFLT -> cob.iflt(target); + case IFNE -> cob.ifne(target); + default -> throw new AssertionError("Should not reach here"); + } + } + case ConstantInstruction i -> { + if (i.constantValue() == null) + if (pathSwitch.nextBoolean()) cob.aconst_null(); + else cob.constantInstruction(null); + else switch (i.constantValue()) { + case Integer iVal -> { + if (iVal == 1 && pathSwitch.nextBoolean()) cob.iconst_1(); + else if (iVal == 2 && pathSwitch.nextBoolean()) cob.iconst_2(); + else if (iVal == 3 && pathSwitch.nextBoolean()) cob.iconst_3(); + else if (iVal == 4 && pathSwitch.nextBoolean()) cob.iconst_4(); + else if (iVal == 5 && pathSwitch.nextBoolean()) cob.iconst_5(); + else if (iVal == -1 && pathSwitch.nextBoolean()) cob.iconst_m1(); + else if (iVal >= -128 && iVal <= 127 && pathSwitch.nextBoolean()) cob.bipush(iVal); + else if (iVal >= -32768 && iVal <= 32767 && pathSwitch.nextBoolean()) cob.sipush(iVal); + else cob.constantInstruction(iVal); + } + case Long lVal -> { + if (lVal == 0 && pathSwitch.nextBoolean()) cob.lconst_0(); + else if (lVal == 1 && pathSwitch.nextBoolean()) cob.lconst_1(); + else cob.constantInstruction(lVal); + } + case Float fVal -> { + if (fVal == 0.0 && pathSwitch.nextBoolean()) cob.fconst_0(); + else if (fVal == 1.0 && pathSwitch.nextBoolean()) cob.fconst_1(); + else if (fVal == 2.0 && pathSwitch.nextBoolean()) cob.fconst_2(); + else cob.constantInstruction(fVal); + } + case Double dVal -> { + if (dVal == 0.0d && pathSwitch.nextBoolean()) cob.dconst_0(); + else if (dVal == 1.0d && pathSwitch.nextBoolean()) cob.dconst_1(); + else cob.constantInstruction(dVal); + } + default -> cob.constantInstruction(i.constantValue()); + } + } + case ConvertInstruction i -> { + switch (i.fromType()) { + case DoubleType -> { + switch (i.toType()) { + case FloatType -> cob.d2f(); + case IntType -> cob.d2i(); + case LongType -> cob.d2l(); + default -> throw new AssertionError("Should not reach here"); + } + } + case FloatType -> { + switch (i.toType()) { + case DoubleType -> cob.f2d(); + case IntType -> cob.f2i(); + case LongType -> cob.f2l(); + default -> throw new AssertionError("Should not reach here"); + } + } + case IntType -> { + switch (i.toType()) { + case ByteType -> cob.i2b(); + case CharType -> cob.i2c(); + case DoubleType -> cob.i2d(); + case FloatType -> cob.i2f(); + case LongType -> cob.i2l(); + case ShortType -> cob.i2s(); + default -> throw new AssertionError("Should not reach here"); + } + } + case LongType -> { + switch (i.toType()) { + case DoubleType -> cob.l2d(); + case FloatType -> cob.l2f(); + case IntType -> cob.l2i(); + default -> throw new AssertionError("Should not reach here"); + } + } + default -> throw new AssertionError("Should not reach here"); + } + } + case FieldInstruction i -> { + if (pathSwitch.nextBoolean()) { + switch (i.opcode()) { + case GETFIELD -> cob.getfield(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case GETSTATIC -> cob.getstatic(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case PUTFIELD -> cob.putfield(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case PUTSTATIC -> cob.putstatic(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + default -> throw new AssertionError("Should not reach here"); + } + } else { + switch (i.opcode()) { + case GETFIELD -> cob.getfield(i.field()); + case GETSTATIC -> cob.getstatic(i.field()); + case PUTFIELD -> cob.putfield(i.field()); + case PUTSTATIC -> cob.putstatic(i.field()); + default -> throw new AssertionError("Should not reach here"); + } + } + } + case InvokeDynamicInstruction i -> { + if (pathSwitch.nextBoolean()) cob.invokedynamic(i.invokedynamic().asSymbol()); + else cob.invokedynamic(i.invokedynamic()); + } + case InvokeInstruction i -> { + if (pathSwitch.nextBoolean()) { + if (i.isInterface()) { + switch (i.opcode()) { + case INVOKEINTERFACE -> cob.invokeinterface(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case INVOKESPECIAL -> cob.invokespecial(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol(), true); + case INVOKESTATIC -> cob.invokestatic(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol(), true); + default -> throw new AssertionError("Should not reach here"); + } + } else { + switch (i.opcode()) { + case INVOKESPECIAL -> cob.invokespecial(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case INVOKESTATIC -> cob.invokestatic(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case INVOKEVIRTUAL -> cob.invokevirtual(i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + default -> throw new AssertionError("Should not reach here"); + } + } + } else { + switch (i.method()) { + case InterfaceMethodRefEntry en -> { + switch (i.opcode()) { + case INVOKEINTERFACE -> cob.invokeinterface(en); + case INVOKESPECIAL -> cob.invokespecial(en); + case INVOKESTATIC -> cob.invokestatic(en); + default -> throw new AssertionError("Should not reach here"); + } + } + case MethodRefEntry en -> { + switch (i.opcode()) { + case INVOKESPECIAL -> cob.invokespecial(en); + case INVOKESTATIC -> cob.invokestatic(en); + case INVOKEVIRTUAL -> cob.invokevirtual(en); + default -> throw new AssertionError("Should not reach here"); + } + } + default -> throw new AssertionError("Should not reach here"); + } + } + } + case LoadInstruction i -> { + switch (i.typeKind()) { + case IntType -> cob.iload(i.slot()); + case FloatType -> cob.fload(i.slot()); + case LongType -> cob.lload(i.slot()); + case DoubleType -> cob.dload(i.slot()); + case ReferenceType -> cob.aload(i.slot()); + default -> throw new AssertionError("Should not reach here"); + } + } + case StoreInstruction i -> { + switch (i.typeKind()) { + case IntType -> cob.istore(i.slot()); + case FloatType -> cob.fstore(i.slot()); + case LongType -> cob.lstore(i.slot()); + case DoubleType -> cob.dstore(i.slot()); + case ReferenceType -> cob.astore(i.slot()); + default -> throw new AssertionError("Should not reach here"); + } + } + case IncrementInstruction i -> + cob.iinc(i.slot(), i.constant()); + case LookupSwitchInstruction i -> + cob.lookupswitch(labels.computeIfAbsent(i.defaultTarget(), l -> cob.newLabel()), + i.cases().stream().map(sc -> + SwitchCase.of(sc.caseValue(), labels.computeIfAbsent(sc.target(), l -> cob.newLabel()))).toList()); + case MonitorInstruction i -> { + switch (i.opcode()) { + case MONITORENTER -> cob.monitorenter(); + case MONITOREXIT -> cob.monitorexit(); + default -> throw new AssertionError("Should not reach here"); + } + } + case NewMultiArrayInstruction i -> { + if (pathSwitch.nextBoolean()) { + cob.multianewarray(i.arrayType().asSymbol(), i.dimensions()); + } else { + cob.multianewarray(i.arrayType(), i.dimensions()); + } + } + case NewObjectInstruction i -> { + if (pathSwitch.nextBoolean()) { + cob.new_(i.className().asSymbol()); + } else { + cob.new_(i.className()); + } + } + case NewPrimitiveArrayInstruction i -> + cob.newarray(i.typeKind()); + case NewReferenceArrayInstruction i -> { + if (pathSwitch.nextBoolean()) { + cob.anewarray(i.componentType().asSymbol()); + } else { + cob.anewarray(i.componentType()); + } + } + case NopInstruction i -> + cob.nop(); + case OperatorInstruction i -> { + switch (i.opcode()) { + case IADD -> cob.iadd(); + case LADD -> cob.ladd(); + case FADD -> cob.fadd(); + case DADD -> cob.dadd(); + case ISUB -> cob.isub(); + case LSUB -> cob.lsub(); + case FSUB -> cob.fsub(); + case DSUB -> cob.dsub(); + case IMUL -> cob.imul(); + case LMUL -> cob.lmul(); + case FMUL -> cob.fmul(); + case DMUL -> cob.dmul(); + case IDIV -> cob.idiv(); + case LDIV -> cob.ldiv(); + case FDIV -> cob.fdiv(); + case DDIV -> cob.ddiv(); + case IREM -> cob.irem(); + case LREM -> cob.lrem(); + case FREM -> cob.frem(); + case DREM -> cob.drem(); + case INEG -> cob.ineg(); + case LNEG -> cob.lneg(); + case FNEG -> cob.fneg(); + case DNEG -> cob.dneg(); + case ISHL -> cob.ishl(); + case LSHL -> cob.lshl(); + case ISHR -> cob.ishr(); + case LSHR -> cob.lshr(); + case IUSHR -> cob.iushr(); + case LUSHR -> cob.lushr(); + case IAND -> cob.iand(); + case LAND -> cob.land(); + case IOR -> cob.ior(); + case LOR -> cob.lor(); + case IXOR -> cob.ixor(); + case LXOR -> cob.lxor(); + case LCMP -> cob.lcmp(); + case FCMPL -> cob.fcmpl(); + case FCMPG -> cob.fcmpg(); + case DCMPL -> cob.dcmpl(); + case DCMPG -> cob.dcmpg(); + case ARRAYLENGTH -> cob.arraylength(); + default -> throw new AssertionError("Should not reach here"); + } + } + case ReturnInstruction i -> { + switch (i.typeKind()) { + case IntType -> cob.ireturn(); + case FloatType -> cob.freturn(); + case LongType -> cob.lreturn(); + case DoubleType -> cob.dreturn(); + case ReferenceType -> cob.areturn(); + case VoidType -> cob.return_(); + default -> throw new AssertionError("Should not reach here"); + } + } + case StackInstruction i -> { + switch (i.opcode()) { + case POP -> cob.pop(); + case POP2 -> cob.pop2(); + case DUP -> cob.dup(); + case DUP_X1 -> cob.dup_x1(); + case DUP_X2 -> cob.dup_x2(); + case DUP2 -> cob.dup2(); + case DUP2_X1 -> cob.dup2_x1(); + case DUP2_X2 -> cob.dup2_x2(); + case SWAP -> cob.swap(); + default -> throw new AssertionError("Should not reach here"); + } + } + case TableSwitchInstruction i -> + cob.tableswitch(i.lowValue(), i.highValue(), + labels.computeIfAbsent(i.defaultTarget(), l -> cob.newLabel()), + i.cases().stream().map(sc -> + SwitchCase.of(sc.caseValue(), labels.computeIfAbsent(sc.target(), l -> cob.newLabel()))).toList()); + case ThrowInstruction i -> cob.athrow(); + case TypeCheckInstruction i -> { + if (pathSwitch.nextBoolean()) { + switch (i.opcode()) { + case CHECKCAST -> cob.checkcast(i.type().asSymbol()); + case INSTANCEOF -> cob.instanceof_(i.type().asSymbol()); + default -> throw new AssertionError("Should not reach here"); + } + } else { + switch (i.opcode()) { + case CHECKCAST -> cob.checkcast(i.type()); + case INSTANCEOF -> cob.instanceof_(i.type()); + default -> throw new AssertionError("Should not reach here"); + } + } + } + case CharacterRange pi -> + cob.characterRange(labels.computeIfAbsent(pi.startScope(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.endScope(), l -> cob.newLabel()), + pi.characterRangeStart(), pi.characterRangeEnd(), pi.flags()); + case ExceptionCatch pi -> + pi.catchType().ifPresentOrElse( + catchType -> cob.exceptionCatch(labels.computeIfAbsent(pi.tryStart(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.tryEnd(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.handler(), l -> cob.newLabel()), + catchType.asSymbol()), + () -> cob.exceptionCatchAll(labels.computeIfAbsent(pi.tryStart(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.tryEnd(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.handler(), l -> cob.newLabel()))); + case LabelTarget pi -> + cob.labelBinding(labels.computeIfAbsent(pi.label(), l -> cob.newLabel())); + case LineNumber pi -> + cob.lineNumber(pi.line()); + case LocalVariable pi -> + cob.localVariable(pi.slot(), pi.name().stringValue(), pi.typeSymbol(), + labels.computeIfAbsent(pi.startScope(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.endScope(), l -> cob.newLabel())); + case LocalVariableType pi -> + cob.localVariableType(pi.slot(), pi.name().stringValue(), + Signature.parseFrom(pi.signatureSymbol().signatureString()), + labels.computeIfAbsent(pi.startScope(), l -> cob.newLabel()), + labels.computeIfAbsent(pi.endScope(), l -> cob.newLabel())); + case RuntimeInvisibleTypeAnnotationsAttribute a -> + cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), cob, labels))); + case RuntimeVisibleTypeAnnotationsAttribute a -> + cob.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), cob, labels))); + case StackMapTableAttribute a -> + throw new AssertionError("Unexpected StackMapTableAttribute here"); + case CustomAttribute a -> + throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); + } + } + com.findAttribute(Attributes.STACK_MAP_TABLE).ifPresent(smta -> + cob.with(StackMapTableAttribute.of(smta.entries().stream().map(fr -> + StackMapFrameInfo.of(labels.computeIfAbsent(fr.target(), l -> cob.newLabel()), + transformFrameTypeInfos(fr.locals(), cob, labels), + transformFrameTypeInfos(fr.stack(), cob, labels))).toList()))); + })); + case AnnotationDefaultAttribute a -> mb.with(AnnotationDefaultAttribute.of(transformAnnotationValue(a.defaultValue()))); + case DeprecatedAttribute a -> mb.with(DeprecatedAttribute.of()); + case ExceptionsAttribute a -> mb.with(ExceptionsAttribute.ofSymbols(a.exceptions().stream().map(ClassEntry::asSymbol).toArray(ClassDesc[]::new))); + case MethodParametersAttribute a -> mb.with(MethodParametersAttribute.of(a.parameters().stream().map(mp -> + MethodParameterInfo.ofParameter(mp.name().map(Utf8Entry::stringValue), mp.flagsMask())).toArray(MethodParameterInfo[]::new))); + case RuntimeInvisibleAnnotationsAttribute a -> mb.with(RuntimeInvisibleAnnotationsAttribute.of(transformAnnotations(a.annotations()))); + case RuntimeInvisibleParameterAnnotationsAttribute a -> mb.with(RuntimeInvisibleParameterAnnotationsAttribute.of(a.parameterAnnotations().stream().map(pas -> List.of(transformAnnotations(pas))).toList())); + case RuntimeInvisibleTypeAnnotationsAttribute a -> mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); + case RuntimeVisibleAnnotationsAttribute a -> mb.with(RuntimeVisibleAnnotationsAttribute.of(transformAnnotations(a.annotations()))); + case RuntimeVisibleParameterAnnotationsAttribute a -> mb.with(RuntimeVisibleParameterAnnotationsAttribute.of(a.parameterAnnotations().stream().map(pas -> List.of(transformAnnotations(pas))).toList())); + case RuntimeVisibleTypeAnnotationsAttribute a -> mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); + case SignatureAttribute a -> mb.with(SignatureAttribute.of(MethodSignature.parseFrom(a.asMethodSignature().signatureString()))); + case SyntheticAttribute a -> mb.with(SyntheticAttribute.of()); + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); + case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName()); + } + } + }); + } + case CompilationIDAttribute a -> clb.with(CompilationIDAttribute.of(a.compilationId().stringValue())); + case DeprecatedAttribute a -> clb.with(DeprecatedAttribute.of()); + case EnclosingMethodAttribute a -> clb.with(EnclosingMethodAttribute.of(a.enclosingClass().asSymbol(), a.enclosingMethodName().map(Utf8Entry::stringValue), a.enclosingMethodTypeSymbol())); + case InnerClassesAttribute a -> clb.with(InnerClassesAttribute.of(a.classes().stream().map(ici -> InnerClassInfo.of( + ici.innerClass().asSymbol(), + ici.outerClass().map(ClassEntry::asSymbol), + ici.innerName().map(Utf8Entry::stringValue), + ici.flagsMask())).toArray(InnerClassInfo[]::new))); + case ModuleAttribute a -> clb.with(ModuleAttribute.of(a.moduleName().asSymbol(), mob -> { + mob.moduleFlags(a.moduleFlagsMask()); + a.moduleVersion().ifPresent(v -> mob.moduleVersion(v.stringValue())); + for (var req : a.requires()) mob.requires(req.requires().asSymbol(), req.requiresFlagsMask(), req.requiresVersion().map(Utf8Entry::stringValue).orElse(null)); + for (var exp : a.exports()) mob.exports(exp.exportedPackage().asSymbol(), exp.exportsFlagsMask(), exp.exportsTo().stream().map(ModuleEntry::asSymbol).toArray(ModuleDesc[]::new)); + for (var opn : a.opens()) mob.opens(opn.openedPackage().asSymbol(), opn.opensFlagsMask(), opn.opensTo().stream().map(ModuleEntry::asSymbol).toArray(ModuleDesc[]::new)); + for (var use : a.uses()) mob.uses(use.asSymbol()); + for (var prov : a.provides()) mob.provides(prov.provides().asSymbol(), prov.providesWith().stream().map(ClassEntry::asSymbol).toArray(ClassDesc[]::new)); + })); + case ModuleHashesAttribute a -> clb.with(ModuleHashesAttribute.of(a.algorithm().stringValue(), + a.hashes().stream().map(mh -> ModuleHashInfo.of(mh.moduleName().asSymbol(), mh.hash())).toArray(ModuleHashInfo[]::new))); + case ModuleMainClassAttribute a -> clb.with(ModuleMainClassAttribute.of(a.mainClass().asSymbol())); + case ModulePackagesAttribute a -> clb.with(ModulePackagesAttribute.ofNames(a.packages().stream().map(PackageEntry::asSymbol).toArray(PackageDesc[]::new))); + case ModuleResolutionAttribute a -> clb.with(ModuleResolutionAttribute.of(a.resolutionFlags())); + case ModuleTargetAttribute a -> clb.with(ModuleTargetAttribute.of(a.targetPlatform().stringValue())); + case NestHostAttribute a -> clb.with(NestHostAttribute.of(a.nestHost().asSymbol())); + case NestMembersAttribute a -> clb.with(NestMembersAttribute.ofSymbols(a.nestMembers().stream().map(ClassEntry::asSymbol).toArray(ClassDesc[]::new))); + case PermittedSubclassesAttribute a -> clb.with(PermittedSubclassesAttribute.ofSymbols(a.permittedSubclasses().stream().map(ClassEntry::asSymbol).toArray(ClassDesc[]::new))); + case RecordAttribute a -> clb.with(RecordAttribute.of(a.components().stream().map(rci -> + RecordComponentInfo.of(rci.name().stringValue(), rci.descriptorSymbol(), rci.attributes().stream().mapMulti((rca, rcac) -> { + switch(rca) { + case RuntimeInvisibleAnnotationsAttribute riaa -> rcac.accept(RuntimeInvisibleAnnotationsAttribute.of(transformAnnotations(riaa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> rcac.accept(RuntimeInvisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(ritaa.annotations(), null, null))); + case RuntimeVisibleAnnotationsAttribute rvaa -> rcac.accept(RuntimeVisibleAnnotationsAttribute.of(transformAnnotations(rvaa.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> rcac.accept(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(rvtaa.annotations(), null, null))); + case SignatureAttribute sa -> rcac.accept(SignatureAttribute.of(Signature.parseFrom(sa.asTypeSignature().signatureString()))); + default -> throw new AssertionError("Unexpected record component attribute: " + rca.attributeName()); + }}).toArray(Attribute[]::new))).toArray(RecordComponentInfo[]::new))); + case RuntimeInvisibleAnnotationsAttribute a -> clb.with(RuntimeInvisibleAnnotationsAttribute.of(transformAnnotations(a.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute a -> clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); + case RuntimeVisibleAnnotationsAttribute a -> clb.with(RuntimeVisibleAnnotationsAttribute.of(transformAnnotations(a.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute a -> clb.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); + case SignatureAttribute a -> clb.with(SignatureAttribute.of(ClassSignature.parseFrom(a.asClassSignature().signatureString()))); + case SourceDebugExtensionAttribute a -> clb.with(SourceDebugExtensionAttribute.of(a.contents())); + case SourceFileAttribute a -> clb.with(SourceFileAttribute.of(a.sourceFile().stringValue())); + case SourceIDAttribute a -> clb.with(SourceIDAttribute.of(a.sourceId().stringValue())); + case SyntheticAttribute a -> clb.with(SyntheticAttribute.of()); + case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); + case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName()); + } + } + }); + } + + static Annotation[] transformAnnotations(List annotations) { + return annotations.stream().map(a -> transformAnnotation(a)).toArray(Annotation[]::new); + } + + static Annotation transformAnnotation(Annotation a) { + return Annotation.of(a.classSymbol(), a.elements().stream().map(ae -> AnnotationElement.of(ae.name().stringValue(), transformAnnotationValue(ae.value()))).toArray(AnnotationElement[]::new)); + } + + static AnnotationValue transformAnnotationValue(AnnotationValue av) { + return switch (av) { + case AnnotationValue.OfAnnotation oa -> AnnotationValue.ofAnnotation(transformAnnotation(oa.annotation())); + case AnnotationValue.OfArray oa -> AnnotationValue.ofArray(oa.values().stream().map(v -> transformAnnotationValue(v)).toArray(AnnotationValue[]::new)); + case AnnotationValue.OfString v -> AnnotationValue.of(v.stringValue()); + case AnnotationValue.OfDouble v -> AnnotationValue.of(v.doubleValue()); + case AnnotationValue.OfFloat v -> AnnotationValue.of(v.floatValue()); + case AnnotationValue.OfLong v -> AnnotationValue.of(v.longValue()); + case AnnotationValue.OfInteger v -> AnnotationValue.of(v.intValue()); + case AnnotationValue.OfShort v -> AnnotationValue.of(v.shortValue()); + case AnnotationValue.OfCharacter v -> AnnotationValue.of(v.charValue()); + case AnnotationValue.OfByte v -> AnnotationValue.of(v.byteValue()); + case AnnotationValue.OfBoolean v -> AnnotationValue.of(v.booleanValue()); + case AnnotationValue.OfClass oc -> AnnotationValue.of(oc.classSymbol()); + case AnnotationValue.OfEnum oe -> AnnotationValue.ofEnum(oe.classSymbol(), oe.constantName().stringValue()); + }; + } + + static TypeAnnotation[] transformTypeAnnotations(List annotations, CodeBuilder cob, HashMap labels) { + return annotations.stream().map(ta -> TypeAnnotation.of( + transformTargetInfo(ta.targetInfo(), cob, labels), + ta.targetPath().stream().map(tpc -> TypeAnnotation.TypePathComponent.of(tpc.typePathKind(), tpc.typeArgumentIndex())).toList(), + ta.classSymbol(), + ta.elements().stream().map(ae -> AnnotationElement.of(ae.name().stringValue(), transformAnnotationValue(ae.value()))).toList())).toArray(TypeAnnotation[]::new); + } + + static TypeAnnotation.TargetInfo transformTargetInfo(TypeAnnotation.TargetInfo ti, CodeBuilder cob, HashMap labels) { + return switch (ti) { + case TypeAnnotation.CatchTarget t -> TypeAnnotation.TargetInfo.ofExceptionParameter(t.exceptionTableIndex()); + case TypeAnnotation.EmptyTarget t -> TypeAnnotation.TargetInfo.of(t.targetType()); + case TypeAnnotation.FormalParameterTarget t -> TypeAnnotation.TargetInfo.ofMethodFormalParameter(t.formalParameterIndex()); + case TypeAnnotation.SupertypeTarget t -> TypeAnnotation.TargetInfo.ofClassExtends(t.supertypeIndex()); + case TypeAnnotation.ThrowsTarget t -> TypeAnnotation.TargetInfo.ofThrows(t.throwsTargetIndex()); + case TypeAnnotation.TypeParameterBoundTarget t -> TypeAnnotation.TargetInfo.ofTypeParameterBound(t.targetType(), t.typeParameterIndex(), t.boundIndex()); + case TypeAnnotation.TypeParameterTarget t -> TypeAnnotation.TargetInfo.ofTypeParameter(t.targetType(), t.typeParameterIndex()); + case TypeAnnotation.LocalVarTarget t -> TypeAnnotation.TargetInfo.ofVariable(t.targetType(), t.table().stream().map(lvti -> + TypeAnnotation.LocalVarTargetInfo.of(labels.computeIfAbsent(lvti.startLabel(), l -> cob.newLabel()), + labels.computeIfAbsent(lvti.endLabel(), l -> cob.newLabel()), lvti.index())).toList()); + case TypeAnnotation.OffsetTarget t -> TypeAnnotation.TargetInfo.ofOffset(t.targetType(), labels.computeIfAbsent(t.target(), l -> cob.newLabel())); + case TypeAnnotation.TypeArgumentTarget t -> TypeAnnotation.TargetInfo.ofTypeArgument(t.targetType(), + labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); + }; + } + + static List transformFrameTypeInfos(List infos, CodeBuilder cob, HashMap labels) { + return infos.stream().map(ti -> { + return switch (ti) { + case StackMapFrameInfo.SimpleVerificationTypeInfo i -> i; + case StackMapFrameInfo.ObjectVerificationTypeInfo i -> StackMapFrameInfo.ObjectVerificationTypeInfo.of(i.classSymbol()); + case StackMapFrameInfo.UninitializedVerificationTypeInfo i -> StackMapFrameInfo.UninitializedVerificationTypeInfo.of(labels.computeIfAbsent(i.newTarget(), l -> cob.newLabel())); + }; + }).toList(); + } +} diff --git a/test/jdk/jdk/classfile/helpers/TestConstants.java b/test/jdk/jdk/classfile/helpers/TestConstants.java new file mode 100644 index 0000000000000..a726607033af1 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/TestConstants.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; + +/** + * TestConstants + */ +public class TestConstants { + public static final ClassDesc CD_System = ClassDesc.of("java.lang.System"); + public static final ClassDesc CD_PrintStream = ClassDesc.of("java.io.PrintStream"); + public static final ClassDesc CD_ArrayList = ClassDesc.of("java.util.ArrayList"); + + public static final MethodTypeDesc MTD_INT_VOID = MethodTypeDesc.ofDescriptor("(I)V"); + public static final MethodTypeDesc MTD_VOID = MethodTypeDesc.ofDescriptor("()V"); +} diff --git a/test/jdk/jdk/classfile/helpers/TestUtil.java b/test/jdk/jdk/classfile/helpers/TestUtil.java new file mode 100644 index 0000000000000..f690a72f6d4a4 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/TestUtil.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import jdk.internal.classfile.impl.LabelContext; +import jdk.internal.classfile.impl.LabelImpl; +import jdk.internal.classfile.instruction.LocalVariable; +import jdk.internal.classfile.instruction.LocalVariableType; + +import java.io.FileOutputStream; +import java.util.Collection; + +public class TestUtil { + + public static void assertEmpty(Collection col) { + if (!col.isEmpty()) throw new AssertionError(col); + } + + public static void writeClass(byte[] bytes, String fn) { + try { + FileOutputStream out = new FileOutputStream(fn); + out.write(bytes); + out.close(); + } catch (Exception ex) { + throw new InternalError(ex); + } + } + + + public static class ExpectedLvRecord { + int slot; + String desc; + String name; + int start; + int length; + + ExpectedLvRecord(int slot, String name, String desc, int start, int length) { + this.slot = slot; + this.name = name; + this.desc = desc; + this.start = start; + this.length = length; + } + + @Override + public boolean equals(Object other) { + if (other instanceof LocalVariable l) { + LabelContext ctx = ((LabelImpl) l.startScope()).labelContext(); + if (!(slot == l.slot() && + desc.equals(l.type().stringValue()) && + name.equals(l.name().stringValue()) && + ctx.labelToBci(l.startScope()) == start && + ctx.labelToBci(l.endScope()) - start == length)) throw new RuntimeException(l.slot() + " " + l.name().stringValue() + " " + l.type().stringValue() + " " + ctx.labelToBci(l.startScope()) + " " + (ctx.labelToBci(l.endScope()) - start)); + return slot == l.slot() && + desc.equals(l.type().stringValue()) && + name.equals(l.name().stringValue()) && + ctx.labelToBci(l.startScope()) == start && + ctx.labelToBci(l.endScope()) - start == length; + } + + throw new RuntimeException(other.toString()); +// return false; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + public static ExpectedLvRecord of(int slot, String name, String desc, int start, int length) { + return new ExpectedLvRecord(slot, name, desc, start, length); + } + } + + public static class ExpectedLvtRecord { + int slot; + String signature; + String name; + int start; + int length; + + ExpectedLvtRecord(int slot, String name, String signature, int start, int length) { + this.slot = slot; + this.name = name; + this.signature = signature; + this.start = start; + this.length = length; + } + + @Override + public boolean equals(Object other) { + if (other instanceof LocalVariableType l) { + LabelContext ctx = ((LabelImpl) l.startScope()).labelContext(); + return slot == l.slot() && + signature.equals(l.signature().stringValue()) && + name.equals(l.name().stringValue()) && + ctx.labelToBci(l.startScope()) == start && + ctx.labelToBci(l.endScope()) - start == length; + } + + return false; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + public static ExpectedLvtRecord of(int slot, String name, String signature, int start, int length) { + return new ExpectedLvtRecord(slot, name, signature, start, length); + } + + public String toString() { + return "LocalVariableType[slot=" +slot + ", name=" + name + ", sig=" + signature +", start=" + start + ", length=" + length +"]"; + } + } +} diff --git a/test/jdk/jdk/classfile/helpers/Transforms.java b/test/jdk/jdk/classfile/helpers/Transforms.java new file mode 100644 index 0000000000000..7dc043acdd8b0 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/Transforms.java @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package helpers; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +import java.lang.constant.ConstantDescs; +import java.util.stream.Stream; + +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.components.ClassRemapper; +import jdk.internal.org.objectweb.asm.AnnotationVisitor; +import jdk.internal.org.objectweb.asm.Attribute; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.FieldVisitor; +import jdk.internal.org.objectweb.asm.Handle; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.ModuleVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.RecordComponentVisitor; +import jdk.internal.org.objectweb.asm.TypePath; +import jdk.internal.org.objectweb.asm.tree.ClassNode; + +/** + * Transforms + */ +public class Transforms { + + static int ASM9 = 9 << 16 | 0 << 8; + + public static final ClassTransform threeLevelNoop = (cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, CodeTransform.ACCEPT_ALL); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }; + + private static final ClassTransform threeLevelNoopPipedCMC_seed = (cb, ce) -> { + if (ce instanceof MethodModel mm) { + MethodTransform transform = (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, CodeTransform.ACCEPT_ALL.andThen(CodeTransform.ACCEPT_ALL)); + } + else + mb.with(me); + }; + cb.transformMethod(mm, transform); + } + else + cb.with(ce); + }; + + static final ClassTransform twoLevelNoop = (cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, MethodTransform.ACCEPT_ALL); + } + else + cb.with(ce); + }; + + static final ClassTransform oneLevelNoop = ClassTransform.ACCEPT_ALL; + + public static final List noops = List.of(threeLevelNoop, twoLevelNoop, oneLevelNoop); + + public enum NoOpTransform { + ARRAYCOPY(bytes -> { + byte[] bs = new byte[bytes.length]; + System.arraycopy(bytes, 0, bs, 0, bytes.length); + return bs; + }), + BUILD_FROM_SCRATCH(bytes -> { + return RebuildingTransformation.transform(Classfile.parse(bytes)); + }), + SHARED_1(true, oneLevelNoop), + SHARED_2(true, twoLevelNoop), + SHARED_3(true, threeLevelNoop), + SHARED_3P(true, threeLevelNoop.andThen(threeLevelNoop)), + SHARED_3L(true, ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)), + SHARED_3Sx(true, threeLevelNoopPipedCMC_seed.andThen(ClassTransform.ACCEPT_ALL)), + SHARED_3bc(true, ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.ACCEPT_ALL) + .andThen(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL))), + UNSHARED_1(false, oneLevelNoop), + UNSHARED_2(false, twoLevelNoop), + UNSHARED_3(false, threeLevelNoop), + SHARED_3_NO_STACKMAP(true, threeLevelNoop, Classfile.Option.generateStackmap(false)), + SHARED_3_NO_DEBUG(true, threeLevelNoop, Classfile.Option.processDebug(false), Classfile.Option.processLineNumbers(false)), + ASM_1(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(cw, 0); + return cw.toByteArray(); + }), + ASM_UNSHARED_1(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(cw, 0); + return cw.toByteArray(); + }), + ASM_3(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(new CustomClassVisitor(cw), 0); + return cw.toByteArray(); + }), + ASM_UNSHARED_3(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(new CustomClassVisitor(cw), 0); + return cw.toByteArray(); + }), + ASM_TREE(bytes -> { + ClassNode node = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(node, 0); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + node.accept(cw); + return cw.toByteArray(); + }), + CLASS_REMAPPER(bytes -> + ClassRemapper.of(Map.of()).remapClass(Classfile.parse(bytes))); + + // Need ASM, LOW_UNSHARED + + public final UnaryOperator transform; + public final boolean shared; + public final ClassTransform classTransform; + public final Classfile.Option[] options; + + NoOpTransform(UnaryOperator transform) { + this.transform = transform; + classTransform = null; + shared = false; + options = new Classfile.Option[0]; + } + + NoOpTransform(boolean shared, + ClassTransform classTransform, + Classfile.Option... options) { + this.shared = shared; + this.classTransform = classTransform; + this.options = shared + ? options + : Stream.concat(Stream.of(options), Stream.of(Classfile.Option.constantPoolSharing(false))).toArray(Classfile.Option[]::new); + this.transform = bytes -> Classfile.parse(bytes, this.options).transform(classTransform); + } + + public Optional classRecord(byte[] bytes) throws IOException { + return switch (this) { + case ARRAYCOPY -> Optional.of(ClassRecord.ofClassModel(Classfile.parse(bytes))); + case SHARED_1, SHARED_2, SHARED_3, + UNSHARED_1, UNSHARED_2, UNSHARED_3, + BUILD_FROM_SCRATCH + -> Optional.of(ClassRecord.ofClassModel(Classfile.parse(bytes), ClassRecord.CompatibilityFilter.By_ClassBuilder)); + default -> Optional.empty(); + }; + } + } + + public enum InjectNopTransform { + ASM_NOP_SHARED(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(new NopClassVisitor(cw), 0); + return cw.toByteArray(); + }), + NOP_SHARED(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return cm.transform((cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.withCode(xb -> { + xb.nopInstruction(); + xm.forEachElement(new Consumer<>() { + @Override + public void accept(CodeElement e) { + xb.with(e); + } + }); + }); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }); + }); + + public final UnaryOperator transform; + + InjectNopTransform(UnaryOperator transform) { + this.transform = transform; + } + } + + public enum SimpleTransform { + ASM_ADD_FIELD(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(cw, 0); + cw.visitField(0, "argleBargleWoogaWooga", "I", null, null); + return cw.toByteArray(); + }), + HIGH_SHARED_ADD_FIELD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return cm.transform(new ClassTransform() { + @Override + public void accept(ClassBuilder builder, ClassElement element) { + builder.with(element); + } + + @Override + public void atEnd(ClassBuilder builder) { + builder.withField("argleBargleWoogaWooga", ConstantDescs.CD_int, b -> { }); + } + }); + }), + HIGH_UNSHARED_ADD_FIELD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return Classfile.build(cm.thisClass().asSymbol(), + cb -> { + cm.forEachElement(cb); + cb.withField("argleBargleWoogaWooga", ConstantDescs.CD_int, b -> { }); + }); + }), + ASM_DEL_METHOD(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + ClassVisitor v = new ClassVisitor(ASM9, cw) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return (name.equals("hashCode") && descriptor.equals("()Z")) + ? null + : super.visitMethod(access, name, descriptor, signature, exceptions); + } + }; + cr.accept(cw, 0); + return cw.toByteArray(); + }), + HIGH_SHARED_DEL_METHOD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return cm.transform((builder, element) -> { + if (!(element instanceof MethodModel mm)) + builder.with(element); + }); + }), + HIGH_UNSHARED_DEL_METHOD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return Classfile.build(cm.thisClass().asSymbol(), + cb -> { + cm.forEachElement(element -> { + if (element instanceof MethodModel mm + && mm.methodName().stringValue().equals("hashCode") + && mm.methodType().stringValue().equals("()Z")) { + + } + else + cb.with(element); + }); + }); + }); + + public final UnaryOperator transform; + + SimpleTransform(UnaryOperator transform) { + this.transform = transform; + } + } + + static class CustomClassVisitor extends ClassVisitor { + + public CustomClassVisitor(ClassVisitor writer) { + super(ASM9, writer); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(String source, String debug) { + super.visitSource(source, debug); + } + + @Override + public ModuleVisitor visitModule(String name, int access, String version) { + return super.visitModule(name, access, version); + } + + @Override + public void visitNestHost(String nestHost) { + super.visitNestHost(nestHost); + } + + @Override + public void visitOuterClass(String owner, String name, String descriptor) { + super.visitOuterClass(owner, name, descriptor); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return super.visitAnnotation(descriptor, visible); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitAttribute(Attribute attribute) { + super.visitAttribute(attribute); + } + + @Override + public void visitNestMember(String nestMember) { + super.visitNestMember(nestMember); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) { + return super.visitRecordComponent(name, descriptor, signature); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return new CustomMethodVisitor(mv); + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + }; + + + static class CustomMethodVisitor extends MethodVisitor { + + public CustomMethodVisitor(MethodVisitor methodVisitor) { + super(ASM9, methodVisitor); + } + + @Override + public void visitParameter(String name, int access) { + super.visitParameter(name, access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return super.visitAnnotationDefault(); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return super.visitAnnotation(descriptor, visible); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitAnnotableParameterCount(int parameterCount, boolean visible) { + super.visitAnnotableParameterCount(parameterCount, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) { + return super.visitParameterAnnotation(parameter, descriptor, visible); + } + + @Override + public void visitAttribute(Attribute attribute) { + super.visitAttribute(attribute); + } + + @Override + public void visitCode() { + super.visitCode(); + } + + @Override + public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) { + super.visitFrame(type, numLocal, local, numStack, stack); + } + + @Override + public void visitInsn(int opcode) { + super.visitInsn(opcode); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(int opcode, int var) { + super.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + @SuppressWarnings("deprecation") + public void visitMethodInsn(int opcode, String owner, String name, String descriptor) { + super.visitMethodInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override + public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitLabel(Label label) { + super.visitLabel(label); + } + + @Override + public void visitLdcInsn(Object value) { + super.visitLdcInsn(value); + } + + @Override + public void visitIincInsn(int var, int increment) { + super.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(String descriptor, int numDimensions) { + super.visitMultiANewArrayInsn(descriptor, numDimensions); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + super.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + super.visitLocalVariable(name, descriptor, signature, start, end, index); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) { + return super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible); + } + + @Override + public void visitLineNumber(int line, Label start) { + super.visitLineNumber(line, start); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + super.visitMaxs(maxStack, maxLocals); + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + }; + + static class NopClassVisitor extends CustomClassVisitor { + + public NopClassVisitor(ClassVisitor writer) { + super(writer); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return new NopMethodVisitor(mv); + } + } + + static class NopMethodVisitor extends CustomMethodVisitor { + + public NopMethodVisitor(MethodVisitor methodVisitor) { + super(methodVisitor); + } + + @Override + public void visitCode() { + super.visitCode(); + visitInsn(Opcodes.NOP); + } + } + +} diff --git a/test/jdk/jdk/classfile/testdata/Lvt.java b/test/jdk/jdk/classfile/testdata/Lvt.java new file mode 100644 index 0000000000000..35a0637a5e83f --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Lvt.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; +import java.util.*; + +public class Lvt { + public void m(String a) { + int b; + Object c; + char[] d = new char[27]; + + for (int j = 0; j < d.length; j++) { + char x = d[j]; + } + } + + public List n(U u, Class > z) { + var v = new ArrayList(); + + Set> s = new TreeSet<>(); + + for (List f : new ArrayList>()) { + System.out.println(f); + } + + return null; + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern1.java b/test/jdk/jdk/classfile/testdata/Pattern1.java new file mode 100644 index 0000000000000..80bb479d2eb96 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern1.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public final class Pattern1 { + + static void troublesCausingMethod() { + Object[] obj = null; + boolean match; + for (int i = 0; i < obj.length; i++) { + match = false; + for (int j = 0; j < obj.length; j++) { + if (obj[i].equals(obj[j])) { + match = true; + break; + } + } + if (!match) { + return; + } + } + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern10.java b/test/jdk/jdk/classfile/testdata/Pattern10.java new file mode 100644 index 0000000000000..ffec7324abdad --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern10.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public class Pattern10 { + + @SuppressWarnings("rawtypes") + private static void troublesCausingMethod(Object arg) { + boolean b = true; + if (b) { + var v = new byte[0]; + } else { + var v = new int[0]; + } + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern2.java b/test/jdk/jdk/classfile/testdata/Pattern2.java new file mode 100644 index 0000000000000..3b227c4032049 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern2.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +import java.util.List; +import java.util.Map; + +public final class Pattern2 { + + static void troublesCausingMethod() { + String s = null; + Object o; + Map map = null; + List list = null; + if (s == null) return; + for (int i = 0; i < 10; i++) { + String key = ""; + if (map.containsKey(key)) { + o = map.get(key); + } else { + o = new Object(); + map.put(key, o); + } + Object bais = new Object(); + try { + list.add(o.toString()); + } catch (Exception ce) { + throw ce; + } + bais.toString(); + } + if (list != null) {} + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern3.java b/test/jdk/jdk/classfile/testdata/Pattern3.java new file mode 100644 index 0000000000000..d99329e9b7277 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern3.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public class Pattern3 { + + private String file; + + void troublesCausingMethod() { + String path = null; + String query = null; + this.file = query == null ? path : path + query; + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern4.java b/test/jdk/jdk/classfile/testdata/Pattern4.java new file mode 100644 index 0000000000000..2d5aa7d22bba6 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern4.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public class Pattern4 { + + static void troublesCausingMethod() { + try { + Object o = null; + try { + o = null; + } catch (Exception e) { + if (o != null) o = null; + } + if (o != null) {} + } catch (Exception e) {} + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern5.java b/test/jdk/jdk/classfile/testdata/Pattern5.java new file mode 100644 index 0000000000000..967d8e11a06e2 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern5.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public class Pattern5 { + + String troublesCausingMethod() { + Object o = null; + String t; + if (o != null || (t = o.toString()) == null) { + t = toString(); + } + return t; + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern6.java b/test/jdk/jdk/classfile/testdata/Pattern6.java new file mode 100644 index 0000000000000..723c1ae5cc101 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern6.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public class Pattern6 { + + static void troublesCausingMethod() { + boolean b = true; + String s1 = null; + String s2; + if (s1 != null) { + s2 = s1; + } else { + try { + s2 = null; + if (b) {} + s2.equals(null); + } catch (Error ex) { + throw ex; + } + } + if (null == s2) {} + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern7.java b/test/jdk/jdk/classfile/testdata/Pattern7.java new file mode 100644 index 0000000000000..30f6c18602829 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern7.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +public class Pattern7 { + + static void troublesCausingMethod() { + Object r = null; + Boolean e = null; + String x; + String d = null; + if (r instanceof Integer && (x = ((Integer) r).toString()) != null) { + d = x + r.toString(); + } else { + if (e != null) {} + } + d.chars(); + } +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern8.java b/test/jdk/jdk/classfile/testdata/Pattern8.java new file mode 100644 index 0000000000000..158f00a09c6c4 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern8.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +import java.util.Vector; + +public class Pattern8 { + private Vector expandTable = null; + private static final int INITIALTABLESIZE = 20; + + private int addExpansion(int anOrder, String expandChars) { + if (expandTable == null) { + expandTable = new Vector<>(INITIALTABLESIZE); + } + + // If anOrder is valid, we want to add it at the beginning of the list + int offset = (anOrder == 13) ? 0 : 1; + + int[] valueList = new int[expandChars.length() + offset]; + if (offset == 1) { + valueList[0] = anOrder; + } + + int j = offset; + for (int i = 0; i < expandChars.length(); i++) { + char ch0 = expandChars.charAt(i); + char ch1; + int ch; + if (Character.isHighSurrogate(ch0)) { + if (++i == expandChars.length() || + !Character.isLowSurrogate(ch1=expandChars.charAt(i))) { + //ether we are missing the low surrogate or the next char + //is not a legal low surrogate, so stop loop + break; + } + ch = Character.toCodePoint(ch0, ch1); + + } else { + ch = ch0; + } + + int mapValue = ch; + + if (mapValue != 0xFFFFFFFF) { + valueList[j++] = mapValue; + } else { + // can't find it in the table, will be filled in by commit(). + valueList[j++] = 0x70000000 + ch; + } + } + if (j < valueList.length) { + //we had at least one supplementary character, the size of valueList + //is bigger than it really needs... + int[] tmpBuf = new int[j]; + while (--j >= 0) { + tmpBuf[j] = valueList[j]; + } + valueList = tmpBuf; + } + // Add the expanding char list into the expansion table. + int tableIndex = 10 + expandTable.size(); + expandTable.addElement(valueList); + + return tableIndex; + } + +} diff --git a/test/jdk/jdk/classfile/testdata/Pattern9.java b/test/jdk/jdk/classfile/testdata/Pattern9.java new file mode 100644 index 0000000000000..70dfd348bcb16 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Pattern9.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +import java.lang.reflect.RecordComponent; +import java.util.Optional; +import java.util.Set; + +public class Pattern9 { + + @SuppressWarnings("rawtypes") + private static void troublesCausingMethod(Object arg) { + if (arg instanceof Record) { + for (RecordComponent rc : arg.getClass().getRecordComponents()) {} + } else if (arg instanceof Optional actualOpt) { + } else { + if (arg instanceof Set actualSet) {} + } + } +} diff --git a/test/jdk/jdk/classfile/testdata/TypeAnnotationPattern.java b/test/jdk/jdk/classfile/testdata/TypeAnnotationPattern.java new file mode 100644 index 0000000000000..7a839045b0fdf --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/TypeAnnotationPattern.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package testdata; + +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TypeAnnotationPattern { + + class Middle { + class Inner {} + } + + @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE}) + @Retention(RetentionPolicy.RUNTIME) + @interface Foo { + } + + @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE}) + @Retention(RetentionPolicy.CLASS) + @interface Bar { + } + + @Foo String @Bar [][] fa; + String @Foo [] @Bar[] fb; + @Bar String[] @Foo [] fc; + + @Foo TypeAnnotationPattern.@Bar Middle.Inner fd; + TypeAnnotationPattern.@Foo Middle.@Bar Inner fe; + @Bar TypeAnnotationPattern.Middle.@Foo Inner ff; + + @Foo Map<@Bar String,Object> fg; + Map<@Foo String,@Bar Object> fh; + @Bar Map fi; + + List<@Foo ? extends @Bar String> fj; + List<@Bar ? extends @Foo String> fk; + + @SuppressWarnings("unchecked") + void annotatedCode( + @Foo String @Bar [][] mpa, + String @Foo [] @Bar[] mpb, + @Bar String[] @Foo [] mpc, + + @Foo TypeAnnotationPattern.@Bar Middle.Inner mpd, + TypeAnnotationPattern.@Foo Middle.@Bar Inner mpe, + @Bar TypeAnnotationPattern.Middle.@Foo Inner mpf, + + @Foo Map<@Bar String,Object> mpg, + Map<@Foo String,@Bar Object> mph, + @Bar Map mpi, + + List<@Foo ? extends @Bar String> mpj, + List<@Bar ? extends @Foo String> mpk + ) { + @Foo String[][] lva; +// String @Foo [][] lvb; // AssertionError from javac +// String[] @Foo [] lvc; // AssertionError from javac + + @Foo TypeAnnotationPattern.@Bar Middle.Inner lvd; +// TypeAnnotationPattern.@Foo Middle.@Bar Inner lve; // AssertionError from javac +// @Bar TypeAnnotationPattern.Middle.@Foo Inner lvf; // AssertionError from javac + + @Foo Map<@Bar String,Object> lvg; + Map<@Foo String,@Bar Object> lvh; + @Bar Map lvi; + + List<@Foo ? extends @Bar String> lvj; + List<@Bar ? extends @Foo String> lvk; + + Object o = null; +// var cea = (@Foo String [][]) o; // AssertionError from javac +// var ceb = (String @Foo [] @Bar[]) o; // AssertionError from javac +// var cec = (@Bar String[] @Foo []) o; // AssertionError from javac + + var ced = (@Foo TypeAnnotationPattern.@Bar Middle.Inner) o; +// var cee = (TypeAnnotationPattern.@Foo Middle.@Bar Inner) o; // AssertionError from javac +// var cef = (@Bar TypeAnnotationPattern.Middle.@Foo Inner) o; // AssertionError from javac + +// var ceg = (@Foo Map<@Bar String,Object> ) o; // AssertionError from javac + var ceh = (Map<@Foo String,@Bar Object>) o; +// var cei = (@Bar Map) o; // AssertionError from javac + + var cej = (List<@Foo ? extends @Bar String>) o; + var cek = (List<@Bar ? extends @Foo String>) o; + +// var na = new @Foo String [][] {}; // AssertionError from javac +// var nb = new String @Foo [] @Bar[] {}; // AssertionError from javac +// var nc = new @Bar String[] @Foo [] {}; // AssertionError from javac + +// var ng = new @Foo HashMap<@Bar String,Object>(); // AssertionError from javac + var nh = new HashMap<@Foo String,@Bar Object>(); +// var ni = new @Bar HashMap(); // AssertionError from javac + +// if (o instanceof @Foo String [][]) {} // AssertionError from javac +// if (o instanceof String @Foo [] @Bar[]) {} // AssertionError from javac +// if (o instanceof @Bar String[] @Foo []) {} // AssertionError from javac + + if (o instanceof @Foo TypeAnnotationPattern.Middle.Inner) {} +// if (o instanceof TypeAnnotationPattern.@Foo Middle.@Bar Inner) {} // AssertionError from javac +// if (o instanceof @Bar TypeAnnotationPattern.Middle.@Foo Inner) {} // AssertionError from javac + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/AbstractCorpusBenchmark.java b/test/micro/org/openjdk/bench/jdk/classfile/AbstractCorpusBenchmark.java new file mode 100644 index 0000000000000..310aa6f7a17b5 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/AbstractCorpusBenchmark.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +/** + * AbstractCorpusBenchmark + */ +@Warmup(iterations = 2) +@Measurement(iterations = 4) +@Fork(1) +@State(Scope.Benchmark) +public class AbstractCorpusBenchmark { + protected byte[][] classes; + + @Setup + public void setup() { + classes = rtJarToBytes(FileSystems.getFileSystem(URI.create("jrt:/"))); + } + + @TearDown + public void tearDown() { + //nop + } + + private static byte[][] rtJarToBytes(FileSystem fs) { + try { + var modules = Stream.of( + Files.walk(fs.getPath("modules/java.base/java")), + Files.walk(fs.getPath("modules"), 2).filter(p -> p.endsWith("module-info.class"))) + .flatMap(p -> p) + .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")) + .map(AbstractCorpusBenchmark::readAllBytes) + .toArray(byte[][]::new); + return modules; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private static byte[] readAllBytes(Path p) { + try { + return Files.readAllBytes(p); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/AdHocAdapt.java b/test/micro/org/openjdk/bench/jdk/classfile/AdHocAdapt.java new file mode 100644 index 0000000000000..5213f7ca8ff51 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/AdHocAdapt.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeTransform; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.infra.Blackhole; + +/** + * AdHocAdapt + */ +public class AdHocAdapt extends AbstractCorpusBenchmark { + public enum X { + LIFT(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)), + LIFT1(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL + .andThen(CodeTransform.ACCEPT_ALL))), + LIFT2(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL))); + + ClassTransform transform; + + X(ClassTransform transform) { + this.transform = transform; + } + } + + @Param + X transform; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transform(Blackhole bh) { + for (byte[] bytes : classes) + bh.consume(Classfile.parse(bytes).transform(transform.transform)); + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/AdaptInjectNoop.java b/test/micro/org/openjdk/bench/jdk/classfile/AdaptInjectNoop.java new file mode 100644 index 0000000000000..c6cb4e095dcf5 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/AdaptInjectNoop.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.infra.Blackhole; + +/** + * AdaptInjectNoop + */ +public class AdaptInjectNoop extends AbstractCorpusBenchmark { + @Param + Transforms.InjectNopTransform transform; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transform(Blackhole bh) { + for (byte[] aClass : classes) + bh.consume(transform.transform.apply(aClass)); + } +} + diff --git a/test/micro/org/openjdk/bench/jdk/classfile/AdaptMetadata.java b/test/micro/org/openjdk/bench/jdk/classfile/AdaptMetadata.java new file mode 100644 index 0000000000000..d3e1cc8924bb2 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/AdaptMetadata.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * CorpusAdapt + */ +public class AdaptMetadata extends AbstractCorpusBenchmark { + + @Param + Transforms.SimpleTransform transform; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transform(Blackhole bh) { + for (byte[] aClass : classes) + bh.consume(transform.transform.apply(aClass)); + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/AdaptNull.java b/test/micro/org/openjdk/bench/jdk/classfile/AdaptNull.java new file mode 100644 index 0000000000000..81032e4c0dab0 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/AdaptNull.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import java.net.URI; +import java.nio.file.FileSystems; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +/** + * CorpusNullAdapt + */ +public class AdaptNull extends AbstractCorpusBenchmark { + + @Param({ +// "ARRAYCOPY", + "ASM_1", + "ASM_3", + "ASM_UNSHARED_3", +// "ASM_TREE", + "SHARED_1", + "SHARED_2", + "SHARED_3", + "SHARED_3_NO_DEBUG", +// "HIGH_X1", +// "HIGH_X2", +// "HIGH_X3", +// "UNSHARED_1", +// "UNSHARED_2", + "UNSHARED_3", +// "SHARED_3_NO_STACKMAP" + }) + Transforms.NoOpTransform noOpTransform; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transform(Blackhole bh) { + for (byte[] aClass : classes) + bh.consume(noOpTransform.transform.apply(aClass)); + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java b/test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java new file mode 100644 index 0000000000000..e856a1890cf3d --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.ClassReader; +import jdk.internal.classfile.constantpool.ConstantPoolBuilder; +import jdk.internal.classfile.impl.AbstractPseudoInstruction; +import jdk.internal.classfile.impl.CodeImpl; +import jdk.internal.classfile.impl.LabelContext; +import jdk.internal.classfile.impl.SplitConstantPool; +import jdk.internal.classfile.impl.StackMapGenerator; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.Throughput) +@State(Scope.Benchmark) +@Fork(1) +@Warmup(iterations = 2) +@Measurement(iterations = 10) +public class GenerateStackMaps { + + record GenData(LabelContext labelContext, + ClassDesc thisClass, + String methodName, + MethodTypeDesc methodDesc, + boolean isStatic, + ByteBuffer bytecode, + ConstantPoolBuilder constantPool, + List handlers) {} + + List data; + Iterator it; + GenData d; + + @Setup(Level.Trial) + public void setup() throws IOException { + data = new LinkedList<>(); + Files.walk(FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java")).forEach(p -> { + if (Files.isRegularFile(p) && p.toString().endsWith(".class")) try { + var clm = Classfile.parse(p); + var thisCls = clm.thisClass().asSymbol(); + var cp = new SplitConstantPool((ClassReader)clm.constantPool()); + for (var m : clm.methods()) { + m.code().ifPresent(com -> { + var bb = ByteBuffer.wrap(((CodeImpl)com).contents()); + data.add(new GenData( + (LabelContext)com, + thisCls, + m.methodName().stringValue(), + m.methodTypeSymbol(), + (m.flags().flagsMask() & Classfile.ACC_STATIC) != 0, + bb.slice(8, bb.getInt(4)), + cp, + com.exceptionHandlers().stream().map(eh -> (AbstractPseudoInstruction.ExceptionCatchImpl)eh).toList())); + }); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + @Benchmark + public void benchmark() { + if (it == null || !it.hasNext()) + it = data.iterator(); + var d = it.next(); + new StackMapGenerator( + d.labelContext(), + d.thisClass(), + d.methodName(), + d.methodDesc(), + d.isStatic(), + d.bytecode().rewind(), + (SplitConstantPool)d.constantPool(), + d.handlers()); + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/ParseOptions.java b/test/micro/org/openjdk/bench/jdk/classfile/ParseOptions.java new file mode 100644 index 0000000000000..42a9f4d9ff4f1 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/ParseOptions.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.infra.Blackhole; + +import static org.openjdk.bench.jdk.classfile.Transforms.threeLevelNoop; + +/** + * ParseOptions + */ +public class ParseOptions extends AbstractCorpusBenchmark { + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transformNoDebug(Blackhole bh) { + for (byte[] aClass : classes) { + ClassModel cm = Classfile.parse(aClass, Classfile.Option.processDebug(false)); + bh.consume(cm.transform(threeLevelNoop)); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transformNoStackmap(Blackhole bh) { + for (byte[] aClass : classes) { + ClassModel cm = Classfile.parse(aClass, Classfile.Option.generateStackmap(false)); + bh.consume(cm.transform(threeLevelNoop)); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void transformNoLineNumbers(Blackhole bh) { + for (byte[] aClass : classes) { + ClassModel cm = Classfile.parse(aClass, Classfile.Option.processLineNumbers(false)); + bh.consume(cm.transform(threeLevelNoop)); + } + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/ReadDeep.java b/test/micro/org/openjdk/bench/jdk/classfile/ReadDeep.java new file mode 100644 index 0000000000000..81ed7b534622e --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/ReadDeep.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.ClassfileElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CompoundElement; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.instruction.LoadInstruction; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.tree.ClassNode; +import jdk.internal.org.objectweb.asm.tree.MethodNode; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.infra.Blackhole; + +/** + * ReadCode + */ +public class ReadDeep extends AbstractCorpusBenchmark { + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void asmStreamCountLoads(Blackhole bh) { + for (byte[] bytes : classes) { + ClassReader cr = new ClassReader(bytes); + + var mv = new MethodVisitor(Opcodes.ASM9) { + int count = 0; + + @Override + public void visitVarInsn(int opcode, int var) { + ++count; + } + }; + + var visitor = new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return mv; + } + }; + cr.accept(visitor, 0); + bh.consume(mv.count); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void asmTreeCountLoads(Blackhole bh) { + for (byte[] bytes : classes) { + var mv = new MethodVisitor(Opcodes.ASM9) { + int count = 0; + + @Override + public void visitVarInsn(int opcode, int var) { + ++count; + } + }; + + ClassNode node = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(node, 0); + for (MethodNode mn : node.methods) { + mn.accept(mv); + } + bh.consume(mv.count); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void jdkElementsCountLoads(Blackhole bh) { + for (byte[] bytes : classes) { + int[] count = new int[1]; + ClassModel cm = Classfile.parse(bytes); + cm.forEachElement(ce -> { + if (ce instanceof MethodModel mm) { + mm.forEachElement(me -> { + if (me instanceof CodeModel xm) { + xm.forEachElement(xe -> { + if (xe instanceof LoadInstruction) { + ++count[0]; + } + }); + } + }); + }; + }); + bh.consume(count[0]); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void jdkElementsDeepIterate(Blackhole bh) { + for (byte[] bytes : classes) { + ClassModel cm = Classfile.parse(bytes); + bh.consume(iterateAll(cm)); + } + } + + private static ClassfileElement iterateAll(CompoundElement model) { + ClassfileElement last = null; + for (var e : model) { + if (e instanceof CompoundElement cm) { + last = iterateAll(cm); + } else { + last = e; + } + } + return last; // provide some kind of result that the benchmark can feed to the black hole + } + +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java b/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java new file mode 100644 index 0000000000000..8b254004ab0a2 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.FieldModel; +import jdk.internal.org.objectweb.asm.*; +import jdk.internal.org.objectweb.asm.tree.*; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +public class ReadMetadata extends AbstractCorpusBenchmark { + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void asmStreamReadName(Blackhole bh) { + for (byte[] bytes : classes) { + ClassReader cr = new ClassReader(bytes); + var visitor = new ClassVisitor(Opcodes.ASM9) { + String theName; + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + theName = name; + } + }; + cr.accept(visitor, 0); + bh.consume(visitor.theName); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void asmTreeReadName(Blackhole bh) { + for (byte[] bytes : classes) { + ClassNode node = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(node, 0); + bh.consume(node.name); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void jdkReadName(Blackhole bh) { + for (byte[] bytes : classes) { + bh.consume(Classfile.parse(bytes).thisClass().asInternalName()); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void asmStreamCountFields(Blackhole bh) { + for (byte[] bytes : classes) { + ClassReader cr = new ClassReader(bytes); + var visitor = new ClassVisitor(Opcodes.ASM9) { + int count; + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + if ((access & Opcodes.ACC_PUBLIC) != 1) { + ++count; + } + return null; + } + }; + cr.accept(visitor, 0); + bh.consume(visitor.count); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void asmTreeCountFields(Blackhole bh) { + for (byte[] bytes : classes) { + int count = 0; + ClassNode node = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(node, 0); + for (FieldNode fn : node.fields) + if ((fn.access & Opcodes.ACC_PUBLIC) != 1) { + ++count; + } + bh.consume(count); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void jdkTreeCountFields(Blackhole bh) { + for (byte[] bytes : classes) { + int count = 0; + ClassModel cm = Classfile.parse(bytes); + for (FieldModel fm : cm.fields()) + if (!fm.flags().has(AccessFlag.PUBLIC)) { + ++count; + } + bh.consume(count); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void jdkCountFields(Blackhole bh) { + for (byte[] bytes : classes) { + int count = 0; + ClassModel cm = Classfile.parse(bytes); + for (ClassElement ce : cm) { + if (ce instanceof FieldModel fm) { + if (!fm.flags().has(AccessFlag.PUBLIC)) { + ++count; + } + } + } + bh.consume(count); + } + } +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/TestConstants.java b/test/micro/org/openjdk/bench/jdk/classfile/TestConstants.java new file mode 100644 index 0000000000000..60697569ef065 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/TestConstants.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; + +/** + * TestConstants + */ +public class TestConstants { + public static final ClassDesc CD_System = ClassDesc.of("java.lang.System"); + public static final ClassDesc CD_PrintStream = ClassDesc.of("java.io.PrintStream"); + public static final ClassDesc CD_ArrayList = ClassDesc.of("java.util.ArrayList"); + + public static final MethodTypeDesc MTD_INT_VOID = MethodTypeDesc.ofDescriptor("(I)V"); + public static final MethodTypeDesc MTD_VOID = MethodTypeDesc.ofDescriptor("()V"); +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java b/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java new file mode 100644 index 0000000000000..cd647858a1b6f --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +import java.lang.constant.ConstantDescs; +import java.util.stream.Stream; + +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.ClassTransform; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeElement; +import jdk.internal.classfile.CodeModel; +import jdk.internal.classfile.CodeTransform; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.MethodTransform; +import jdk.internal.classfile.components.ClassRemapper; +import jdk.internal.org.objectweb.asm.AnnotationVisitor; +import jdk.internal.org.objectweb.asm.Attribute; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.FieldVisitor; +import jdk.internal.org.objectweb.asm.Handle; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.ModuleVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.RecordComponentVisitor; +import jdk.internal.org.objectweb.asm.TypePath; +import jdk.internal.org.objectweb.asm.tree.ClassNode; + +/** + * Transforms + */ +public class Transforms { + + static int ASM9 = 9 << 16 | 0 << 8; + + public static final ClassTransform threeLevelNoop = (cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, CodeTransform.ACCEPT_ALL); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }; + + private static final ClassTransform threeLevelNoopPipedCMC_seed = (cb, ce) -> { + if (ce instanceof MethodModel mm) { + MethodTransform transform = (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.transformCode(xm, CodeTransform.ACCEPT_ALL.andThen(CodeTransform.ACCEPT_ALL)); + } + else + mb.with(me); + }; + cb.transformMethod(mm, transform); + } + else + cb.with(ce); + }; + + static final ClassTransform twoLevelNoop = (cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, MethodTransform.ACCEPT_ALL); + } + else + cb.with(ce); + }; + + static final ClassTransform oneLevelNoop = ClassTransform.ACCEPT_ALL; + + public static final List noops = List.of(threeLevelNoop, twoLevelNoop, oneLevelNoop); + + public enum NoOpTransform { + ARRAYCOPY(bytes -> { + byte[] bs = new byte[bytes.length]; + System.arraycopy(bytes, 0, bs, 0, bytes.length); + return bs; + }), + SHARED_1(true, oneLevelNoop), + SHARED_2(true, twoLevelNoop), + SHARED_3(true, threeLevelNoop), + SHARED_3P(true, threeLevelNoop.andThen(threeLevelNoop)), + SHARED_3L(true, ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)), + SHARED_3Sx(true, threeLevelNoopPipedCMC_seed.andThen(ClassTransform.ACCEPT_ALL)), + SHARED_3bc(true, ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL) + .andThen(ClassTransform.ACCEPT_ALL) + .andThen(ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL))), + UNSHARED_1(false, oneLevelNoop), + UNSHARED_2(false, twoLevelNoop), + UNSHARED_3(false, threeLevelNoop), + SHARED_3_NO_STACKMAP(true, threeLevelNoop, Classfile.Option.generateStackmap(false)), + SHARED_3_NO_DEBUG(true, threeLevelNoop, Classfile.Option.processDebug(false), Classfile.Option.processLineNumbers(false)), + ASM_1(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(cw, 0); + return cw.toByteArray(); + }), + ASM_UNSHARED_1(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(cw, 0); + return cw.toByteArray(); + }), + ASM_3(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(new CustomClassVisitor(cw), 0); + return cw.toByteArray(); + }), + ASM_UNSHARED_3(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(new CustomClassVisitor(cw), 0); + return cw.toByteArray(); + }), + ASM_TREE(bytes -> { + ClassNode node = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(node, 0); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + node.accept(cw); + return cw.toByteArray(); + }), + CLASS_REMAPPER(bytes -> + ClassRemapper.of(Map.of()).remapClass(Classfile.parse(bytes))); + + // Need ASM, LOW_UNSHARED + + public final UnaryOperator transform; + public final boolean shared; + public final ClassTransform classTransform; + public final Classfile.Option[] options; + + NoOpTransform(UnaryOperator transform) { + this.transform = transform; + classTransform = null; + shared = false; + options = new Classfile.Option[0]; + } + + NoOpTransform(boolean shared, + ClassTransform classTransform, + Classfile.Option... options) { + this.shared = shared; + this.classTransform = classTransform; + this.options = shared + ? options + : Stream.concat(Stream.of(options), Stream.of(Classfile.Option.constantPoolSharing(false))).toArray(Classfile.Option[]::new); + this.transform = bytes -> Classfile.parse(bytes, this.options).transform(classTransform); + } + } + + public enum InjectNopTransform { + ASM_NOP_SHARED(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(new NopClassVisitor(cw), 0); + return cw.toByteArray(); + }), + NOP_SHARED(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return cm.transform((cb, ce) -> { + if (ce instanceof MethodModel mm) { + cb.transformMethod(mm, (mb, me) -> { + if (me instanceof CodeModel xm) { + mb.withCode(xb -> { + xb.nopInstruction(); + xm.forEachElement(new Consumer<>() { + @Override + public void accept(CodeElement e) { + xb.with(e); + } + }); + }); + } + else + mb.with(me); + }); + } + else + cb.with(ce); + }); + }); + + public final UnaryOperator transform; + + InjectNopTransform(UnaryOperator transform) { + this.transform = transform; + } + } + + public enum SimpleTransform { + ASM_ADD_FIELD(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + cr.accept(cw, 0); + cw.visitField(0, "argleBargleWoogaWooga", "I", null, null); + return cw.toByteArray(); + }), + HIGH_SHARED_ADD_FIELD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return cm.transform(new ClassTransform() { + @Override + public void accept(ClassBuilder builder, ClassElement element) { + builder.with(element); + } + + @Override + public void atEnd(ClassBuilder builder) { + builder.withField("argleBargleWoogaWooga", ConstantDescs.CD_int, b -> { }); + } + }); + }), + HIGH_UNSHARED_ADD_FIELD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return Classfile.build(cm.thisClass().asSymbol(), + cb -> { + cm.forEachElement(cb); + cb.withField("argleBargleWoogaWooga", ConstantDescs.CD_int, b -> { }); + }); + }), + ASM_DEL_METHOD(bytes -> { + ClassReader cr = new ClassReader(bytes); + jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(cr, jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES); + ClassVisitor v = new ClassVisitor(ASM9, cw) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return (name.equals("hashCode") && descriptor.equals("()Z")) + ? null + : super.visitMethod(access, name, descriptor, signature, exceptions); + } + }; + cr.accept(cw, 0); + return cw.toByteArray(); + }), + HIGH_SHARED_DEL_METHOD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return cm.transform((builder, element) -> { + if (!(element instanceof MethodModel mm)) + builder.with(element); + }); + }), + HIGH_UNSHARED_DEL_METHOD(bytes -> { + ClassModel cm = Classfile.parse(bytes); + return Classfile.build(cm.thisClass().asSymbol(), + cb -> { + cm.forEachElement(element -> { + if (element instanceof MethodModel mm + && mm.methodName().stringValue().equals("hashCode") + && mm.methodType().stringValue().equals("()Z")) { + + } + else + cb.with(element); + }); + }); + }); + + public final UnaryOperator transform; + + SimpleTransform(UnaryOperator transform) { + this.transform = transform; + } + } + + static class CustomClassVisitor extends ClassVisitor { + + public CustomClassVisitor(ClassVisitor writer) { + super(ASM9, writer); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(String source, String debug) { + super.visitSource(source, debug); + } + + @Override + public ModuleVisitor visitModule(String name, int access, String version) { + return super.visitModule(name, access, version); + } + + @Override + public void visitNestHost(String nestHost) { + super.visitNestHost(nestHost); + } + + @Override + public void visitOuterClass(String owner, String name, String descriptor) { + super.visitOuterClass(owner, name, descriptor); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return super.visitAnnotation(descriptor, visible); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitAttribute(Attribute attribute) { + super.visitAttribute(attribute); + } + + @Override + public void visitNestMember(String nestMember) { + super.visitNestMember(nestMember); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) { + return super.visitRecordComponent(name, descriptor, signature); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return new CustomMethodVisitor(mv); + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + }; + + + static class CustomMethodVisitor extends MethodVisitor { + + public CustomMethodVisitor(MethodVisitor methodVisitor) { + super(ASM9, methodVisitor); + } + + @Override + public void visitParameter(String name, int access) { + super.visitParameter(name, access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return super.visitAnnotationDefault(); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return super.visitAnnotation(descriptor, visible); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitAnnotableParameterCount(int parameterCount, boolean visible) { + super.visitAnnotableParameterCount(parameterCount, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) { + return super.visitParameterAnnotation(parameter, descriptor, visible); + } + + @Override + public void visitAttribute(Attribute attribute) { + super.visitAttribute(attribute); + } + + @Override + public void visitCode() { + super.visitCode(); + } + + @Override + public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) { + super.visitFrame(type, numLocal, local, numStack, stack); + } + + @Override + public void visitInsn(int opcode) { + super.visitInsn(opcode); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(int opcode, int var) { + super.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + @SuppressWarnings("deprecation") + public void visitMethodInsn(int opcode, String owner, String name, String descriptor) { + super.visitMethodInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override + public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitLabel(Label label) { + super.visitLabel(label); + } + + @Override + public void visitLdcInsn(Object value) { + super.visitLdcInsn(value); + } + + @Override + public void visitIincInsn(int var, int increment) { + super.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(String descriptor, int numDimensions) { + super.visitMultiANewArrayInsn(descriptor, numDimensions); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + super.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible); + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + super.visitLocalVariable(name, descriptor, signature, start, end, index); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) { + return super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible); + } + + @Override + public void visitLineNumber(int line, Label start) { + super.visitLineNumber(line, start); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + super.visitMaxs(maxStack, maxLocals); + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + }; + + static class NopClassVisitor extends CustomClassVisitor { + + public NopClassVisitor(ClassVisitor writer) { + super(writer); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return new NopMethodVisitor(mv); + } + } + + static class NopMethodVisitor extends CustomMethodVisitor { + + public NopMethodVisitor(MethodVisitor methodVisitor) { + super(methodVisitor); + } + + @Override + public void visitCode() { + super.visitCode(); + visitInsn(Opcodes.NOP); + } + } + +} diff --git a/test/micro/org/openjdk/bench/jdk/classfile/Write.java b/test/micro/org/openjdk/bench/jdk/classfile/Write.java new file mode 100644 index 0000000000000..d3d171828f6bb --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/classfile/Write.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package org.openjdk.bench.jdk.classfile; + +import jdk.internal.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.attribute.SourceFileAttribute; +import jdk.internal.org.objectweb.asm.*; +import org.openjdk.jmh.annotations.*; + +import java.io.FileOutputStream; +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.openjdk.bench.jdk.classfile.TestConstants.CD_PrintStream; +import static org.openjdk.bench.jdk.classfile.TestConstants.CD_System; +import static org.openjdk.bench.jdk.classfile.TestConstants.MTD_INT_VOID; +import static org.openjdk.bench.jdk.classfile.TestConstants.MTD_VOID; +import static jdk.internal.classfile.Opcode.*; +import static jdk.internal.classfile.TypeKind.*; +import static jdk.internal.classfile.TypeKind.IntType; +import static jdk.internal.org.objectweb.asm.Opcodes.V12; + +/** + * Write + * + * Generates this program with 40 mains... + * + * class MyClass { + * public static void main(String[] args) { + * int fac = 1; + * for (int i = 1; i < 10; ++i) { + * fac = fac * i; + * } + * System.out.println(fac); + * } + * } + */ +@Warmup(iterations = 3) +@Measurement(iterations = 5) +@Fork(1) +public class Write { + static String checkFileAsm = "/tmp/asw/MyClass.class"; + static String checkFileBc = "/tmp/byw/MyClass.class"; + static boolean writeClassAsm = Files.exists(Paths.get(checkFileAsm).getParent()); + static boolean writeClassBc = Files.exists(Paths.get(checkFileBc).getParent()); + + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public byte[] asmStream() { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cw.visit(V12, Opcodes.ACC_PUBLIC, "MyClass", null, "java/lang/Object", null); + cw.visitSource("MyClass.java", null); + + { + MethodVisitor mv = cw.visitMethod(0, "", "()V", null, null); + mv.visitCode(); + Label startLabel = new Label(); + Label endLabel = new Label(); + mv.visitLabel(startLabel); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(Opcodes.RETURN); + mv.visitLabel(endLabel); + mv.visitLocalVariable("this", "LMyClass;", null, startLabel, endLabel, 1); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + for (int xi = 0; xi < 40; ++xi) { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC, "main"+ ((xi==0)? "" : ""+xi), "([Ljava/lang/String;)V", null, null); + mv.visitCode(); + Label loopTop = new Label(); + Label loopEnd = new Label(); + Label startLabel = new Label(); + Label endLabel = new Label(); + Label iStart = new Label(); + mv.visitLabel(startLabel); + mv.visitInsn(Opcodes.ICONST_1); + mv.visitVarInsn(Opcodes.ISTORE, 1); + mv.visitLabel(iStart); + mv.visitInsn(Opcodes.ICONST_1); + mv.visitVarInsn(Opcodes.ISTORE, 2); + mv.visitLabel(loopTop); + mv.visitVarInsn(Opcodes.ILOAD, 2); + mv.visitIntInsn(Opcodes.BIPUSH, 10); + mv.visitJumpInsn(Opcodes.IF_ICMPGE, loopEnd); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitVarInsn(Opcodes.ILOAD, 2); + mv.visitInsn(Opcodes.IMUL); + mv.visitVarInsn(Opcodes.ISTORE, 1); + mv.visitIincInsn(2, 1); + mv.visitJumpInsn(Opcodes.GOTO, loopTop); + mv.visitLabel(loopEnd); + mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); + mv.visitLabel(endLabel); + mv.visitInsn(Opcodes.RETURN); + mv.visitLocalVariable("fac", "I", null, startLabel, endLabel, 1); + mv.visitLocalVariable("i", "I", null, iStart, loopEnd, 2); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + cw.visitEnd(); + + byte[] bytes = cw.toByteArray(); + if (writeClassAsm) writeClass(bytes, checkFileAsm); + return bytes; + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public byte[] jdkTree() { + + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withVersion(52, 0); + cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + .withMethod("", MethodTypeDesc.of(CD_void), 0, mb -> mb + .withCode(codeb -> codeb.loadInstruction(TypeKind.ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + ); + for (int xi = 0; xi < 40; ++xi) { + cb.withMethod("main" + ((xi == 0) ? "" : "" + xi), MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.STATIC, AccessFlag.PUBLIC).flagsMask(), + mb -> mb.withCode(c0 -> { + jdk.internal.classfile.Label loopTop = c0.newLabel(); + jdk.internal.classfile.Label loopEnd = c0.newLabel(); + int vFac = 1; + int vI = 2; + c0.constantInstruction(ICONST_1, 1) // 0 + .storeInstruction(IntType, vFac) // 1 + .constantInstruction(ICONST_1, 1) // 2 + .storeInstruction(IntType, vI) // 3 + .labelBinding(loopTop) + .loadInstruction(IntType, vI) // 4 + .constantInstruction(BIPUSH, 10) // 5 + .branchInstruction(IF_ICMPGE, loopEnd) // 6 + .loadInstruction(IntType, vFac) // 7 + .loadInstruction(IntType, vI) // 8 + .operatorInstruction(IMUL) // 9 + .storeInstruction(IntType, vFac) // 10 + .incrementInstruction(vI, 1) // 11 + .branchInstruction(GOTO, loopTop) // 12 + .labelBinding(loopEnd) + .fieldInstruction(GETSTATIC, CD_System, "out", CD_PrintStream) // 13 + .loadInstruction(IntType, vFac) + .invokeInstruction(INVOKEVIRTUAL, CD_PrintStream, "println", MTD_INT_VOID, false) // 15 + .returnInstruction(VoidType); + })); + } + }); + if (writeClassBc) writeClass(bytes, checkFileBc); + return bytes; + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public byte[] jdkTreePrimitive() { + + byte[] bytes = Classfile.build(ClassDesc.of("MyClass"), cb -> { + cb.withFlags(AccessFlag.PUBLIC); + cb.withVersion(52, 0); + cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) + .withMethod("", MethodTypeDesc.of(CD_void), 0, + mb -> mb.withCode(codeb -> codeb.loadInstruction(ReferenceType, 0) + .invokeInstruction(INVOKESPECIAL, CD_Object, "", MTD_VOID, false) + .returnInstruction(VoidType) + ) + ); + for (int xi = 0; xi < 40; ++xi) { + cb.withMethod("main" + ((xi == 0) ? "" : "" + xi), MethodTypeDesc.of(CD_void, CD_String.arrayType()), + AccessFlags.ofMethod(AccessFlag.STATIC, AccessFlag.PUBLIC).flagsMask(), + mb -> mb.withCode(c0 -> { + jdk.internal.classfile.Label loopTop = c0.newLabel(); + jdk.internal.classfile.Label loopEnd = c0.newLabel(); + int vFac = 1; + int vI = 2; + c0.constantInstruction(ICONST_1, 1) // 0 + .storeInstruction(IntType, 1) // 1 + .constantInstruction(ICONST_1, 1) // 2 + .storeInstruction(IntType, 2) // 3 + .labelBinding(loopTop) + .loadInstruction(IntType, 2) // 4 + .constantInstruction(BIPUSH, 10) // 5 + .branchInstruction(IF_ICMPGE, loopEnd) // 6 + .loadInstruction(IntType, 1) // 7 + .loadInstruction(IntType, 2) // 8 + .operatorInstruction(IMUL) // 9 + .storeInstruction(IntType, 1) // 10 + .incrementInstruction(2, 1) // 11 + .branchInstruction(GOTO, loopTop) // 12 + .labelBinding(loopEnd) + .fieldInstruction(GETSTATIC, CD_System, "out", CD_PrintStream) // 13 + .loadInstruction(IntType, 1) + .invokeInstruction(INVOKEVIRTUAL, CD_PrintStream, "println", MTD_INT_VOID, false) // 15 + .returnInstruction(VoidType); + })); + } + }); + if (writeClassBc) writeClass(bytes, checkFileBc); + return bytes; + } + + private void writeClass(byte[] bytes, String fn) { + try { + FileOutputStream out = new FileOutputStream(fn); + out.write(bytes); + out.close(); + } catch (Exception ex) { + throw new InternalError(ex); + } + } +} +