From 34c60575d3c059895a534e2ff84150250f0c7dc2 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 10 Jun 2022 14:09:44 +0200 Subject: [PATCH 001/190] Initial commit of Classfile Processing API --- README.md | 48 +- make/Docs.gmk | 26 +- make/Main.gmk | 6 + make/RunTests.gmk | 9 +- make/modules/java.base/Java.gmk | 3 +- make/test/BuildMicrobenchmark.gmk | 7 + .../classes/jdk/classfile/AccessFlags.java | 112 + .../classes/jdk/classfile/Annotation.java | 108 + .../jdk/classfile/AnnotationElement.java | 193 ++ .../jdk/classfile/AnnotationValue.java | 390 ++++ .../classes/jdk/classfile/Attribute.java | 103 + .../jdk/classfile/AttributeMapper.java | 84 + .../jdk/classfile/AttributedElement.java | 108 + .../classes/jdk/classfile/Attributes.java | 930 +++++++++ .../jdk/classfile/BootstrapMethodEntry.java | 64 + .../classes/jdk/classfile/BufWriter.java | 200 ++ .../classes/jdk/classfile/ClassBuilder.java | 298 +++ .../classes/jdk/classfile/ClassElement.java | 69 + .../jdk/classfile/ClassHierarchyResolver.java | 123 ++ .../classes/jdk/classfile/ClassModel.java | 121 ++ .../classes/jdk/classfile/ClassReader.java | 278 +++ .../classes/jdk/classfile/ClassSignature.java | 83 + .../classes/jdk/classfile/ClassTransform.java | 170 ++ .../classes/jdk/classfile/Classfile.java | 647 ++++++ .../jdk/classfile/ClassfileBuilder.java | 89 + .../jdk/classfile/ClassfileElement.java | 38 + .../jdk/classfile/ClassfileTransform.java | 156 ++ .../jdk/classfile/ClassfileVersion.java | 55 + .../classes/jdk/classfile/CodeBuilder.java | 1258 +++++++++++ .../classes/jdk/classfile/CodeElement.java | 70 + .../classes/jdk/classfile/CodeModel.java | 66 + .../classes/jdk/classfile/CodeTransform.java | 96 + .../jdk/classfile/CompoundElement.java | 97 + .../jdk/classfile/CustomAttribute.java | 47 + .../classes/jdk/classfile/FieldBuilder.java | 72 + .../classes/jdk/classfile/FieldElement.java | 48 + .../classes/jdk/classfile/FieldModel.java | 60 + .../classes/jdk/classfile/FieldTransform.java | 109 + .../classes/jdk/classfile/Instruction.java | 68 + .../classes/jdk/classfile/Interfaces.java | 77 + .../share/classes/jdk/classfile/Label.java | 44 + .../classes/jdk/classfile/MethodBuilder.java | 95 + .../classes/jdk/classfile/MethodElement.java | 55 + .../classes/jdk/classfile/MethodModel.java | 63 + .../jdk/classfile/MethodSignature.java | 127 ++ .../jdk/classfile/MethodTransform.java | 121 ++ .../share/classes/jdk/classfile/Opcode.java | 339 +++ .../jdk/classfile/PseudoInstruction.java | 47 + .../classes/jdk/classfile/Signature.java | 314 +++ .../classes/jdk/classfile/Superclass.java | 48 + .../classes/jdk/classfile/TypeAnnotation.java | 586 ++++++ .../share/classes/jdk/classfile/TypeKind.java | 133 ++ .../jdk/classfile/WritableElement.java | 48 + .../attribute/AnnotationDefaultAttribute.java | 60 + .../attribute/BootstrapMethodsAttribute.java | 58 + .../attribute/CharacterRangeInfo.java | 102 + .../CharacterRangeTableAttribute.java | 57 + .../classfile/attribute/CodeAttribute.java | 50 + .../attribute/CompilationIDAttribute.java | 58 + .../attribute/ConstantValueAttribute.java | 56 + .../attribute/DeprecatedAttribute.java | 52 + .../attribute/EnclosingMethodAttribute.java | 68 + .../attribute/ExceptionsAttribute.java | 85 + .../classfile/attribute/InnerClassInfo.java | 120 ++ .../attribute/InnerClassesAttribute.java | 66 + .../classfile/attribute/LineNumberInfo.java | 54 + .../attribute/LineNumberTableAttribute.java | 58 + .../attribute/LocalVariableInfo.java | 84 + .../LocalVariableTableAttribute.java | 57 + .../attribute/LocalVariableTypeInfo.java | 83 + .../LocalVariableTypeTableAttribute.java | 58 + .../attribute/MethodParameterInfo.java | 92 + .../attribute/MethodParametersAttribute.java | 68 + .../classfile/attribute/ModuleAttribute.java | 175 ++ .../classfile/attribute/ModuleExportInfo.java | 124 ++ .../classfile/attribute/ModuleHashInfo.java | 65 + .../attribute/ModuleHashesAttribute.java | 120 ++ .../attribute/ModuleMainClassAttribute.java | 67 + .../classfile/attribute/ModuleOpenInfo.java | 119 ++ .../attribute/ModulePackagesAttribute.java | 91 + .../attribute/ModuleProvideInfo.java | 92 + .../attribute/ModuleRequireInfo.java | 111 + .../attribute/ModuleResolutionAttribute.java | 85 + .../attribute/ModuleTargetAttribute.java | 79 + .../attribute/NestHostAttribute.java | 56 + .../attribute/NestMembersAttribute.java | 84 + .../PermittedSubclassesAttribute.java | 85 + .../classfile/attribute/RecordAttribute.java | 65 + .../attribute/RecordComponentInfo.java | 114 + .../RuntimeInvisibleAnnotationsAttribute.java | 67 + ...nvisibleParameterAnnotationsAttribute.java | 61 + ...timeInvisibleTypeAnnotationsAttribute.java | 73 + .../RuntimeVisibleAnnotationsAttribute.java | 67 + ...eVisibleParameterAnnotationsAttribute.java | 61 + ...untimeVisibleTypeAnnotationsAttribute.java | 73 + .../attribute/SignatureAttribute.java | 112 + .../SourceDebugExtensionAttribute.java | 52 + .../attribute/SourceFileAttribute.java | 57 + .../attribute/SourceIDAttribute.java | 58 + .../attribute/StackMapTableAttribute.java | 159 ++ .../attribute/SyntheticAttribute.java | 52 + .../classfile/attribute/UnknownAttribute.java | 46 + .../AnnotationConstantValueEntry.java | 41 + .../classfile/constantpool/ClassEntry.java | 53 + .../constantpool/ConstantDynamicEntry.java | 52 + .../classfile/constantpool/ConstantPool.java | 82 + .../constantpool/ConstantPoolBuilder.java | 556 +++++ .../constantpool/ConstantValueEntry.java | 42 + .../classfile/constantpool/DoubleEntry.java | 41 + .../DynamicConstantPoolEntry.java | 59 + .../classfile/constantpool/FieldRefEntry.java | 36 + .../classfile/constantpool/FloatEntry.java | 42 + .../classfile/constantpool/IntegerEntry.java | 41 + .../constantpool/InterfaceMethodRefEntry.java | 37 + .../constantpool/InvokeDynamicEntry.java | 51 + .../constantpool/LoadableConstantEntry.java | 40 + .../jdk/classfile/constantpool/LongEntry.java | 41 + .../constantpool/MemberRefEntry.java | 68 + .../constantpool/MethodHandleEntry.java | 53 + .../constantpool/MethodRefEntry.java | 36 + .../constantpool/MethodTypeEntry.java | 50 + .../classfile/constantpool/ModuleEntry.java | 45 + .../constantpool/NameAndTypeEntry.java | 45 + .../classfile/constantpool/PackageEntry.java | 45 + .../jdk/classfile/constantpool/PoolEntry.java | 68 + .../classfile/constantpool/StringEntry.java | 45 + .../jdk/classfile/constantpool/Utf8Entry.java | 48 + .../impl/AbstractAttributeMapper.java | 105 + .../impl/AbstractBoundLocalVariable.java | 112 + .../classfile/impl/AbstractDirectBuilder.java | 59 + .../jdk/classfile/impl/AbstractElement.java | 48 + .../classfile/impl/AbstractInstruction.java | 1548 ++++++++++++++ .../classfile/impl/AbstractUnboundModel.java | 81 + .../jdk/classfile/impl/AccessFlagsImpl.java | 87 + .../jdk/classfile/impl/AnnotationImpl.java | 173 ++ .../jdk/classfile/impl/AnnotationReader.java | 273 +++ .../jdk/classfile/impl/AttributeHolder.java | 82 + .../jdk/classfile/impl/BlockCodeBuilder.java | 108 + .../jdk/classfile/impl/BoundAttribute.java | 1062 ++++++++++ .../classfile/impl/BoundCharacterRange.java | 97 + .../classfile/impl/BoundLocalVariable.java | 59 + .../impl/BoundLocalVariableType.java | 58 + .../impl/BoundRecordComponentInfo.java | 67 + .../jdk/classfile/impl/BufWriterImpl.java | 208 ++ .../classfile/impl/BufferedCodeBuilder.java | 213 ++ .../classfile/impl/BufferedFieldBuilder.java | 120 ++ .../classfile/impl/BufferedMethodBuilder.java | 200 ++ .../jdk/classfile/impl/BytecodeHelpers.java | 393 ++++ .../classfile/impl/ChainedClassBuilder.java | 103 + .../classfile/impl/ChainedCodeBuilder.java | 68 + .../classfile/impl/ChainedFieldBuilder.java | 70 + .../classfile/impl/ChainedMethodBuilder.java | 86 + .../classfile/impl/ClassHierarchyImpl.java | 185 ++ .../classes/jdk/classfile/impl/ClassImpl.java | 239 +++ .../jdk/classfile/impl/ClassPrinterImpl.java | 725 +++++++ .../jdk/classfile/impl/ClassReaderImpl.java | 443 ++++ .../classfile/impl/ClassfileVersionImpl.java | 56 + .../classes/jdk/classfile/impl/CodeImpl.java | 502 +++++ .../impl/ConcreteBootstrapMethodEntry.java | 110 + .../jdk/classfile/impl/ConcreteEntry.java | 1048 ++++++++++ .../classfile/impl/DirectClassBuilder.java | 204 ++ .../jdk/classfile/impl/DirectCodeBuilder.java | 703 +++++++ .../classfile/impl/DirectFieldBuilder.java | 77 + .../classfile/impl/DirectMethodBuilder.java | 139 ++ .../classes/jdk/classfile/impl/EntryMap.java | 192 ++ .../classes/jdk/classfile/impl/FieldImpl.java | 128 ++ .../jdk/classfile/impl/InstructionData.java | 106 + .../jdk/classfile/impl/InterfacesImpl.java | 53 + .../jdk/classfile/impl/LabelContext.java | 37 + .../classes/jdk/classfile/impl/LabelImpl.java | 101 + .../jdk/classfile/impl/LabelResolver.java | 35 + .../jdk/classfile/impl/LineNumberImpl.java | 80 + .../jdk/classfile/impl/MethodImpl.java | 148 ++ .../jdk/classfile/impl/MethodInfo.java | 46 + .../impl/ModuleAttributeBuilderImpl.java | 155 ++ .../jdk/classfile/impl/ModuleDescImpl.java | 50 + .../impl/NonterminalCodeBuilder.java | 78 + .../classes/jdk/classfile/impl/Options.java | 89 + .../jdk/classfile/impl/PackageDescImpl.java | 72 + .../jdk/classfile/impl/RawBytecodeHelper.java | 171 ++ .../jdk/classfile/impl/SignaturesImpl.java | 308 +++ .../jdk/classfile/impl/SplitConstantPool.java | 613 ++++++ .../jdk/classfile/impl/StackMapDecoder.java | 232 +++ .../jdk/classfile/impl/StackMapGenerator.java | 1632 +++++++++++++++ .../jdk/classfile/impl/SuperclassImpl.java | 54 + .../jdk/classfile/impl/TargetInfoImpl.java | 96 + .../classfile/impl/TemporaryConstantPool.java | 211 ++ .../classfile/impl/TerminalCodeBuilder.java | 35 + .../classfile/impl/TerminalFieldBuilder.java | 35 + .../classfile/impl/TerminalMethodBuilder.java | 37 + .../jdk/classfile/impl/TransformImpl.java | 319 +++ .../jdk/classfile/impl/UnboundAttribute.java | 959 +++++++++ .../classes/jdk/classfile/impl/Util.java | 231 +++ .../impl/verifier/VerificationBytecodes.java | 403 ++++ .../impl/verifier/VerificationFrame.java | 405 ++++ .../impl/verifier/VerificationSignature.java | 335 +++ .../impl/verifier/VerificationTable.java | 411 ++++ .../impl/verifier/VerificationType.java | 464 +++++ .../impl/verifier/VerificationWrapper.java | 199 ++ .../classfile/impl/verifier/VerifierImpl.java | 1846 +++++++++++++++++ .../instruction/ArrayLoadInstruction.java | 58 + .../instruction/ArrayStoreInstruction.java | 58 + .../instruction/BranchInstruction.java | 59 + .../classfile/instruction/CharacterRange.java | 87 + .../instruction/ConstantInstruction.java | 127 ++ .../instruction/ConvertInstruction.java | 74 + .../classfile/instruction/ExceptionCatch.java | 105 + .../instruction/FieldInstruction.java | 123 ++ .../instruction/IncrementInstruction.java | 60 + .../instruction/InvokeDynamicInstruction.java | 104 + .../instruction/InvokeInstruction.java | 144 ++ .../classfile/instruction/LabelTarget.java | 45 + .../jdk/classfile/instruction/LineNumber.java | 46 + .../instruction/LoadInstruction.java | 70 + .../classfile/instruction/LocalVariable.java | 84 + .../instruction/LocalVariableType.java | 81 + .../instruction/LookupSwitchInstruction.java | 62 + .../instruction/MonitorInstruction.java | 52 + .../instruction/NewMultiArrayInstruction.java | 62 + .../instruction/NewObjectInstruction.java | 54 + .../NewPrimitiveArrayInstruction.java | 54 + .../NewReferenceArrayInstruction.java | 53 + .../classfile/instruction/NopInstruction.java | 45 + .../instruction/OperatorInstruction.java | 58 + .../instruction/ReturnInstruction.java | 65 + .../instruction/StackInstruction.java | 53 + .../instruction/StoreInstruction.java | 68 + .../jdk/classfile/instruction/SwitchCase.java | 56 + .../instruction/TableSwitchInstruction.java | 73 + .../instruction/ThrowInstruction.java | 46 + .../instruction/TypeCheckInstruction.java | 70 + .../jdk/classfile/jdktypes/AccessFlag.java | 421 ++++ .../jdk/classfile/jdktypes/ModuleDesc.java | 85 + .../jdk/classfile/jdktypes/PackageDesc.java | 106 + .../classes/jdk/classfile/package-info.java | 475 +++++ .../classfile/snippets/PackageSnippets.java | 225 ++ .../classfile/transforms/ClassRemapper.java | 222 ++ .../transforms/CodeLocalsShifter.java | 123 ++ .../classfile/transforms/LabelsRemapper.java | 105 + .../jdk/classfile/util/ClassPrinter.java | 55 + .../com/sun/tools/javac/code/Preview.java | 10 +- .../sun/tools/javac/comp/TransPatterns.java | 4 +- test/jdk/TEST.groups | 1 + test/jdk/jdk/classfile/AccessFlagsTest.java | 85 + test/jdk/jdk/classfile/AdaptCodeTest.java | 137 ++ .../AdvancedTransformationsTest.java | 276 +++ .../jdk/classfile/AnnotationModelTest.java | 63 + test/jdk/jdk/classfile/AnnotationTest.java | 211 ++ test/jdk/jdk/classfile/ArrayTest.java | 114 + test/jdk/jdk/classfile/BSMTest.java | 111 + test/jdk/jdk/classfile/BasicBlockTest.java | 68 + test/jdk/jdk/classfile/BuilderBlockTest.java | 156 ++ test/jdk/jdk/classfile/BuilderParamTest.java | 67 + .../jdk/classfile/ClassHierarchyInfoTest.java | 107 + test/jdk/jdk/classfile/ClassPrinterTest.java | 412 ++++ .../jdk/classfile/ConstantPoolCopyTest.java | 262 +++ test/jdk/jdk/classfile/CorpusTest.java | 251 +++ test/jdk/jdk/classfile/LDCTest.java | 94 + test/jdk/jdk/classfile/LimitsTest.java | 47 + test/jdk/jdk/classfile/LowAdaptTest.java | 112 + .../jdk/classfile/LowJCovAttributeTest.java | 185 ++ test/jdk/jdk/classfile/LowModuleTest.java | 244 +++ test/jdk/jdk/classfile/LvtTest.java | 335 +++ .../jdk/classfile/MassAdaptCopyCodeTest.java | 104 + .../MassAdaptCopyPrimitiveMatchCodeTest.java | 226 ++ test/jdk/jdk/classfile/ModuleBuilderTest.java | 201 ++ test/jdk/jdk/classfile/ModuleDescTest.java | 61 + test/jdk/jdk/classfile/OneToOneTest.java | 159 ++ .../jdk/classfile/OpcodesValidationTest.java | 120 ++ test/jdk/jdk/classfile/PackageDescTest.java | 68 + test/jdk/jdk/classfile/ShortJumpsFixTest.java | 243 +++ test/jdk/jdk/classfile/SignaturesTest.java | 176 ++ test/jdk/jdk/classfile/StackMapsTest.java | 182 ++ .../jdk/jdk/classfile/StreamedVsListTest.java | 248 +++ test/jdk/jdk/classfile/TEST.properties | 15 + .../TempConstantPoolBuilderTest.java | 73 + .../jdk/classfile/TestRecordComponent.java | 118 ++ test/jdk/jdk/classfile/TransformTests.java | 134 ++ test/jdk/jdk/classfile/Utf8EntryTest.java | 204 ++ test/jdk/jdk/classfile/UtilTest.java | 71 + test/jdk/jdk/classfile/VerifierSelfTest.java | 93 + test/jdk/jdk/classfile/WriteTest.java | 134 ++ .../examples/AnnotationsExamples.java | 236 +++ .../classfile/examples/ExampleGallery.java | 282 +++ .../ExperimentalTransformExamples.java | 70 + .../classfile/examples/ModuleExamples.java | 88 + .../classfile/examples/TransformExamples.java | 68 + .../helpers/ByteArrayClassLoader.java | 86 + .../jdk/classfile/helpers/ClassRecord.java | 1832 ++++++++++++++++ .../classfile/helpers/CorpusTestHelper.java | 125 ++ .../InstructionModelToCodeBuilder.java | 197 ++ .../jdk/classfile/helpers/TestConstants.java | 40 + test/jdk/jdk/classfile/helpers/TestUtil.java | 183 ++ .../jdk/jdk/classfile/helpers/Transforms.java | 590 ++++++ test/jdk/jdk/classfile/testdata/Lvt.java | 50 + test/jdk/jdk/classfile/testdata/Pattern1.java | 45 + .../jdk/jdk/classfile/testdata/Pattern10.java | 38 + test/jdk/jdk/classfile/testdata/Pattern2.java | 56 + test/jdk/jdk/classfile/testdata/Pattern3.java | 36 + test/jdk/jdk/classfile/testdata/Pattern4.java | 40 + test/jdk/jdk/classfile/testdata/Pattern5.java | 37 + test/jdk/jdk/classfile/testdata/Pattern6.java | 46 + test/jdk/jdk/classfile/testdata/Pattern7.java | 41 + test/jdk/jdk/classfile/testdata/Pattern8.java | 89 + test/jdk/jdk/classfile/testdata/Pattern9.java | 42 + .../testdata/TypeAnnotationPattern.java | 130 ++ .../classfile/AbstractCorpusBenchmark.java | 86 + .../bench/jdk/classfile/AdHocAdapt.java | 63 + .../bench/jdk/classfile/AdaptInjectNoop.java | 47 + .../bench/jdk/classfile/AdaptMetadata.java | 52 + .../bench/jdk/classfile/AdaptNull.java | 64 + .../bench/jdk/classfile/ParseOptions.java | 67 + .../openjdk/bench/jdk/classfile/ReadDeep.java | 143 ++ .../bench/jdk/classfile/ReadMetadata.java | 143 ++ .../bench/jdk/classfile/TestConstants.java | 40 + .../bench/jdk/classfile/Transforms.java | 578 ++++++ .../openjdk/bench/jdk/classfile/Write.java | 246 +++ 317 files changed, 50413 insertions(+), 14 deletions(-) create mode 100755 src/java.base/share/classes/jdk/classfile/AccessFlags.java create mode 100644 src/java.base/share/classes/jdk/classfile/Annotation.java create mode 100644 src/java.base/share/classes/jdk/classfile/AnnotationElement.java create mode 100644 src/java.base/share/classes/jdk/classfile/AnnotationValue.java create mode 100755 src/java.base/share/classes/jdk/classfile/Attribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/AttributeMapper.java create mode 100755 src/java.base/share/classes/jdk/classfile/AttributedElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/Attributes.java create mode 100755 src/java.base/share/classes/jdk/classfile/BootstrapMethodEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/BufWriter.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassElement.java create mode 100644 src/java.base/share/classes/jdk/classfile/ClassHierarchyResolver.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassModel.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassReader.java create mode 100644 src/java.base/share/classes/jdk/classfile/ClassSignature.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassTransform.java create mode 100755 src/java.base/share/classes/jdk/classfile/Classfile.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassfileBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassfileElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassfileTransform.java create mode 100755 src/java.base/share/classes/jdk/classfile/ClassfileVersion.java create mode 100755 src/java.base/share/classes/jdk/classfile/CodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/CodeElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/CodeModel.java create mode 100755 src/java.base/share/classes/jdk/classfile/CodeTransform.java create mode 100755 src/java.base/share/classes/jdk/classfile/CompoundElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/CustomAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/FieldBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/FieldElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/FieldModel.java create mode 100755 src/java.base/share/classes/jdk/classfile/FieldTransform.java create mode 100755 src/java.base/share/classes/jdk/classfile/Instruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/Interfaces.java create mode 100755 src/java.base/share/classes/jdk/classfile/Label.java create mode 100755 src/java.base/share/classes/jdk/classfile/MethodBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/MethodElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/MethodModel.java create mode 100644 src/java.base/share/classes/jdk/classfile/MethodSignature.java create mode 100755 src/java.base/share/classes/jdk/classfile/MethodTransform.java create mode 100755 src/java.base/share/classes/jdk/classfile/Opcode.java create mode 100755 src/java.base/share/classes/jdk/classfile/PseudoInstruction.java create mode 100644 src/java.base/share/classes/jdk/classfile/Signature.java create mode 100755 src/java.base/share/classes/jdk/classfile/Superclass.java create mode 100755 src/java.base/share/classes/jdk/classfile/TypeAnnotation.java create mode 100755 src/java.base/share/classes/jdk/classfile/TypeKind.java create mode 100755 src/java.base/share/classes/jdk/classfile/WritableElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/AnnotationDefaultAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/BootstrapMethodsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/CharacterRangeInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/CharacterRangeTableAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/DeprecatedAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/EnclosingMethodAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ExceptionsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/InnerClassesAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/LineNumberInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/LineNumberTableAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTableAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTypeInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTypeTableAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/MethodParametersAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleHashInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleHashesAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleMainClassAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModulePackagesAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleProvideInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleResolutionAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/ModuleTargetAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/NestHostAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/NestMembersAttribute.java create mode 100644 src/java.base/share/classes/jdk/classfile/attribute/PermittedSubclassesAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RecordAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RecordComponentInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleParameterAnnotationsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleAnnotationsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleParameterAnnotationsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/SignatureAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/SourceDebugExtensionAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/SourceFileAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/SourceIDAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/SyntheticAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/attribute/UnknownAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/AnnotationConstantValueEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/ConstantPool.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/ConstantValueEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/DynamicConstantPoolEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/FieldRefEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/InterfaceMethodRefEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/MemberRefEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/MethodHandleEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/MethodRefEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/MethodTypeEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/ModuleEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/NameAndTypeEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/PackageEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/StringEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/constantpool/Utf8Entry.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AbstractDirectBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AbstractElement.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AbstractUnboundModel.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/BoundRecordComponentInfo.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ChainedClassBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ChainedFieldBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ChainedMethodBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ClassHierarchyImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ClassReaderImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ConcreteBootstrapMethodEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/DirectClassBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/DirectFieldBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/DirectMethodBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/EntryMap.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/InstructionData.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/LabelContext.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/LabelImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/LabelResolver.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/MethodImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/MethodInfo.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/ModuleAttributeBuilderImpl.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/ModuleDescImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/Options.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/PackageDescImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/RawBytecodeHelper.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/SignaturesImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/TerminalCodeBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/TerminalFieldBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/TerminalMethodBuilder.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/TransformImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/Util.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationBytecodes.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationFrame.java create mode 100755 src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationSignature.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationTable.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationType.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.java create mode 100644 src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ArrayLoadInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ConvertInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ExceptionCatch.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/InvokeDynamicInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/LabelTarget.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/LineNumber.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/LookupSwitchInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/MonitorInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/NewMultiArrayInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/NewObjectInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/NewPrimitiveArrayInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/NewReferenceArrayInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/NopInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/OperatorInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/SwitchCase.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/TableSwitchInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/ThrowInstruction.java create mode 100755 src/java.base/share/classes/jdk/classfile/instruction/TypeCheckInstruction.java create mode 100644 src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java create mode 100644 src/java.base/share/classes/jdk/classfile/jdktypes/ModuleDesc.java create mode 100644 src/java.base/share/classes/jdk/classfile/jdktypes/PackageDesc.java create mode 100755 src/java.base/share/classes/jdk/classfile/package-info.java create mode 100755 src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java create mode 100644 src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java create mode 100644 src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java create mode 100644 src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java create mode 100644 src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java create mode 100644 test/jdk/jdk/classfile/AccessFlagsTest.java create mode 100644 test/jdk/jdk/classfile/AdaptCodeTest.java create mode 100644 test/jdk/jdk/classfile/AdvancedTransformationsTest.java create mode 100644 test/jdk/jdk/classfile/AnnotationModelTest.java create mode 100644 test/jdk/jdk/classfile/AnnotationTest.java create mode 100644 test/jdk/jdk/classfile/ArrayTest.java create mode 100644 test/jdk/jdk/classfile/BSMTest.java create mode 100644 test/jdk/jdk/classfile/BasicBlockTest.java create mode 100644 test/jdk/jdk/classfile/BuilderBlockTest.java create mode 100644 test/jdk/jdk/classfile/BuilderParamTest.java create mode 100644 test/jdk/jdk/classfile/ClassHierarchyInfoTest.java create mode 100644 test/jdk/jdk/classfile/ClassPrinterTest.java create mode 100644 test/jdk/jdk/classfile/ConstantPoolCopyTest.java create mode 100644 test/jdk/jdk/classfile/CorpusTest.java create mode 100644 test/jdk/jdk/classfile/LDCTest.java create mode 100644 test/jdk/jdk/classfile/LimitsTest.java create mode 100644 test/jdk/jdk/classfile/LowAdaptTest.java create mode 100644 test/jdk/jdk/classfile/LowJCovAttributeTest.java create mode 100644 test/jdk/jdk/classfile/LowModuleTest.java create mode 100644 test/jdk/jdk/classfile/LvtTest.java create mode 100644 test/jdk/jdk/classfile/MassAdaptCopyCodeTest.java create mode 100644 test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java create mode 100644 test/jdk/jdk/classfile/ModuleBuilderTest.java create mode 100644 test/jdk/jdk/classfile/ModuleDescTest.java create mode 100644 test/jdk/jdk/classfile/OneToOneTest.java create mode 100644 test/jdk/jdk/classfile/OpcodesValidationTest.java create mode 100644 test/jdk/jdk/classfile/PackageDescTest.java create mode 100644 test/jdk/jdk/classfile/ShortJumpsFixTest.java create mode 100644 test/jdk/jdk/classfile/SignaturesTest.java create mode 100644 test/jdk/jdk/classfile/StackMapsTest.java create mode 100644 test/jdk/jdk/classfile/StreamedVsListTest.java create mode 100644 test/jdk/jdk/classfile/TEST.properties create mode 100644 test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java create mode 100644 test/jdk/jdk/classfile/TestRecordComponent.java create mode 100644 test/jdk/jdk/classfile/TransformTests.java create mode 100644 test/jdk/jdk/classfile/Utf8EntryTest.java create mode 100644 test/jdk/jdk/classfile/UtilTest.java create mode 100644 test/jdk/jdk/classfile/VerifierSelfTest.java create mode 100644 test/jdk/jdk/classfile/WriteTest.java create mode 100644 test/jdk/jdk/classfile/examples/AnnotationsExamples.java create mode 100755 test/jdk/jdk/classfile/examples/ExampleGallery.java create mode 100755 test/jdk/jdk/classfile/examples/ExperimentalTransformExamples.java create mode 100644 test/jdk/jdk/classfile/examples/ModuleExamples.java create mode 100755 test/jdk/jdk/classfile/examples/TransformExamples.java create mode 100644 test/jdk/jdk/classfile/helpers/ByteArrayClassLoader.java create mode 100644 test/jdk/jdk/classfile/helpers/ClassRecord.java create mode 100644 test/jdk/jdk/classfile/helpers/CorpusTestHelper.java create mode 100644 test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.java create mode 100644 test/jdk/jdk/classfile/helpers/TestConstants.java create mode 100644 test/jdk/jdk/classfile/helpers/TestUtil.java create mode 100644 test/jdk/jdk/classfile/helpers/Transforms.java create mode 100644 test/jdk/jdk/classfile/testdata/Lvt.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern1.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern10.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern2.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern3.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern4.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern5.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern6.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern7.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern8.java create mode 100644 test/jdk/jdk/classfile/testdata/Pattern9.java create mode 100644 test/jdk/jdk/classfile/testdata/TypeAnnotationPattern.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/AbstractCorpusBenchmark.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/AdHocAdapt.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/AdaptInjectNoop.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/AdaptMetadata.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/AdaptNull.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/ParseOptions.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/ReadDeep.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java create mode 100644 test/micro/org/openjdk/bench/jdk/classfile/TestConstants.java create mode 100644 test/micro/org/openjdk/bench/jdk/classfile/Transforms.java create mode 100755 test/micro/org/openjdk/bench/jdk/classfile/Write.java diff --git a/README.md b/README.md index 399e7cc311f57..17b2c2b90fd1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,21 @@ -# Welcome to the JDK! +# Classfile Processing API for JDK + +Provide an API for parsing, generating, and transforming Java class files. This will initially serve as an internal replacement for ASM in the JDK, to be later opened as a public API. + +See [JEP ???](https://bugs.openjdk.java.net/browse/JDK-8280389) +or [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openjdk/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/Classfile_20Processing_20API/) +for more information about Classfile Processing API. + +See for more information about +the OpenJDK Community and the JDK. + +### Sources + +Classfile Processing API source are a part of java.base JDK module sources: + +- [src/java.base/share/classes/jdk/classfile/](src/java.base/share/classes/jdk/classfile/) + +### Building For build instructions please see the [online documentation](https://openjdk.java.net/groups/build/doc/building.html), @@ -7,5 +24,30 @@ or either of these files: - [doc/building.html](doc/building.html) (html version) - [doc/building.md](doc/building.md) (markdown version) -See for more information about -the OpenJDK Community and the JDK. +### Testing + +Classfile Processing API tests are a part of JDK tests: + +- [test/jdk/jdk/classfile/](test/jdk/jdk/classfile/) + +Test can be selectivelly executed as: + + make test TEST=jdk/classfile + +See [doc/testing.md](doc/testing.md) for more information about JDK testing. + +### Benchmarking + +Classfile Processing API benchmarks are a part of JDK Microbenchmark Suite: + +- [test/micro/org/openjdk/bench/jdk/classfile/](test/micro/org/openjdk/bench/jdk/classfile/) + +Benchmarks can be selectively executed as: + + make test TEST=micro:org.openjdk.bench.jdk.bytecode.+ + +See [JEP 230: Microbenchmark Suite](https://bugs.openjdk.java.net/browse/JDK-8050952) for more information about JDK benchmarks. + +### Use Cases + +See our [development branch](https://github.com/openjdk/jdk-sandbox/tree/classfile-api-dev-branch#use-cases) for actual JDK use cases. diff --git a/make/Docs.gmk b/make/Docs.gmk index 1e99f840c28e5..e230eb9d2fd40 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -106,7 +106,7 @@ JAVADOC_DISABLED_DOCLINT_PACKAGES := org.w3c.* javax.smartcardio JAVADOC_OPTIONS := -use -keywords -notimestamp \ -encoding ISO-8859-1 -docencoding UTF-8 -breakiterator \ -splitIndex --system none -javafx --expand-requires transitive \ - --override-methods=summary + --override-methods=summary -XDignorePreview=true # The reference options must stay stable to allow for comparisons across the # development cycle. @@ -497,6 +497,24 @@ $(eval $(call SetupApiDocsGeneration, REFERENCE_API, \ # Targets generated are returned in REFERENCE_API_JAVADOC_TARGETS and # REFERENCE_API_MODULEGRAPH_TARGETS. +################################################################################ +# Setup generation of Classfile Processing API javadoc + +CLASSFILE_SRC := $(TOPDIR)/src/java.base/share/classes +CLASSFILE_TARGET := $(DOCS_OUTPUTDIR)/classfile-api + +$(eval $(call SetupExecute, CLASSFILE_API_TARGET, \ + DEPS := $(BUILD_TOOLS_JDK), \ + OUTPUT_DIR := $(CLASSFILE_TARGET), \ + SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/docs, \ + COMMAND := $(JAVA) -Djava.awt.headless=true \ + $(NEW_JAVADOC) -d $(CLASSFILE_TARGET) \ + -XDignorePreview=true -notimestamp -public \ + -encoding ISO-8859-1 -docencoding UTF-8 -Xdoclint:none \ + --snippet-path $(CLASSFILE_SRC) \ + $(call FindFiles, $(CLASSFILE_SRC)/jdk/classfile/), \ +)) + ################################################################################ # Use this variable to control which spec files are included in the output. @@ -731,6 +749,8 @@ docs-reference-api-javadoc: $(REFERENCE_API_JAVADOC_TARGETS) $(REFERENCE_API_CUS docs-reference-api-modulegraph: $(REFERENCE_API_MODULEGRAPH_TARGETS) +docs-classfile-api-javadoc: $(CLASSFILE_API_TARGET) + docs-jdk-specs: $(JDK_SPECS_TARGETS) docs-jdk-index: $(JDK_INDEX_TARGETS) @@ -742,9 +762,9 @@ docs-specs-zip: $(SPECS_ZIP_TARGETS) all: docs-jdk-api-javadoc docs-jdk-api-modulegraph docs-javase-api-javadoc \ docs-javase-api-modulegraph docs-reference-api-javadoc \ docs-reference-api-modulegraph docs-jdk-specs docs-jdk-index docs-zip \ - docs-specs-zip + docs-specs-zip docs-classfile-api-javadoc .PHONY: default all docs-jdk-api-javadoc docs-jdk-api-modulegraph \ docs-javase-api-javadoc docs-javase-api-modulegraph \ docs-reference-api-javadoc docs-reference-api-modulegraph docs-jdk-specs \ - docs-jdk-index docs-zip docs-specs-zip + docs-jdk-index docs-zip docs-specs-zip docs-classfile-api-javadoc diff --git a/make/Main.gmk b/make/Main.gmk index 45bed0d045757..ffb7c824bbb82 100644 --- a/make/Main.gmk +++ b/make/Main.gmk @@ -500,6 +500,12 @@ $(eval $(call SetupTarget, docs-reference-api-modulegraph, \ DEPS := buildtools-modules runnable-buildjdk, \ )) +$(eval $(call SetupTarget, docs-classfile-api-javadoc, \ + MAKEFILE := Docs, \ + TARGET := docs-classfile-api-javadoc, \ + DEPS := buildtools-modules runnable-buildjdk, \ +)) + # The gensrc steps for jdk.jdi create html spec files. $(eval $(call SetupTarget, docs-jdk-specs, \ MAKEFILE := Docs, \ diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 349d442f90f58..dc93a5d58861c 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -592,7 +592,14 @@ 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.classfile=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.attribute=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.constantpool=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.jdktypes=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.transforms=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..9fd573751dd06 100644 --- a/make/modules/java.base/Java.gmk +++ b/make/modules/java.base/Java.gmk @@ -25,7 +25,7 @@ DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' -JAVAC_FLAGS += -XDstringConcat=inline +JAVAC_FLAGS += -XDstringConcat=inline -XDignorePreview COPY += .icu .dat .spp .nrm content-types.properties \ hijrah-config-Hijrah-umalqura_islamic-umalqura.properties CLEAN += intrinsic.properties @@ -34,6 +34,7 @@ EXCLUDE_FILES += \ $(TOPDIR)/src/java.base/share/classes/jdk/internal/module/ModuleLoaderMap.java EXCLUDES += java/lang/doc-files +EXCLUDES += jdk/classfile/snippets # 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..e03cb754ecb2c 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -96,6 +96,13 @@ $(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.classfile=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.attribute=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.constantpool=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.jdktypes=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.transforms=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/classfile/AccessFlags.java b/src/java.base/share/classes/jdk/classfile/AccessFlags.java new file mode 100755 index 0000000000000..13045bd462d47 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.util.Set; +import jdk.classfile.impl.AccessFlagsImpl; +import jdk.classfile.jdktypes.AccessFlag; + +/** + * Models the access flags for a class, method, or field. Delivered as a + * {@link jdk.classfile.ClassElement}, {@link jdk.classfile.FieldElement}, or + * {@link jdk.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. + * @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/classfile/Annotation.java b/src/java.base/share/classes/jdk/classfile/Annotation.java new file mode 100644 index 0000000000000..e6c56032a8ab1 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AnnotationImpl; +import jdk.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); + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/AnnotationElement.java b/src/java.base/share/classes/jdk/classfile/AnnotationElement.java new file mode 100644 index 0000000000000..d8fb4bed8027f --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.lang.constant.ClassDesc; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AnnotationImpl; +import jdk.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/classfile/AnnotationValue.java b/src/java.base/share/classes/jdk/classfile/AnnotationValue.java new file mode 100644 index 0000000000000..0817fe36d3fa0 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/AnnotationValue.java @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import jdk.classfile.constantpool.AnnotationConstantValueEntry; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AnnotationImpl; +import jdk.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 AnnotationImpl.OfConstantImpl { + /** {@return the constant} */ + AnnotationConstantValueEntry constant(); + /** {@return the constant} */ + ConstantDesc constantValue(); + } + + /** Models a class-valued element */ + sealed interface OfClass extends AnnotationValue + permits AnnotationImpl.OfClassImpl { + /** {@return the class name} */ + Utf8Entry className(); + } + + /** Models an enum-valued element */ + sealed interface OfEnum extends AnnotationValue + permits AnnotationImpl.OfEnumImpl { + /** {@return the enum class name} */ + Utf8Entry className(); + + /** {@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.OfConstantImpl('s', 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.OfConstantImpl('D', 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.OfConstantImpl('F', 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.OfConstantImpl('J', 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.OfConstantImpl('I', 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.OfConstantImpl('S', 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.OfConstantImpl('C', 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.OfConstantImpl('B', 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.OfConstantImpl('Z', 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.getClass()); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/Attribute.java b/src/java.base/share/classes/jdk/classfile/Attribute.java new file mode 100755 index 0000000000000..a617cc59d5b45 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.attribute.AnnotationDefaultAttribute; +import jdk.classfile.attribute.BootstrapMethodsAttribute; +import jdk.classfile.attribute.CharacterRangeTableAttribute; +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.attribute.CompilationIDAttribute; +import jdk.classfile.attribute.ConstantValueAttribute; +import jdk.classfile.attribute.DeprecatedAttribute; +import jdk.classfile.attribute.EnclosingMethodAttribute; +import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.InnerClassesAttribute; +import jdk.classfile.attribute.LineNumberTableAttribute; +import jdk.classfile.attribute.LocalVariableTableAttribute; +import jdk.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.classfile.attribute.MethodParametersAttribute; +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleHashesAttribute; +import jdk.classfile.attribute.ModuleMainClassAttribute; +import jdk.classfile.attribute.ModulePackagesAttribute; +import jdk.classfile.attribute.ModuleResolutionAttribute; +import jdk.classfile.attribute.ModuleTargetAttribute; +import jdk.classfile.attribute.NestHostAttribute; +import jdk.classfile.attribute.NestMembersAttribute; +import jdk.classfile.attribute.PermittedSubclassesAttribute; +import jdk.classfile.attribute.RecordAttribute; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.attribute.SourceIDAttribute; +import jdk.classfile.attribute.StackMapTableAttribute; +import jdk.classfile.attribute.SyntheticAttribute; +import jdk.classfile.attribute.UnknownAttribute; +import jdk.classfile.impl.BoundAttribute; +import jdk.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/classfile/AttributeMapper.java b/src/java.base/share/classes/jdk/classfile/AttributeMapper.java new file mode 100755 index 0000000000000..74fd47eac0584 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/AttributeMapper.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.classfile; + +import java.util.Set; + +/** + * 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); + + /** + * Indicates in what places in the classfile this attribute can appear. + */ + Set whereApplicable(); + + /** + * {@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/classfile/AttributedElement.java b/src/java.base/share/classes/jdk/classfile/AttributedElement.java new file mode 100755 index 0000000000000..04e4c1708b406 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/AttributedElement.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.classfile; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import jdk.classfile.attribute.RecordComponentInfo; +import jdk.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 kind of the attributed element} + */ + Kind attributedElementKind(); + + /** + * {@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; + } + + /** + * Enum constants describing locations in the classfile where attributes + * are permitted. + */ + enum Kind { + CLASS, METHOD, FIELD, CODE_ATTRIBUTE, RECORD_COMPONENT; + + public static final Set CLASS_ONLY + = Set.of(CLASS); + public static final Set METHOD_ONLY + = Set.of(METHOD); + public static final Set FIELD_ONLY + = Set.of(FIELD); + public static final Set CODE_ONLY + = Set.of(CODE_ATTRIBUTE); + public static final Set EVERYWHERE + = Set.of(CLASS, METHOD, FIELD, CODE_ATTRIBUTE, RECORD_COMPONENT); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/Attributes.java b/src/java.base/share/classes/jdk/classfile/Attributes.java new file mode 100755 index 0000000000000..b025624a62287 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/Attributes.java @@ -0,0 +1,930 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.util.List; +import java.util.Map; + +import jdk.classfile.attribute.AnnotationDefaultAttribute; +import jdk.classfile.attribute.BootstrapMethodsAttribute; +import jdk.classfile.attribute.CharacterRangeInfo; +import jdk.classfile.attribute.CharacterRangeTableAttribute; +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.attribute.CompilationIDAttribute; +import jdk.classfile.attribute.ConstantValueAttribute; +import jdk.classfile.attribute.DeprecatedAttribute; +import jdk.classfile.attribute.EnclosingMethodAttribute; +import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.InnerClassInfo; +import jdk.classfile.attribute.InnerClassesAttribute; +import jdk.classfile.attribute.LineNumberInfo; +import jdk.classfile.attribute.LineNumberTableAttribute; +import jdk.classfile.attribute.LocalVariableInfo; +import jdk.classfile.attribute.LocalVariableTableAttribute; +import jdk.classfile.attribute.LocalVariableTypeInfo; +import jdk.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.classfile.attribute.MethodParameterInfo; +import jdk.classfile.attribute.MethodParametersAttribute; +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleExportInfo; +import jdk.classfile.attribute.ModuleHashInfo; +import jdk.classfile.attribute.ModuleHashesAttribute; +import jdk.classfile.attribute.ModuleMainClassAttribute; +import jdk.classfile.attribute.ModuleOpenInfo; +import jdk.classfile.attribute.ModulePackagesAttribute; +import jdk.classfile.attribute.ModuleProvideInfo; +import jdk.classfile.attribute.ModuleRequireInfo; +import jdk.classfile.attribute.ModuleResolutionAttribute; +import jdk.classfile.attribute.ModuleTargetAttribute; +import jdk.classfile.attribute.NestHostAttribute; +import jdk.classfile.attribute.NestMembersAttribute; +import jdk.classfile.attribute.PermittedSubclassesAttribute; +import jdk.classfile.attribute.RecordAttribute; +import jdk.classfile.attribute.RecordComponentInfo; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.attribute.SourceIDAttribute; +import jdk.classfile.attribute.StackMapTableAttribute; +import jdk.classfile.attribute.SyntheticAttribute; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractAttributeMapper; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.CodeImpl; +import jdk.classfile.impl.ConcreteEntry; + +import static java.util.Map.entry; +import static jdk.classfile.AttributedElement.Kind.CLASS_ONLY; +import static jdk.classfile.AttributedElement.Kind.CODE_ONLY; +import static jdk.classfile.AttributedElement.Kind.EVERYWHERE; +import static jdk.classfile.AttributedElement.Kind.FIELD_ONLY; +import static jdk.classfile.AttributedElement.Kind.METHOD_ONLY; + +/** + * 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 static final int HASH_ANNOTATION_DEFAULT = ConcreteEntry.hashString(NAME_ANNOTATION_DEFAULT.hashCode()); + private static final int HASH_BOOTSTRAP_METHODS = ConcreteEntry.hashString(NAME_BOOTSTRAP_METHODS.hashCode()); + private static final int HASH_CHARACTER_RANGE_TABLE = ConcreteEntry.hashString(NAME_CHARACTER_RANGE_TABLE.hashCode()); + private static final int HASH_CODE = ConcreteEntry.hashString(NAME_CODE.hashCode()); + private static final int HASH_COMPILATION_ID = ConcreteEntry.hashString(NAME_COMPILATION_ID.hashCode()); + private static final int HASH_CONSTANT_VALUE = ConcreteEntry.hashString(NAME_CONSTANT_VALUE.hashCode()); + private static final int HASH_DEPRECATED = ConcreteEntry.hashString(NAME_DEPRECATED.hashCode()); + private static final int HASH_ENCLOSING_METHOD = ConcreteEntry.hashString(NAME_ENCLOSING_METHOD.hashCode()); + private static final int HASH_EXCEPTIONS = ConcreteEntry.hashString(NAME_EXCEPTIONS.hashCode()); + private static final int HASH_INNER_CLASSES = ConcreteEntry.hashString(NAME_INNER_CLASSES.hashCode()); + private static final int HASH_LINE_NUMBER_TABLE = ConcreteEntry.hashString(NAME_LINE_NUMBER_TABLE.hashCode()); + private static final int HASH_LOCAL_VARIABLE_TABLE = ConcreteEntry.hashString(NAME_LOCAL_VARIABLE_TABLE.hashCode()); + private static final int HASH_LOCAL_VARIABLE_TYPE_TABLE = ConcreteEntry.hashString(NAME_LOCAL_VARIABLE_TYPE_TABLE.hashCode()); + private static final int HASH_METHOD_PARAMETERS = ConcreteEntry.hashString(NAME_METHOD_PARAMETERS.hashCode()); + private static final int HASH_MODULE = ConcreteEntry.hashString(NAME_MODULE.hashCode()); + private static final int HASH_MODULE_HASHES = ConcreteEntry.hashString(NAME_MODULE_HASHES.hashCode()); + private static final int HASH_MODULE_MAIN_CLASS = ConcreteEntry.hashString(NAME_MODULE_MAIN_CLASS.hashCode()); + private static final int HASH_MODULE_PACKAGES = ConcreteEntry.hashString(NAME_MODULE_PACKAGES.hashCode()); + private static final int HASH_MODULE_RESOLUTION = ConcreteEntry.hashString(NAME_MODULE_RESOLUTION.hashCode()); + private static final int HASH_MODULE_TARGET = ConcreteEntry.hashString(NAME_MODULE_TARGET.hashCode()); + private static final int HASH_NEST_HOST = ConcreteEntry.hashString(NAME_NEST_HOST.hashCode()); + private static final int HASH_NEST_MEMBERS = ConcreteEntry.hashString(NAME_NEST_MEMBERS.hashCode()); + private static final int HASH_PERMITTED_SUBCLASSES = ConcreteEntry.hashString(NAME_PERMITTED_SUBCLASSES.hashCode()); + private static final int HASH_RECORD = ConcreteEntry.hashString(NAME_RECORD.hashCode()); + private static final int HASH_RUNTIME_INVISIBLE_ANNOTATIONS = ConcreteEntry.hashString(NAME_RUNTIME_INVISIBLE_ANNOTATIONS.hashCode()); + private static final int HASH_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = ConcreteEntry.hashString(NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.hashCode()); + private static final int HASH_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = ConcreteEntry.hashString(NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.hashCode()); + private static final int HASH_RUNTIME_VISIBLE_ANNOTATIONS = ConcreteEntry.hashString(NAME_RUNTIME_VISIBLE_ANNOTATIONS.hashCode()); + private static final int HASH_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = ConcreteEntry.hashString(NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.hashCode()); + private static final int HASH_RUNTIME_VISIBLE_TYPE_ANNOTATIONS = ConcreteEntry.hashString(NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS.hashCode()); + private static final int HASH_SIGNATURE = ConcreteEntry.hashString(NAME_SIGNATURE.hashCode()); + private static final int HASH_SOURCE_DEBUG_EXTENSION = ConcreteEntry.hashString(NAME_SOURCE_DEBUG_EXTENSION.hashCode()); + private static final int HASH_SOURCE_FILE = ConcreteEntry.hashString(NAME_SOURCE_FILE.hashCode()); + private static final int HASH_SOURCE_ID = ConcreteEntry.hashString(NAME_SOURCE_ID.hashCode()); + private static final int HASH_STACK_MAP_TABLE = ConcreteEntry.hashString(NAME_STACK_MAP_TABLE.hashCode()); + private static final int HASH_SYNTHETIC = ConcreteEntry.hashString(NAME_SYNTHETIC.hashCode()); + + private Attributes() { + } + + /** Attribute mapper for the {@code AnnotationDefault} attribute */ + public static final AttributeMapper + ANNOTATION_DEFAULT = new AbstractAttributeMapper<>(NAME_ANNOTATION_DEFAULT, METHOD_ONLY, Classfile.JAVA_5_VERSION) { + @Override + public AnnotationDefaultAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundAnnotationDefaultAttr(e, 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, CLASS_ONLY, Classfile.JAVA_17_VERSION) { + @Override + public BootstrapMethodsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundBootstrapMethodsAttribute(e, 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, CODE_ONLY, true, Classfile.JAVA_4_VERSION) { + @Override + public CharacterRangeTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundCharacterRangeTableAttribute(e, 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, METHOD_ONLY) { + @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, CLASS_ONLY, true) { + @Override + public CompilationIDAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundCompilationIDAttribute(e, 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, FIELD_ONLY) { + @Override + public ConstantValueAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundConstantValueAttribute(e, 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, EVERYWHERE, true) { + @Override + public DeprecatedAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundDeprecatedAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_5_VERSION) { + @Override + public EnclosingMethodAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundEnclosingMethodAttribute(e, 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, METHOD_ONLY) { + @Override + public ExceptionsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundExceptionsAttribute(e, 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, CLASS_ONLY) { + @Override + public InnerClassesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundInnerClassesAttribute(e, 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, CODE_ONLY, true) { + @Override + public LineNumberTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLineNumberTableAttribute(e, 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, CODE_ONLY, 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, CODE_ONLY, 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, METHOD_ONLY, Classfile.JAVA_8_VERSION) { + @Override + public MethodParametersAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundMethodParametersAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_9_VERSION) { + @Override + public ModuleAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_9_VERSION) { + @Override + public ModuleHashesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleHashesAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_9_VERSION) { + @Override + public ModuleMainClassAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleMainClassAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_9_VERSION) { + @Override + public ModulePackagesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModulePackagesAttribute(e, 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, CLASS_ONLY, true, Classfile.JAVA_9_VERSION) { + @Override + public ModuleResolutionAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleResolutionAttribute(e, 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, CLASS_ONLY, true, Classfile.JAVA_9_VERSION) { + @Override + public ModuleTargetAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleTargetAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_11_VERSION) { + @Override + public NestHostAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundNestHostAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_11_VERSION) { + @Override + public NestMembersAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundNestMembersAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_15_VERSION) { + @Override + public PermittedSubclassesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundPermittedSubclassesAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_16_VERSION) { + @Override + public RecordAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRecordAttribute(e, 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, EVERYWHERE, 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, EVERYWHERE, Classfile.JAVA_5_VERSION) { + @Override + public RuntimeInvisibleParameterAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeInvisibleParameterAnnotationsAttribute(e, 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, EVERYWHERE, 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, EVERYWHERE, 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, EVERYWHERE, Classfile.JAVA_5_VERSION) { + @Override + public RuntimeVisibleParameterAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeVisibleParameterAnnotationsAttribute(e, 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, EVERYWHERE, 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, EVERYWHERE, Classfile.JAVA_5_VERSION) { + @Override + public SignatureAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSignatureAttribute(e, 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, CLASS_ONLY, Classfile.JAVA_5_VERSION) { + @Override + public SourceDebugExtensionAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceDebugExtensionAttribute(e, 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, CLASS_ONLY) { + @Override + public SourceFileAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceFileAttribute(e, 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, CLASS_ONLY) { + @Override + public SourceIDAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceIDAttribute(e, 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, CODE_ONLY, Classfile.JAVA_6_VERSION) { + @Override + public StackMapTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundStackMapTableAttribute((CodeModel)e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, StackMapTableAttribute attr) { + throw new AssertionError("should never reach here"); + } + }; + + + /** Attribute mapper for the {@code Synthetic} attribute */ + public static final AttributeMapper + SYNTHETIC = new AbstractAttributeMapper<>(NAME_SYNTHETIC, EVERYWHERE) { + @Override + public SyntheticAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSyntheticAttribute(e, 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) { + int hash = name.hashCode(); + switch (name.length()) { + case 4: + if (hash == HASH_CODE && name.equalsString(NAME_CODE)) + return CODE; + break; + case 6: + if (hash == HASH_MODULE && name.equalsString(NAME_MODULE)) + return MODULE; + else if (hash == HASH_RECORD && name.equalsString(NAME_RECORD)) + return RECORD; + break; + case 8: + if (hash == HASH_NEST_HOST && name.equalsString(NAME_NEST_HOST)) + return NEST_HOST; + else if (hash == HASH_SOURCE_ID && name.equalsString(NAME_SOURCE_ID)) + return SOURCE_ID; + break; + case 9: + if (hash == HASH_SIGNATURE && name.equalsString(NAME_SIGNATURE)) + return SIGNATURE; + else if (hash == HASH_SYNTHETIC && name.equalsString(NAME_SYNTHETIC)) + return SYNTHETIC; + break; + case 10: + if (hash == HASH_DEPRECATED && name.equalsString(NAME_DEPRECATED)) + return DEPRECATED; + else if (hash == HASH_EXCEPTIONS && name.equalsString(NAME_EXCEPTIONS)) + return EXCEPTIONS; + else if (hash == HASH_SOURCE_FILE && name.equalsString(NAME_SOURCE_FILE)) + return SOURCE_FILE; + break; + case 11: + if (hash == HASH_NEST_MEMBERS && name.equalsString(NAME_NEST_MEMBERS)) + return NEST_MEMBERS; + break; + case 12: + if (hash == HASH_INNER_CLASSES && name.equalsString(NAME_INNER_CLASSES)) + return INNER_CLASSES; + else if (hash == HASH_MODULE_HASHES && name.equalsString(NAME_MODULE_HASHES)) + return MODULE_HASHES; + else if (hash == HASH_MODULE_TARGET && name.equalsString(NAME_MODULE_TARGET)) + return MODULE_TARGET; + break; + case 13: + if (hash == HASH_COMPILATION_ID && name.equalsString(NAME_COMPILATION_ID)) + return COMPILATION_ID; + else if (hash == HASH_CONSTANT_VALUE && name.equalsString(NAME_CONSTANT_VALUE)) + return CONSTANT_VALUE; + else if (hash == HASH_STACK_MAP_TABLE && name.equalsString(NAME_STACK_MAP_TABLE)) + return STACK_MAP_TABLE; + break; + case 14: + if (hash == HASH_MODULE_PACKAGES && name.equalsString(NAME_MODULE_PACKAGES)) + return MODULE_PACKAGES; + break; + case 15: + if (hash == HASH_ENCLOSING_METHOD && name.equalsString(NAME_ENCLOSING_METHOD)) + return ENCLOSING_METHOD; + else if (hash == HASH_LINE_NUMBER_TABLE && name.equalsString(NAME_LINE_NUMBER_TABLE)) + return LINE_NUMBER_TABLE; + else if (hash == HASH_MODULE_MAIN_CLASS && name.equalsString(NAME_MODULE_MAIN_CLASS)) + return MODULE_MAIN_CLASS; + break; + case 16: + if (hash == HASH_BOOTSTRAP_METHODS && name.equalsString(NAME_BOOTSTRAP_METHODS)) + return BOOTSTRAP_METHODS; + else if (hash == HASH_METHOD_PARAMETERS && name.equalsString(NAME_METHOD_PARAMETERS)) + return METHOD_PARAMETERS; + else if (hash == HASH_MODULE_RESOLUTION && name.equalsString(NAME_MODULE_RESOLUTION)) + return MODULE_RESOLUTION; + break; + case 17: + if (hash == HASH_ANNOTATION_DEFAULT && name.equalsString(NAME_ANNOTATION_DEFAULT)) + return ANNOTATION_DEFAULT; + break; + case 18: + if (hash == HASH_LOCAL_VARIABLE_TABLE && name.equalsString(NAME_LOCAL_VARIABLE_TABLE)) + return LOCAL_VARIABLE_TABLE; + break; + case 19: + if (hash == HASH_CHARACTER_RANGE_TABLE && name.equalsString(NAME_CHARACTER_RANGE_TABLE)) + return CHARACTER_RANGE_TABLE; + else if (hash == HASH_PERMITTED_SUBCLASSES && name.equalsString(NAME_PERMITTED_SUBCLASSES)) + return PERMITTED_SUBCLASSES; + break; + case 20: + if (hash == HASH_SOURCE_DEBUG_EXTENSION && name.equalsString(NAME_SOURCE_DEBUG_EXTENSION)) + return SOURCE_DEBUG_EXTENSION; + break; + case 22: + if (hash == HASH_LOCAL_VARIABLE_TYPE_TABLE && name.equalsString(NAME_LOCAL_VARIABLE_TYPE_TABLE)) + return LOCAL_VARIABLE_TYPE_TABLE; + break; + case 25: + if (hash == HASH_RUNTIME_VISIBLE_ANNOTATIONS && name.equalsString(NAME_RUNTIME_VISIBLE_ANNOTATIONS)) + return RUNTIME_VISIBLE_ANNOTATIONS; + break; + case 27: + if (hash == HASH_RUNTIME_INVISIBLE_ANNOTATIONS && name.equalsString(NAME_RUNTIME_INVISIBLE_ANNOTATIONS)) + return RUNTIME_INVISIBLE_ANNOTATIONS; + break; + case 29: + if (hash == HASH_RUNTIME_VISIBLE_TYPE_ANNOTATIONS && name.equalsString(NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS)) + return RUNTIME_VISIBLE_TYPE_ANNOTATIONS; + break; + case 31: + if (hash == HASH_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS && name.equalsString(NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS)) + return RUNTIME_INVISIBLE_TYPE_ANNOTATIONS; + break; + case 34: + if (hash == HASH_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS && name.equalsString(NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS)) + return RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS; + break; + case 36: + if (hash == HASH_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS && name.equalsString(NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS)) + return RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS; + break; + } + return null; + } + + /** + * Map from names to attribute mappers for all standard attributes. + */ + public static final Map> PREDEFINED_ATTRIBUTES = Map.ofEntries( + entry(NAME_CONSTANT_VALUE, CONSTANT_VALUE), + entry(NAME_CODE, CODE), + entry(NAME_STACK_MAP_TABLE, STACK_MAP_TABLE), + entry(NAME_EXCEPTIONS, EXCEPTIONS), + entry(NAME_INNER_CLASSES, INNER_CLASSES), + entry(NAME_ENCLOSING_METHOD, ENCLOSING_METHOD), + entry(NAME_SYNTHETIC, SYNTHETIC), + entry(NAME_SIGNATURE, SIGNATURE), + entry(NAME_SOURCE_FILE, SOURCE_FILE), + entry(NAME_SOURCE_DEBUG_EXTENSION, SOURCE_DEBUG_EXTENSION), + entry(NAME_LINE_NUMBER_TABLE, LINE_NUMBER_TABLE), + entry(NAME_LOCAL_VARIABLE_TABLE, LOCAL_VARIABLE_TABLE), + entry(NAME_LOCAL_VARIABLE_TYPE_TABLE, LOCAL_VARIABLE_TYPE_TABLE), + entry(NAME_DEPRECATED, DEPRECATED), + entry(NAME_RUNTIME_VISIBLE_ANNOTATIONS, RUNTIME_VISIBLE_ANNOTATIONS), + entry(NAME_RUNTIME_INVISIBLE_ANNOTATIONS, RUNTIME_INVISIBLE_ANNOTATIONS), + entry(NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS), + entry(NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS), + entry(NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, RUNTIME_VISIBLE_TYPE_ANNOTATIONS), + entry(NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), + entry(NAME_ANNOTATION_DEFAULT, ANNOTATION_DEFAULT), + entry(NAME_BOOTSTRAP_METHODS, BOOTSTRAP_METHODS), + entry(NAME_METHOD_PARAMETERS, METHOD_PARAMETERS), + entry(NAME_MODULE, MODULE), + entry(NAME_MODULE_PACKAGES, MODULE_PACKAGES), + entry(NAME_MODULE_MAIN_CLASS, MODULE_MAIN_CLASS), + entry(NAME_NEST_HOST, NEST_HOST), + entry(NAME_NEST_MEMBERS, NEST_MEMBERS), + entry(NAME_RECORD, RECORD), + entry(NAME_PERMITTED_SUBCLASSES, PERMITTED_SUBCLASSES)); + +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/BootstrapMethodEntry.java b/src/java.base/share/classes/jdk/classfile/BootstrapMethodEntry.java new file mode 100755 index 0000000000000..4f8afd915de58 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.util.List; + +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.impl.ConcreteBootstrapMethodEntry; + +/** + * 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 ConcreteBootstrapMethodEntry { + + /** + * {@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/classfile/BufWriter.java b/src/java.base/share/classes/jdk/classfile/BufWriter.java new file mode 100755 index 0000000000000..4f174f5b0ffa4 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.nio.ByteBuffer; +import java.util.List; + +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.PoolEntry; +import jdk.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/classfile/ClassBuilder.java b/src/java.base/share/classes/jdk/classfile/ClassBuilder.java new file mode 100755 index 0000000000000..a576ba87c759b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassBuilder.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.ChainedClassBuilder; +import jdk.classfile.impl.DirectClassBuilder; +import jdk.classfile.impl.Util; +import jdk.classfile.jdktypes.AccessFlag; + +/** + * 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 {@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(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/classfile/ClassElement.java b/src/java.base/share/classes/jdk/classfile/ClassElement.java new file mode 100755 index 0000000000000..0d710aedd7c2c --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.attribute.CompilationIDAttribute; +import jdk.classfile.attribute.DeprecatedAttribute; +import jdk.classfile.attribute.EnclosingMethodAttribute; +import jdk.classfile.attribute.InnerClassesAttribute; +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleHashesAttribute; +import jdk.classfile.attribute.ModuleMainClassAttribute; +import jdk.classfile.attribute.ModulePackagesAttribute; +import jdk.classfile.attribute.ModuleResolutionAttribute; +import jdk.classfile.attribute.ModuleTargetAttribute; +import jdk.classfile.attribute.NestHostAttribute; +import jdk.classfile.attribute.NestMembersAttribute; +import jdk.classfile.attribute.PermittedSubclassesAttribute; +import jdk.classfile.attribute.RecordAttribute; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.attribute.SourceIDAttribute; +import jdk.classfile.attribute.SyntheticAttribute; +import jdk.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/classfile/ClassHierarchyResolver.java b/src/java.base/share/classes/jdk/classfile/ClassHierarchyResolver.java new file mode 100644 index 0000000000000..706378d3f448c --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassHierarchyResolver.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.classfile; + +import java.io.InputStream; +import java.lang.constant.ClassDesc; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import jdk.classfile.impl.Util; + +import jdk.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} + */ + public static final ClassHierarchyResolver DEFAULT_CLASS_HIERARCHY_RESOLVER + = new ClassHierarchyImpl.CachedClassHierarchyResolver( + new Function() { + @Override + @SuppressWarnings("removal") + public InputStream apply(ClassDesc classDesc) { + return AccessController.doPrivileged(new PrivilegedAction<>() { + @Override + public InputStream run() { + 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/classfile/ClassModel.java b/src/java.base/share/classes/jdk/classfile/ClassModel.java new file mode 100755 index 0000000000000..5253a315cf167 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.impl.ClassImpl; +import jdk.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/classfile/ClassReader.java b/src/java.base/share/classes/jdk/classfile/ClassReader.java new file mode 100755 index 0000000000000..9dc75e4e7e514 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassReader.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.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.Key#ATTRIBUTE_MAPPER}. + */ + Function> customAttributes(); + + /** + * {@return the value corresponding to the specified processing option} + * + * @param option the option key to fetch + * @param the type of the option value (unchecked) + */ + T optionValue(Classfile.Option.Key option); + + // 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 ClassCastException 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 ClassCastException 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 ClassCastException 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 ClassCastException 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 ClassCastException 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/classfile/ClassSignature.java b/src/java.base/share/classes/jdk/classfile/ClassSignature.java new file mode 100644 index 0000000000000..ec87620ac9f1f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassSignature.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.classfile; + +import java.util.List; +import jdk.classfile.impl.SignaturesImpl; +import static java.util.Objects.requireNonNull; +import static jdk.classfile.impl.SignaturesImpl.null2Empty; + +/** + * Models the generic signature of a class, 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 a signature} + * @param superclassSignature the superclass + * @param superinterfaceSignatures the interfaces + */ + public static ClassSignature of(Signature.RefTypeSig superclassSignature, + Signature.RefTypeSig... superinterfaceSignatures) { + return of(null, superclassSignature, superinterfaceSignatures); + } + + /** + * {@return a 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) { + requireNonNull(superclassSignature); + return new SignaturesImpl.ClassSignatureImpl(null2Empty(typeParameters), + superclassSignature, List.of(superinterfaceSignatures)); + } + + /** + * Parses a raw class signature string into a {@linkplain Signature} + * @param signature the raw signature string + * @return the signature + */ + public static ClassSignature parseFrom(String signature) { + requireNonNull(signature); + return new SignaturesImpl().parseClassSignature(signature); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/ClassTransform.java b/src/java.base/share/classes/jdk/classfile/ClassTransform.java new file mode 100755 index 0000000000000..ae8adfc06daf8 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.classfile.attribute.CodeAttribute; +import jdk.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.ClassTransformImpl(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/Classfile.java b/src/java.base/share/classes/jdk/classfile/Classfile.java new file mode 100755 index 0000000000000..2d30fac2a1ac6 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/Classfile.java @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.UnknownAttribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.ClassImpl; +import jdk.classfile.impl.DirectClassBuilder; +import jdk.classfile.impl.Options; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.jdktypes.PackageDesc; + +/** + * Main entry points for parsing, transforming, and generating classfiles. + */ +public class Classfile { + private Classfile() { + } + + /** + * An option that affects the writing of classfiles. + * @param the type of the optional value + */ + public sealed interface Option permits Options.OptionValue { + /** {@return the option key} */ + Key key(); + + /** {@return the option value} */ + V value(); + + /** + * Key values for defined options. + */ + 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; + } + + /** + * {@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<>(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<>(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<>(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<>(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<>(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<>(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<>(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<>(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<>(Key.ATTRIBUTE_MAPPER, r); } + } + + /** + * 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> os = (options == null || options.length == 0) + ? Collections.emptyList() + : List.of(options); + return new ClassImpl(bytes, os); + } + + /** + * Parse a classfile into a {@link ClassModel}. + * @param path the path to the classfile + * @param options the desired processing options + * @return the class model + */ + public static ClassModel parse(Path path, Option... options) throws IOException { + return parse(Files.readAllBytes(path), options); + } + + /** + * Build a classfile into a byte array. + * @param thisClass the name of the class to build + * @param handler a handler that receives a {@link ClassBuilder} + * @return the classfile bytes + */ + public static byte[] build(ClassDesc thisClass, + Consumer handler) { + return build(thisClass, Collections.emptySet(), handler); + } + + /** + * Build a classfile into a byte array. + * @param thisClass the name of the class to build + * @param options the desired processing options + * @param handler a handler that receives a {@link ClassBuilder} + * @return the classfile bytes + */ + public static byte[] build(ClassDesc thisClass, + Collection> options, + Consumer handler) { + ConstantPoolBuilder pool = ConstantPoolBuilder.of(options); + return build(pool.classEntry(thisClass), pool, handler); + } + + /** + * Build a classfile into a byte array using the provided constant pool + * builder (which encapsulates classfile processing options.) + * + * @param thisClassEntry the name of the class to build + * @param constantPool the constant pool builder + * @param handler a handler that receives a {@link ClassBuilder} + * @return the classfile bytes + */ + public static byte[] build(ClassEntry thisClassEntry, + ConstantPoolBuilder constantPool, + Consumer handler) { + thisClassEntry = constantPool.maybeClone(thisClassEntry); + DirectClassBuilder builder = new DirectClassBuilder(constantPool, thisClassEntry); + handler.accept(builder); + return builder.build(); + } + + /** + * Build a classfile into a file. + * @param path the path to the file to write + * @param thisClass the name of the class to build + * @param handler a handler that receives a {@link ClassBuilder} + */ + public static void buildTo(Path path, + ClassDesc thisClass, + Consumer handler) throws IOException { + Files.write(path, build(thisClass, Collections.emptySet(), handler)); + } + + /** + * Build a classfile into a file. + * @param path the path to the file to write + * @param thisClass the name of the class to build + * @param options the desired processing options + * @param handler a handler that receives a {@link ClassBuilder} + */ + public static void buildTo(Path path, + ClassDesc thisClass, + Collection> options, + Consumer handler) throws IOException { + Files.write(path, build(thisClass, options, handler)); + } + + /** + * Build a module descriptor into a byte array. + * @param moduleAttribute the {@code Module} attribute + * @return the classfile bytes + */ + public static byte[] buildModule(ModuleAttribute moduleAttribute) { + return buildModule(moduleAttribute, List.of(), clb -> {}); + } + + /** + * Build a module descriptor into a byte array. + * @param moduleAttribute the {@code Module} attribute + * @param packages additional module packages + * @return the classfile bytes + */ + public static byte[] buildModule(ModuleAttribute moduleAttribute, + List packages) { + return buildModule(moduleAttribute, packages, clb -> {}); + } + + /** + * Build a module descriptor into a byte array. + * @param moduleAttribute the {@code Module} attribute + * @param packages additional module packages + * @param handler a handler that receives a {@link ClassBuilder} + * @return the classfile bytes + */ + public static byte[] buildModule(ModuleAttribute moduleAttribute, + List packages, + Consumer handler) { + return build(ClassDesc.of("module-info"), clb -> { + clb.withFlags(AccessFlag.MODULE); + clb.with(moduleAttribute); + if (!packages.isEmpty()) { + var cp = clb.constantPool(); + var allPackages = new LinkedHashSet(); + for (var exp : moduleAttribute.exports()) allPackages.add(cp.maybeClone(exp.exportedPackage())); + for (var opn : moduleAttribute.opens()) allPackages.add(cp.maybeClone(opn.openedPackage())); + boolean emitMPA = false; + for (var p : packages) + emitMPA |= allPackages.add(cp.packageEntry(p)); + if(emitMPA) + clb.with(new UnboundAttribute.UnboundModulePackagesAttribute(allPackages)); + } + handler.accept(clb); + }); + } + + /** + * Build a module descriptor into a file. + * @param path the file to write + * @param moduleAttribute the {@code Module} attribute + */ + public static void buildModuleTo(Path path, + ModuleAttribute moduleAttribute) throws IOException { + buildModuleTo(path, moduleAttribute, List.of(), clb -> {}); + } + + /** + * Build a module descriptor into a file. + * @param path the file to write + * @param moduleAttribute the {@code Module} attribute + * @param packages additional module packages + */ + public static void buildModuleTo(Path path, + ModuleAttribute moduleAttribute, + List packages) throws IOException { + buildModuleTo(path, moduleAttribute, packages, clb -> {}); + } + + /** + * Build a module descriptor into a file. + * @param path the file to write + * @param moduleAttribute the {@code Module} attribute + * @param packages additional module packages + * @param handler a handler that receives a {@link ClassBuilder} + */ + public static void buildModuleTo(Path path, + ModuleAttribute moduleAttribute, + List packages, + Consumer handler) throws IOException { + Files.write(path, buildModule(moduleAttribute, packages, handler)); + } + + public static final int MAGIC_NUMBER = 0xCAFEBABE; + + public static final int NOP = 0; + public static final int ACONST_NULL = 1; + public static final int ICONST_M1 = 2; + public static final int ICONST_0 = 3; + public static final int ICONST_1 = 4; + public static final int ICONST_2 = 5; + public static final int ICONST_3 = 6; + public static final int ICONST_4 = 7; + public static final int ICONST_5 = 8; + public static final int LCONST_0 = 9; + public static final int LCONST_1 = 10; + public static final int FCONST_0 = 11; + public static final int FCONST_1 = 12; + public static final int FCONST_2 = 13; + public static final int DCONST_0 = 14; + public static final int DCONST_1 = 15; + public static final int BIPUSH = 16; + public static final int SIPUSH = 17; + public static final int LDC = 18; + public static final int LDC_W = 19; + public static final int LDC2_W = 20; + public static final int ILOAD = 21; + public static final int LLOAD = 22; + public static final int FLOAD = 23; + public static final int DLOAD = 24; + public static final int ALOAD = 25; + public static final int ILOAD_0 = 26; + public static final int ILOAD_1 = 27; + public static final int ILOAD_2 = 28; + public static final int ILOAD_3 = 29; + public static final int LLOAD_0 = 30; + public static final int LLOAD_1 = 31; + public static final int LLOAD_2 = 32; + public static final int LLOAD_3 = 33; + public static final int FLOAD_0 = 34; + public static final int FLOAD_1 = 35; + public static final int FLOAD_2 = 36; + public static final int FLOAD_3 = 37; + public static final int DLOAD_0 = 38; + public static final int DLOAD_1 = 39; + public static final int DLOAD_2 = 40; + public static final int DLOAD_3 = 41; + public static final int ALOAD_0 = 42; + public static final int ALOAD_1 = 43; + public static final int ALOAD_2 = 44; + public static final int ALOAD_3 = 45; + public static final int IALOAD = 46; + public static final int LALOAD = 47; + public static final int FALOAD = 48; + public static final int DALOAD = 49; + public static final int AALOAD = 50; + public static final int BALOAD = 51; + public static final int CALOAD = 52; + public static final int SALOAD = 53; + public static final int ISTORE = 54; + public static final int LSTORE = 55; + public static final int FSTORE = 56; + public static final int DSTORE = 57; + public static final int ASTORE = 58; + public static final int ISTORE_0 = 59; + public static final int ISTORE_1 = 60; + public static final int ISTORE_2 = 61; + public static final int ISTORE_3 = 62; + public static final int LSTORE_0 = 63; + public static final int LSTORE_1 = 64; + public static final int LSTORE_2 = 65; + public static final int LSTORE_3 = 66; + public static final int FSTORE_0 = 67; + public static final int FSTORE_1 = 68; + public static final int FSTORE_2 = 69; + public static final int FSTORE_3 = 70; + public static final int DSTORE_0 = 71; + public static final int DSTORE_1 = 72; + public static final int DSTORE_2 = 73; + public static final int DSTORE_3 = 74; + public static final int ASTORE_0 = 75; + public static final int ASTORE_1 = 76; + public static final int ASTORE_2 = 77; + public static final int ASTORE_3 = 78; + public static final int IASTORE = 79; + public static final int LASTORE = 80; + public static final int FASTORE = 81; + public static final int DASTORE = 82; + public static final int AASTORE = 83; + public static final int BASTORE = 84; + public static final int CASTORE = 85; + public static final int SASTORE = 86; + public static final int POP = 87; + public static final int POP2 = 88; + public static final int DUP = 89; + public static final int DUP_X1 = 90; + public static final int DUP_X2 = 91; + public static final int DUP2 = 92; + public static final int DUP2_X1 = 93; + public static final int DUP2_X2 = 94; + public static final int SWAP = 95; + public static final int IADD = 96; + public static final int LADD = 97; + public static final int FADD = 98; + public static final int DADD = 99; + public static final int ISUB = 100; + public static final int LSUB = 101; + public static final int FSUB = 102; + public static final int DSUB = 103; + public static final int IMUL = 104; + public static final int LMUL = 105; + public static final int FMUL = 106; + public static final int DMUL = 107; + public static final int IDIV = 108; + public static final int LDIV = 109; + public static final int FDIV = 110; + public static final int DDIV = 111; + public static final int IREM = 112; + public static final int LREM = 113; + public static final int FREM = 114; + public static final int DREM = 115; + public static final int INEG = 116; + public static final int LNEG = 117; + public static final int FNEG = 118; + public static final int DNEG = 119; + public static final int ISHL = 120; + public static final int LSHL = 121; + public static final int ISHR = 122; + public static final int LSHR = 123; + public static final int IUSHR = 124; + public static final int LUSHR = 125; + public static final int IAND = 126; + public static final int LAND = 127; + public static final int IOR = 128; + public static final int LOR = 129; + public static final int IXOR = 130; + public static final int LXOR = 131; + public static final int IINC = 132; + public static final int I2L = 133; + public static final int I2F = 134; + public static final int I2D = 135; + public static final int L2I = 136; + public static final int L2F = 137; + public static final int L2D = 138; + public static final int F2I = 139; + public static final int F2L = 140; + public static final int F2D = 141; + public static final int D2I = 142; + public static final int D2L = 143; + public static final int D2F = 144; + public static final int I2B = 145; + public static final int I2C = 146; + public static final int I2S = 147; + public static final int LCMP = 148; + public static final int FCMPL = 149; + public static final int FCMPG = 150; + public static final int DCMPL = 151; + public static final int DCMPG = 152; + public static final int IFEQ = 153; + public static final int IFNE = 154; + public static final int IFLT = 155; + public static final int IFGE = 156; + public static final int IFGT = 157; + public static final int IFLE = 158; + public static final int IF_ICMPEQ = 159; + public static final int IF_ICMPNE = 160; + public static final int IF_ICMPLT = 161; + public static final int IF_ICMPGE = 162; + public static final int IF_ICMPGT = 163; + public static final int IF_ICMPLE = 164; + public static final int IF_ACMPEQ = 165; + public static final int IF_ACMPNE = 166; + public static final int GOTO = 167; + public static final int JSR = 168; + public static final int RET = 169; + public static final int TABLESWITCH = 170; + public static final int LOOKUPSWITCH = 171; + public static final int IRETURN = 172; + public static final int LRETURN = 173; + public static final int FRETURN = 174; + public static final int DRETURN = 175; + public static final int ARETURN = 176; + public static final int RETURN = 177; + public static final int GETSTATIC = 178; + public static final int PUTSTATIC = 179; + public static final int GETFIELD = 180; + public static final int PUTFIELD = 181; + public static final int INVOKEVIRTUAL = 182; + public static final int INVOKESPECIAL = 183; + public static final int INVOKESTATIC = 184; + public static final int INVOKEINTERFACE = 185; + public static final int INVOKEDYNAMIC = 186; + public static final int NEW = 187; + public static final int NEWARRAY = 188; + public static final int ANEWARRAY = 189; + public static final int ARRAYLENGTH = 190; + public static final int ATHROW = 191; + public static final int CHECKCAST = 192; + public static final int INSTANCEOF = 193; + public static final int MONITORENTER = 194; + public static final int MONITOREXIT = 195; + public static final int WIDE = 196; + public static final int MULTIANEWARRAY = 197; + public static final int IFNULL = 198; + public static final int IFNONNULL = 199; + public static final int GOTO_W = 200; + public static final int JSR_W = 201; + + public static final int ACC_PUBLIC = 0x0001; + public static final int ACC_PROTECTED = 0x0004; + public static final int ACC_PRIVATE = 0x0002; + public static final int ACC_INTERFACE = 0x0200; + public static final int ACC_ENUM = 0x4000; + public static final int ACC_ANNOTATION = 0x2000; + public static final int ACC_SUPER = 0x0020; + public static final int ACC_ABSTRACT = 0x0400; + public static final int ACC_VOLATILE = 0x0040; + public static final int ACC_TRANSIENT = 0x0080; + public static final int ACC_SYNTHETIC = 0x1000; + public static final int ACC_STATIC = 0x0008; + public static final int ACC_FINAL = 0x0010; + public static final int ACC_SYNCHRONIZED = 0x0020; + public static final int ACC_BRIDGE = 0x0040; + public static final int ACC_VARARGS = 0x0080; + public static final int ACC_NATIVE = 0x0100; + public static final int ACC_STRICT = 0x0800; + public static final int ACC_MODULE = 0x8000; + public static final int ACC_OPEN = 0x20; + public static final int ACC_MANDATED = 0x8000; + public static final int ACC_TRANSITIVE = 0x20; + public static final int ACC_STATIC_PHASE = 0x40; + + public static final int CRT_STATEMENT = 0x0001; + public static final int CRT_BLOCK = 0x0002; + public static final int CRT_ASSIGNMENT = 0x0004; + public static final int CRT_FLOW_CONTROLLER = 0x0008; + public static final int CRT_FLOW_TARGET = 0x0010; + public static final int CRT_INVOKE = 0x0020; + public static final int CRT_CREATE = 0x0040; + public static final int CRT_BRANCH_TRUE = 0x0080; + public static final int CRT_BRANCH_FALSE = 0x0100; + + public static final int TAG_CLASS = 7; + public static final int TAG_CONSTANTDYNAMIC = 17; + public static final int TAG_DOUBLE = 6; + public static final int TAG_FIELDREF = 9; + public static final int TAG_FLOAT = 4; + public static final int TAG_INTEGER = 3; + public static final int TAG_INTERFACEMETHODREF = 11; + public static final int TAG_INVOKEDYNAMIC = 18; + public static final int TAG_LONG = 5; + public static final int TAG_METHODHANDLE = 15; + public static final int TAG_METHODREF = 10; + public static final int TAG_METHODTYPE = 16; + public static final int TAG_MODULE = 19; + public static final int TAG_NAMEANDTYPE = 12; + public static final int TAG_PACKAGE = 20; + public static final int TAG_STRING = 8; + public static final int TAG_UNICODE = 2; + public static final int TAG_UTF8 = 1; + + //type annotations + public static final int TAT_CLASS_TYPE_PARAMETER = 0x00; + public static final int TAT_METHOD_TYPE_PARAMETER = 0x01; + public static final int TAT_CLASS_EXTENDS = 0x10; + public static final int TAT_CLASS_TYPE_PARAMETER_BOUND = 0x11; + public static final int TAT_METHOD_TYPE_PARAMETER_BOUND = 0x12; + public static final int TAT_FIELD = 0x13; + public static final int TAT_METHOD_RETURN = 0x14; + public static final int TAT_METHOD_RECEIVER = 0x15; + public static final int TAT_METHOD_FORMAL_PARAMETER = 0x16; + public static final int TAT_THROWS = 0x17; + public static final int TAT_LOCAL_VARIABLE = 0x40; + public static final int TAT_RESOURCE_VARIABLE = 0x41; + public static final int TAT_EXCEPTION_PARAMETER = 0x42; + public static final int TAT_INSTANCEOF = 0x43; + public static final int TAT_NEW = 0x44; + public static final int TAT_CONSTRUCTOR_REFERENCE = 0x45; + public static final int TAT_METHOD_REFERENCE = 0x46; + public static final int TAT_CAST = 0x47; + public static final int TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + public static final int TAT_METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + public static final int TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + public static final int TAT_METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + //stackmap verification types + public static final int VT_TOP = 0; + public static final int VT_INTEGER = 1; + public static final int VT_FLOAT = 2; + public static final int VT_DOUBLE = 3; + public static final int VT_LONG = 4; + public static final int VT_NULL = 5; + public static final int VT_UNINITIALIZED_THIS = 6; + public static final int VT_OBJECT = 7; + public static final int VT_UNINITIALIZED = 8; + + public static final int DEFAULT_CLASS_FLAGS = ACC_PUBLIC; + + public static final int JAVA_1_VERSION = 45; + public static final int JAVA_2_VERSION = 46; + public static final int JAVA_3_VERSION = 47; + public static final int JAVA_4_VERSION = 48; + public static final int JAVA_5_VERSION = 49; + public static final int JAVA_6_VERSION = 50; + public static final int JAVA_7_VERSION = 51; + public static final int JAVA_8_VERSION = 52; + public static final int JAVA_9_VERSION = 53; + public static final int JAVA_10_VERSION = 54; + public static final int JAVA_11_VERSION = 55; + public static final int JAVA_12_VERSION = 56; + public static final int JAVA_13_VERSION = 57; + public static final int JAVA_14_VERSION = 58; + public static final int JAVA_15_VERSION = 59; + public static final int JAVA_16_VERSION = 60; + public static final int JAVA_17_VERSION = 61; + public static final int JAVA_18_VERSION = 62; + + public static final int LATEST_MAJOR_VERSION = JAVA_17_VERSION; + public static final int LATEST_MINOR_VERSION = 0; + public static final int PREVIEW_MINOR_VERSION = -1; + +} diff --git a/src/java.base/share/classes/jdk/classfile/ClassfileBuilder.java b/src/java.base/share/classes/jdk/classfile/ClassfileBuilder.java new file mode 100755 index 0000000000000..3de46d08a7290 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassfileBuilder.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.classfile; + +import java.lang.constant.ClassDesc; +import java.util.function.Consumer; + +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; + +/** + * A builder for a classfile or portion of a classfile. Builders are rarely + * created directly; they are passed to handlers by methods such as + * {@link Classfile#build(ClassDesc, Consumer)} or to transforms. + * Elements of the newly built entity can be specified + * abstractly (by passing a {@link ClassfileElement} to {@link #with(ClassfileElement)} + * or concretely by calling the various {@code withXxx} methods. + * + * @see ClassfileTransform + */ +public +interface ClassfileBuilder> + extends Consumer { + + /** + * Integrate the {@link ClassfileElement} into the entity being built. + * @param e the element + */ + @Override + default void accept(E e) { + with(e); + } + + /** + * Integrate the {@link ClassfileElement} into the entity being built. + * @param e the element + * @return this builder + */ + B with(E e); + + /** + * {@return the constant pool builder associated with this builder} + */ + ConstantPoolBuilder constantPool(); + + /** + * {@return whether the provided constant pool is compatible with this builder} + * @param source the constant pool to test compatibility with + */ + default boolean canWriteDirect(ConstantPool source) { + return constantPool().canWriteDirect(source); + } + + /** + * Apply a transform to a model, directing results to this builder. + * @param model the model to transform + * @param transform the transform to apply + */ + default void transform(CompoundElement model, ClassfileTransform transform) { + @SuppressWarnings("unchecked") + B builder = (B) this; + var resolved = transform.resolve(builder); + resolved.startHandler().run(); + model.forEachElement(resolved.consumer()); + resolved.endHandler().run(); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/ClassfileElement.java b/src/java.base/share/classes/jdk/classfile/ClassfileElement.java new file mode 100755 index 0000000000000..2a5374dc095e6 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassfileElement.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 jdk.classfile; + +/** + * Immutable model for a portion of (or the entirety of) a classfile. Elements + * that model parts of the classfile that have attributes will implement {@link + * AttributedElement}; elements that model complex parts of the classfile that + * themselves contain their own child elements will implement {@link + * CompoundElement}. Elements specific to various locations in the classfile + * will implement {@link ClassElement}, {@link MethodElement}, etc. + */ +public sealed interface ClassfileElement + permits AttributedElement, CompoundElement, WritableElement, + ClassElement, CodeElement, FieldElement, MethodElement { +} diff --git a/src/java.base/share/classes/jdk/classfile/ClassfileTransform.java b/src/java.base/share/classes/jdk/classfile/ClassfileTransform.java new file mode 100755 index 0000000000000..879a8213acd25 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassfileTransform.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.classfile; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; + +/** + * A transformation on streams of elements. Transforms are used during + * transformation of classfile entities; a transform is provided to a method like + * {@link ClassModel#transform(ClassTransform)}, and the elements of the class, + * along with a builder, are presented to the transform. + * + *

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.) + * + * @@@ Add stateful transform snippet + * + * @@@ Add examples of chaining + */ +sealed public 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/classfile/ClassfileVersion.java b/src/java.base/share/classes/jdk/classfile/ClassfileVersion.java new file mode 100755 index 0000000000000..bf66e13ea05ec --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.impl.ClassfileVersionImpl; + +/** + * Models the classfile version information for a class. Delivered as a {@link + * jdk.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/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java new file mode 100755 index 0000000000000..3cd1e287022e1 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -0,0 +1,1258 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +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.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BlockCodeBuilder; +import jdk.classfile.impl.BytecodeHelpers; +import jdk.classfile.impl.ChainedCodeBuilder; +import jdk.classfile.impl.LabelImpl; +import jdk.classfile.impl.LineNumberImpl; +import jdk.classfile.impl.NonterminalCodeBuilder; +import jdk.classfile.impl.TerminalCodeBuilder; +import jdk.classfile.instruction.ArrayLoadInstruction; +import jdk.classfile.instruction.ArrayStoreInstruction; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.ConvertInstruction; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.InvokeDynamicInstruction; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.MonitorInstruction; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewObjectInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import jdk.classfile.instruction.NopInstruction; +import jdk.classfile.instruction.OperatorInstruction; +import jdk.classfile.instruction.ReturnInstruction; +import jdk.classfile.instruction.StackInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.SwitchCase; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.instruction.ThrowInstruction; +import jdk.classfile.instruction.TypeCheckInstruction; + +import static java.util.Objects.requireNonNull; +import static jdk.classfile.impl.BytecodeHelpers.handleDescToHandleInfo; + +/** + * 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 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 bytecode offset associated with the specified label} */ + int labelToBci(Label label); + + /** + * {@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); + + /** + * 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. + */ + default CodeBuilder block(Consumer handler) { + BlockCodeBuilder child = new BlockCodeBuilder(this); + child.start(); + handler.accept(child); + child.end(); + return this; + } + + /** + * Add an "if-then" block that is conditional on the boolean value + * on top of the operand stack. + * + * @param thenHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the {@code if} + * @return this builder + */ + default CodeBuilder ifThen(Consumer thenHandler) { + BlockCodeBuilder thenBlock = new BlockCodeBuilder(this); + branchInstruction(Opcode.IFEQ, thenBlock.endLabel()); + thenBlock.start(); + thenHandler.accept(thenBlock); + thenBlock.end(); + return this; + } + + /** + * Add an "if-then-else" block that is conditional on the boolean value + * on top of the operand stack. + * + * @param thenHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the {@code if} + * @param elseHandler handler that receives a {@linkplain CodeBuilder} to + * generate the body of the {@code else} + * @return this builder + */ + default CodeBuilder ifThenElse(Consumer thenHandler, + Consumer elseHandler) { + BlockCodeBuilder thenBlock = new BlockCodeBuilder(this); + BlockCodeBuilder elseBlock = new BlockCodeBuilder(this); + branchInstruction(Opcode.IFEQ, elseBlock.startLabel()); + thenBlock.start(); + thenHandler.accept(thenBlock); + if (thenBlock.reachable()) + thenBlock.branchInstruction(Opcode.GOTO, elseBlock.endLabel()); + thenBlock.end(); + elseBlock.start(); + elseHandler.accept(elseBlock); + elseBlock.end(); + 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) { + if (opcode == Opcode.INVOKEINTERFACE) { + with(InvokeInstruction.of(opcode, constantPool().interfaceMethodRefEntry(owner, name, desc))); + } else { + MemberRefEntry ref = isInterface + ? constantPool().interfaceMethodRefEntry(owner, name, desc) + : constantPool().methodRefEntry(owner, name, desc); + with(InvokeInstruction.of(opcode, ref)); + } + return this; + } + + 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().natEntry(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) { + if (opcode == null) // Infer opcode + return constantInstruction(value); + + BytecodeHelpers.validateValue(opcode, value); + + if (opcode == Opcode.ACONST_NULL) { + with(ConstantInstruction.ofIntrinsic(Opcode.ACONST_NULL)); + } + else if (opcode == Opcode.SIPUSH && value instanceof Integer iVal) { + with(ConstantInstruction.ofArgument(Opcode.SIPUSH, iVal)); + } + else if (opcode == Opcode.SIPUSH && value instanceof Long lVal) { + with(ConstantInstruction.ofArgument(Opcode.SIPUSH, (int) (lVal & (long)0xffff))); + } + else if (opcode == Opcode.BIPUSH && value instanceof Integer iVal) { + with(ConstantInstruction.ofArgument(Opcode.BIPUSH, iVal)); + } + else if (opcode == Opcode.BIPUSH && value instanceof Long lVal) { + with(ConstantInstruction.ofArgument(Opcode.BIPUSH, (int) (lVal & (long)0xff))); + } + else if (opcode == Opcode.LDC || opcode == Opcode.LDC_W || opcode == Opcode.LDC2_W) { + with(ConstantInstruction.ofLoad(opcode, BytecodeHelpers.constantEntry(constantPool(), value))); + } + else { + Opcode known = BytecodeHelpers.constantsToOpcodes.get(value); + if (known == null) + throw new AssertionError("Couldn't determine which opcode to use to load " + value); + with(ConstantInstruction.ofIntrinsic(known)); + } + + return this; + } + + default CodeBuilder constantInstruction(int value) { + return with(switch (value) { + case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); + case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); + case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); + case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); + case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); + case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); + case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); + default -> { + if (value >= -128 && value <= 127) { + yield ConstantInstruction.ofArgument(Opcode.BIPUSH, value); + } + else if (value >= -32768 && value <= 32767) { + yield ConstantInstruction.ofArgument(Opcode.SIPUSH, value); + } + else { + yield ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), value)); + } + } + }); + } + + default CodeBuilder constantInstruction(ConstantDesc value) { + // This method must ensure any call to constant(Opcode, ConstantDesc) has a non-null Opcode. + if (value == null) { + return constantInstruction(Opcode.ACONST_NULL, null); + } + else if (value instanceof Integer iVal) { + return constantInstruction((int)iVal); + } else if (value instanceof Long lVal) { + if (lVal == 0) + return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_0)); + else if (lVal == 1) + return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_1)); + else + return constantInstruction(Opcode.LDC2_W, lVal); + } else if (value instanceof Float fVal) { + if (fVal == 0.0) + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_0)); + else if (fVal == 1.0) + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_1)); + else if (fVal == 2.0) + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_2)); + else + return constantInstruction(Opcode.LDC, fVal); + } else if (value instanceof Double dVal) { + if (dVal == 0.0d) + return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_0)); + else if (dVal == 1.0d) + return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_1)); + else + return constantInstruction(Opcode.LDC2_W, dVal); + } else { + return constantInstruction(Opcode.LDC, value); + } + } + + default CodeBuilder monitorInstruction(Opcode opcode) { + with(MonitorInstruction.of(opcode)); + return null; + } + + default CodeBuilder nopInstruction() { + with(NopInstruction.of()); + return this; + } + + + // 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(LineNumberImpl.of(line)); + return this; + } + + default CodeBuilder exceptionCatch(Label start, Label end, Label handler, ClassEntry catchType) { + requireNonNull(catchType); + with(ExceptionCatch.of(handler, start, end, catchType)); + return this; + } + + default CodeBuilder exceptionCatch(Label start, Label end, Label handler, Optional catchType) { + return catchType.isPresent() + ? exceptionCatch(start, end, handler, catchType.get()) + : exceptionCatchAll(start, end, handler); + } + + 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(new AbstractInstruction.UnboundCharacterRange(startScope, endScope, characterRangeStart, characterRangeEnd, flags)); + return this; + } + + default CodeBuilder localVariable(int slot, Utf8Entry nameEntry, Utf8Entry descriptorEntry, Label startScope, Label endScope) { + with(new AbstractInstruction.UnboundLocalVariable(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(new AbstractInstruction.UnboundLocalVariableType(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 constantInstruction(Opcode.ACONST_NULL, 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 constantInstruction(Opcode.DCONST_0, 0.0d); + } + + default CodeBuilder dconst_1() { + return constantInstruction(Opcode.DCONST_1, 1.0d); + } + + 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 constantInstruction(Opcode.FCONST_0, 0.0f); + } + + default CodeBuilder fconst_1() { + return constantInstruction(Opcode.FCONST_1, 1.0f); + } + + default CodeBuilder fconst_2() { + return constantInstruction(Opcode.FCONST_2, 2.0f); + } + + 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 constantInstruction(Opcode.ICONST_0, 0); + } + + default CodeBuilder iconst_1() { + return constantInstruction(Opcode.ICONST_1, 1); + } + + default CodeBuilder iconst_2() { + return constantInstruction(Opcode.ICONST_2, 2); + } + + default CodeBuilder iconst_3() { + return constantInstruction(Opcode.ICONST_3, 3); + } + + default CodeBuilder iconst_4() { + return constantInstruction(Opcode.ICONST_4, 4); + } + + default CodeBuilder iconst_5() { + return constantInstruction(Opcode.ICONST_5, 5); + } + + default CodeBuilder iconst_m1() { + return constantInstruction(Opcode.ICONST_M1, -1); + } + + 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_ACMPNE, 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)); + } + + // @@@ Other overloads? + 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) { + with(InvokeInstruction.of(Opcode.INVOKEINTERFACE, constantPool().interfaceMethodRefEntry(owner, name, type))); + return this; + } + + default CodeBuilder invokespecial(InterfaceMethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKESPECIAL, ref); + } + + default CodeBuilder invokespecial(ClassDesc owner, String name, MethodTypeDesc type) { + return invokespecial(owner, name, type, false); + } + + default CodeBuilder invokespecial(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { + MemberRefEntry ref = isInterface + ? constantPool().interfaceMethodRefEntry(owner, name, type) + : constantPool().methodRefEntry(owner, name, type); + with(InvokeInstruction.of(Opcode.INVOKESPECIAL, ref)); + return this; + } + + default CodeBuilder invokestatic(InterfaceMethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKESTATIC, ref); + } + + default CodeBuilder invokestatic(ClassDesc owner, String name, MethodTypeDesc type) { + return invokestatic(owner, name, type, false); + } + + default CodeBuilder invokestatic(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { + MemberRefEntry ref = isInterface + ? constantPool().interfaceMethodRefEntry(owner, name, type) + : constantPool().methodRefEntry(owner, name, type); + with(InvokeInstruction.of(Opcode.INVOKESTATIC, ref)); + return this; + } + + default CodeBuilder invokevirtual(InterfaceMethodRefEntry ref) { + return invokeInstruction(Opcode.INVOKEVIRTUAL, ref); + } + + default CodeBuilder invokevirtual(ClassDesc owner, String name, MethodTypeDesc type) { + return invokevirtual(owner, name, type, false); + } + + default CodeBuilder invokevirtual(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { + MemberRefEntry ref = isInterface + ? constantPool().interfaceMethodRefEntry(owner, name, type) + : constantPool().methodRefEntry(owner, name, type); + with(InvokeInstruction.of(Opcode.INVOKEVIRTUAL, ref)); + return this; + } + + 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 constantInstruction(Opcode.LCONST_0, 0L); + } + + default CodeBuilder lconst_1() { + return constantInstruction(Opcode.LCONST_1, 1L); + } + + 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 operatorInstruction(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/classfile/CodeElement.java b/src/java.base/share/classes/jdk/classfile/CodeElement.java new file mode 100755 index 0000000000000..86e8d09301093 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/CodeElement.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.classfile; + +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.impl.AbstractInstruction; + +/** + * 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, AbstractInstruction, + CustomAttribute, RuntimeVisibleTypeAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute { + /** + * {@return the kind of this instruction} + */ + Kind codeKind(); + + /** + * {@return the opcode of this instruction} + */ + Opcode opcode(); + + /** + * {@return the size in bytes of this instruction} + */ + int sizeInBytes(); + + /** + * Kinds of instructions. + */ + 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, + LABEL_TARGET, EXCEPTION_CATCH, CHARACTER_RANGE, LOCAL_VARIABLE, LOCAL_VARIABLE_TYPE, LINE_NUMBER, + PARAMETER_ANNOTATION, TYPE_ANNOTATION, END; + } + +} diff --git a/src/java.base/share/classes/jdk/classfile/CodeModel.java b/src/java.base/share/classes/jdk/classfile/CodeModel.java new file mode 100755 index 0000000000000..a983a15c4edfc --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/CodeModel.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.classfile; + +import java.util.List; +import java.util.Optional; + +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.impl.BufferedCodeBuilder; +import jdk.classfile.impl.CodeImpl; +import jdk.classfile.impl.LabelResolver; +import jdk.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, LabelResolver + 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/classfile/CodeTransform.java b/src/java.base/share/classes/jdk/classfile/CodeTransform.java new file mode 100755 index 0000000000000..f809ca0297b7f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/CodeTransform.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.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. + */ + public static final 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.CodeTransformImpl(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/CompoundElement.java b/src/java.base/share/classes/jdk/classfile/CompoundElement.java new file mode 100755 index 0000000000000..aa3ce0b440111 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.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.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/classfile/CustomAttribute.java b/src/java.base/share/classes/jdk/classfile/CustomAttribute.java new file mode 100755 index 0000000000000..e264cffea6097 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.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/classfile/FieldBuilder.java b/src/java.base/share/classes/jdk/classfile/FieldBuilder.java new file mode 100755 index 0000000000000..bf04f93c68f2e --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.ChainedFieldBuilder; +import jdk.classfile.impl.TerminalFieldBuilder; +import jdk.classfile.jdktypes.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/classfile/FieldElement.java b/src/java.base/share/classes/jdk/classfile/FieldElement.java new file mode 100755 index 0000000000000..2528ad333874a --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.attribute.ConstantValueAttribute; +import jdk.classfile.attribute.DeprecatedAttribute; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.attribute.SyntheticAttribute; +import jdk.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/classfile/FieldModel.java b/src/java.base/share/classes/jdk/classfile/FieldModel.java new file mode 100755 index 0000000000000..90956785b7c52 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.lang.constant.ClassDesc; +import java.util.Optional; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BufferedFieldBuilder; +import jdk.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 descriptorSymbol() { + return ClassDesc.ofDescriptor(fieldType().stringValue()); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/FieldTransform.java b/src/java.base/share/classes/jdk/classfile/FieldTransform.java new file mode 100755 index 0000000000000..3e0801c450bc4 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/FieldTransform.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.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.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. + */ + public static final 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); + }; + } + + default FieldTransform andThen(FieldTransform t) { + return new TransformImpl.ChainedFieldTransform(this, t); + } + + @Override + default ResolvedTransform resolve(FieldBuilder builder) { + return new TransformImpl.FieldTransformImpl(e -> accept(builder, e), + () -> atEnd(builder), + () -> atStart(builder)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/Instruction.java b/src/java.base/share/classes/jdk/classfile/Instruction.java new file mode 100755 index 0000000000000..b8402894c3239 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/Instruction.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.classfile; + +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.instruction.ArrayLoadInstruction; +import jdk.classfile.instruction.ArrayStoreInstruction; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.ConvertInstruction; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.InvokeDynamicInstruction; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.MonitorInstruction; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewObjectInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import jdk.classfile.instruction.NopInstruction; +import jdk.classfile.instruction.OperatorInstruction; +import jdk.classfile.instruction.ReturnInstruction; +import jdk.classfile.instruction.StackInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.instruction.ThrowInstruction; +import jdk.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 { + +} diff --git a/src/java.base/share/classes/jdk/classfile/Interfaces.java b/src/java.base/share/classes/jdk/classfile/Interfaces.java new file mode 100755 index 0000000000000..7cf9c445c6d5c --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.InterfacesImpl; +import jdk.classfile.impl.Util; + +/** + * Models the interfaces of a class. Delivered as a {@link + * jdk.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/classfile/Label.java b/src/java.base/share/classes/jdk/classfile/Label.java new file mode 100755 index 0000000000000..f2a5dae26f70c --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.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/classfile/MethodBuilder.java b/src/java.base/share/classes/jdk/classfile/MethodBuilder.java new file mode 100755 index 0000000000000..8d55b32bfd733 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.ChainedMethodBuilder; +import jdk.classfile.impl.TerminalMethodBuilder; +import jdk.classfile.jdktypes.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/classfile/MethodElement.java b/src/java.base/share/classes/jdk/classfile/MethodElement.java new file mode 100755 index 0000000000000..badd1b98460a3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.attribute.AnnotationDefaultAttribute; +import jdk.classfile.attribute.DeprecatedAttribute; +import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.MethodParametersAttribute; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.attribute.SyntheticAttribute; +import jdk.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/classfile/MethodModel.java b/src/java.base/share/classes/jdk/classfile/MethodModel.java new file mode 100755 index 0000000000000..125d6785c9a58 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.lang.constant.MethodTypeDesc; +import java.util.Optional; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BufferedMethodBuilder; +import jdk.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 descriptorSymbol() { + return MethodTypeDesc.ofDescriptor(methodType().stringValue()); + } + + /** {@return the body of this method, if there is one} */ + Optional code(); +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/MethodSignature.java b/src/java.base/share/classes/jdk/classfile/MethodSignature.java new file mode 100644 index 0000000000000..a079fb2c146e3 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/MethodSignature.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import jdk.classfile.impl.SignaturesImpl; +import static java.util.Objects.requireNonNull; +import static jdk.classfile.impl.SignaturesImpl.null2Empty; +import jdk.classfile.impl.Util; + +/** + * Models the generic signature of a class, 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 a signature for a raw (no generic information) method descriptor} + * @param descriptor the method descriptor + */ + public static MethodSignature of(MethodTypeDesc descriptor) { + requireNonNull(descriptor); + // @@@ MethodReference Signature::of + // @@@ Input to Util.mappedList is immutable therefore so is result + return new SignaturesImpl.MethodSignatureImpl(List.of(), + Util.mappedList(descriptor.parameterList(), Signature::of), + Signature.of(descriptor.returnType()), + List.of()); + } + + /** + * {@return a signature} + * @param arguments signatures for the method arguments + * @param result signature for the return type + */ + public static MethodSignature of(List arguments, Signature result) { + return of(null, arguments, result, null); + } + + /** + * {@return a signature} + * @param arguments signatures for the method arguments + * @param result signature for the return type + * @param exceptions sigantures for the exceptions + */ + public static MethodSignature of(List arguments, + Signature result, + List exceptions) { + return of(null, arguments, result, exceptions); + } + + /** + * {@return a signature} + * @param typeParameters signatures for the type parameters + * @param arguments signatures for the method arguments + * @param result signature for the return type + */ + public static MethodSignature of(List typeParameters, + List arguments, + Signature result) { + return of(typeParameters, arguments, result, null); + } + + /** + * {@return a signature} + * @param typeParameters signatures for the type parameters + * @param arguments signatures for the method arguments + * @param result signature for the return type + * @param exceptions sigantures for the exceptions + */ + public static MethodSignature of(List typeParameters, + List arguments, + Signature result, + List exceptions) { + requireNonNull(result); + return new SignaturesImpl.MethodSignatureImpl(null2Empty(typeParameters), + null2Empty(arguments), result, null2Empty(exceptions)); + } + + /** + * Parses a raw method signature string into a {@linkplain Signature} + * @param signature the raw signature string + * @return the signature + */ + public static MethodSignature parseFrom(String signature) { + requireNonNull(signature); + return new SignaturesImpl().parseMethodSignature(signature); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/MethodTransform.java b/src/java.base/share/classes/jdk/classfile/MethodTransform.java new file mode 100755 index 0000000000000..ebc5d5b70ed89 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.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. + */ + public static final 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.MethodTransformImpl(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/classfile/Opcode.java b/src/java.base/share/classes/jdk/classfile/Opcode.java new file mode 100755 index 0000000000000..c8f73af924e03 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/Opcode.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.lang.constant.ConstantDesc; + +/** + * 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, CodeElement.Kind.NOP), + ACONST_NULL(Classfile.ACONST_NULL, 1, CodeElement.Kind.CONSTANT, TypeKind.ReferenceType), + ICONST_M1(Classfile.ICONST_M1, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, -1), + ICONST_0(Classfile.ICONST_0, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 0), + ICONST_1(Classfile.ICONST_1, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 1), + ICONST_2(Classfile.ICONST_2, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 2), + ICONST_3(Classfile.ICONST_3, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 3), + ICONST_4(Classfile.ICONST_4, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 4), + ICONST_5(Classfile.ICONST_5, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 5), + LCONST_0(Classfile.LCONST_0, 1, CodeElement.Kind.CONSTANT, TypeKind.LongType, 0, 0L), + LCONST_1(Classfile.LCONST_1, 1, CodeElement.Kind.CONSTANT, TypeKind.LongType, 0, 1L), + FCONST_0(Classfile.FCONST_0, 1, CodeElement.Kind.CONSTANT, TypeKind.FloatType, 0, 0.0f), + FCONST_1(Classfile.FCONST_1, 1, CodeElement.Kind.CONSTANT, TypeKind.FloatType, 0, 1.0f), + FCONST_2(Classfile.FCONST_2, 1, CodeElement.Kind.CONSTANT, TypeKind.FloatType, 0, 2.0f), + DCONST_0(Classfile.DCONST_0, 1, CodeElement.Kind.CONSTANT, TypeKind.DoubleType, 0, 0.0d), + DCONST_1(Classfile.DCONST_1, 1, CodeElement.Kind.CONSTANT, TypeKind.DoubleType, 0, 1.0d), + BIPUSH(Classfile.BIPUSH, 2, CodeElement.Kind.CONSTANT, TypeKind.ByteType), + SIPUSH(Classfile.SIPUSH, 3, CodeElement.Kind.CONSTANT, TypeKind.ShortType), + LDC(Classfile.LDC, 2, CodeElement.Kind.CONSTANT), + LDC_W(Classfile.LDC_W, 3, CodeElement.Kind.CONSTANT), + LDC2_W(Classfile.LDC2_W, 3, CodeElement.Kind.CONSTANT), + ILOAD(Classfile.ILOAD, 2, CodeElement.Kind.LOAD, TypeKind.IntType, -1), + LLOAD(Classfile.LLOAD, 2, CodeElement.Kind.LOAD, TypeKind.LongType, -1), + FLOAD(Classfile.FLOAD, 2, CodeElement.Kind.LOAD, TypeKind.FloatType, -1), + DLOAD(Classfile.DLOAD, 2, CodeElement.Kind.LOAD, TypeKind.DoubleType, -1), + ALOAD(Classfile.ALOAD, 2, CodeElement.Kind.LOAD, TypeKind.ReferenceType, -1), + ILOAD_0(Classfile.ILOAD_0, 1, CodeElement.Kind.LOAD, TypeKind.IntType, 0), + ILOAD_1(Classfile.ILOAD_1, 1, CodeElement.Kind.LOAD, TypeKind.IntType, 1), + ILOAD_2(Classfile.ILOAD_2, 1, CodeElement.Kind.LOAD, TypeKind.IntType, 2), + ILOAD_3(Classfile.ILOAD_3, 1, CodeElement.Kind.LOAD, TypeKind.IntType, 3), + LLOAD_0(Classfile.LLOAD_0, 1, CodeElement.Kind.LOAD, TypeKind.LongType, 0), + LLOAD_1(Classfile.LLOAD_1, 1, CodeElement.Kind.LOAD, TypeKind.LongType, 1), + LLOAD_2(Classfile.LLOAD_2, 1, CodeElement.Kind.LOAD, TypeKind.LongType, 2), + LLOAD_3(Classfile.LLOAD_3, 1, CodeElement.Kind.LOAD, TypeKind.LongType, 3), + FLOAD_0(Classfile.FLOAD_0, 1, CodeElement.Kind.LOAD, TypeKind.FloatType, 0), + FLOAD_1(Classfile.FLOAD_1, 1, CodeElement.Kind.LOAD, TypeKind.FloatType, 1), + FLOAD_2(Classfile.FLOAD_2, 1, CodeElement.Kind.LOAD, TypeKind.FloatType, 2), + FLOAD_3(Classfile.FLOAD_3, 1, CodeElement.Kind.LOAD, TypeKind.FloatType, 3), + DLOAD_0(Classfile.DLOAD_0, 1, CodeElement.Kind.LOAD, TypeKind.DoubleType, 0), + DLOAD_1(Classfile.DLOAD_1, 1, CodeElement.Kind.LOAD, TypeKind.DoubleType, 1), + DLOAD_2(Classfile.DLOAD_2, 1, CodeElement.Kind.LOAD, TypeKind.DoubleType, 2), + DLOAD_3(Classfile.DLOAD_3, 1, CodeElement.Kind.LOAD, TypeKind.DoubleType, 3), + ALOAD_0(Classfile.ALOAD_0, 1, CodeElement.Kind.LOAD, TypeKind.ReferenceType, 0), + ALOAD_1(Classfile.ALOAD_1, 1, CodeElement.Kind.LOAD, TypeKind.ReferenceType, 1), + ALOAD_2(Classfile.ALOAD_2, 1, CodeElement.Kind.LOAD, TypeKind.ReferenceType, 2), + ALOAD_3(Classfile.ALOAD_3, 1, CodeElement.Kind.LOAD, TypeKind.ReferenceType, 3), + IALOAD(Classfile.IALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.IntType), + LALOAD(Classfile.LALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.LongType), + FALOAD(Classfile.FALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.FloatType), + DALOAD(Classfile.DALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.DoubleType), + AALOAD(Classfile.AALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.ReferenceType), + BALOAD(Classfile.BALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.ByteType), + CALOAD(Classfile.CALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.CharType), + SALOAD(Classfile.SALOAD, 1, CodeElement.Kind.ARRAY_LOAD, TypeKind.ShortType), + ISTORE(Classfile.ISTORE, 2, CodeElement.Kind.STORE, TypeKind.IntType, -1), + LSTORE(Classfile.LSTORE, 2, CodeElement.Kind.STORE, TypeKind.LongType, -1), + FSTORE(Classfile.FSTORE, 2, CodeElement.Kind.STORE, TypeKind.FloatType, -1), + DSTORE(Classfile.DSTORE, 2, CodeElement.Kind.STORE, TypeKind.DoubleType, -1), + ASTORE(Classfile.ASTORE, 2, CodeElement.Kind.STORE, TypeKind.ReferenceType, -1), + ISTORE_0(Classfile.ISTORE_0, 1, CodeElement.Kind.STORE, TypeKind.IntType, 0), + ISTORE_1(Classfile.ISTORE_1, 1, CodeElement.Kind.STORE, TypeKind.IntType, 1), + ISTORE_2(Classfile.ISTORE_2, 1, CodeElement.Kind.STORE, TypeKind.IntType, 2), + ISTORE_3(Classfile.ISTORE_3, 1, CodeElement.Kind.STORE, TypeKind.IntType, 3), + LSTORE_0(Classfile.LSTORE_0, 1, CodeElement.Kind.STORE, TypeKind.LongType, 0), + LSTORE_1(Classfile.LSTORE_1, 1, CodeElement.Kind.STORE, TypeKind.LongType, 1), + LSTORE_2(Classfile.LSTORE_2, 1, CodeElement.Kind.STORE, TypeKind.LongType, 2), + LSTORE_3(Classfile.LSTORE_3, 1, CodeElement.Kind.STORE, TypeKind.LongType, 3), + FSTORE_0(Classfile.FSTORE_0, 1, CodeElement.Kind.STORE, TypeKind.FloatType, 0), + FSTORE_1(Classfile.FSTORE_1, 1, CodeElement.Kind.STORE, TypeKind.FloatType, 1), + FSTORE_2(Classfile.FSTORE_2, 1, CodeElement.Kind.STORE, TypeKind.FloatType, 2), + FSTORE_3(Classfile.FSTORE_3, 1, CodeElement.Kind.STORE, TypeKind.FloatType, 3), + DSTORE_0(Classfile.DSTORE_0, 1, CodeElement.Kind.STORE, TypeKind.DoubleType, 0), + DSTORE_1(Classfile.DSTORE_1, 1, CodeElement.Kind.STORE, TypeKind.DoubleType, 1), + DSTORE_2(Classfile.DSTORE_2, 1, CodeElement.Kind.STORE, TypeKind.DoubleType, 2), + DSTORE_3(Classfile.DSTORE_3, 1, CodeElement.Kind.STORE, TypeKind.DoubleType, 3), + ASTORE_0(Classfile.ASTORE_0, 1, CodeElement.Kind.STORE, TypeKind.ReferenceType, 0), + ASTORE_1(Classfile.ASTORE_1, 1, CodeElement.Kind.STORE, TypeKind.ReferenceType, 1), + ASTORE_2(Classfile.ASTORE_2, 1, CodeElement.Kind.STORE, TypeKind.ReferenceType, 2), + ASTORE_3(Classfile.ASTORE_3, 1, CodeElement.Kind.STORE, TypeKind.ReferenceType, 3), + IASTORE(Classfile.IASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.IntType), + LASTORE(Classfile.LASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.LongType), + FASTORE(Classfile.FASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.FloatType), + DASTORE(Classfile.DASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.DoubleType), + AASTORE(Classfile.AASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.ReferenceType), + BASTORE(Classfile.BASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.ByteType), + CASTORE(Classfile.CASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.CharType), + SASTORE(Classfile.SASTORE, 1, CodeElement.Kind.ARRAY_STORE, TypeKind.ShortType), + POP(Classfile.POP, 1, CodeElement.Kind.STACK), + POP2(Classfile.POP2, 1, CodeElement.Kind.STACK), + DUP(Classfile.DUP, 1, CodeElement.Kind.STACK), + DUP_X1(Classfile.DUP_X1, 1, CodeElement.Kind.STACK), + DUP_X2(Classfile.DUP_X2, 1, CodeElement.Kind.STACK), + DUP2(Classfile.DUP2, 1, CodeElement.Kind.STACK), + DUP2_X1(Classfile.DUP2_X1, 1, CodeElement.Kind.STACK), + DUP2_X2(Classfile.DUP2_X2, 1, CodeElement.Kind.STACK), + SWAP(Classfile.SWAP, 1, CodeElement.Kind.STACK), + IADD(Classfile.IADD, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LADD(Classfile.LADD, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FADD(Classfile.FADD, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DADD(Classfile.DADD, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + ISUB(Classfile.ISUB, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LSUB(Classfile.LSUB, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FSUB(Classfile.FSUB, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DSUB(Classfile.DSUB, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + IMUL(Classfile.IMUL, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LMUL(Classfile.LMUL, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FMUL(Classfile.FMUL, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DMUL(Classfile.DMUL, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + IDIV(Classfile.IDIV, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LDIV(Classfile.LDIV, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FDIV(Classfile.FDIV, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DDIV(Classfile.DDIV, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + IREM(Classfile.IREM, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LREM(Classfile.LREM, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FREM(Classfile.FREM, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DREM(Classfile.DREM, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + INEG(Classfile.INEG, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LNEG(Classfile.LNEG, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FNEG(Classfile.FNEG, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DNEG(Classfile.DNEG, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + ISHL(Classfile.ISHL, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LSHL(Classfile.LSHL, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + ISHR(Classfile.ISHR, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + LSHR(Classfile.LSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + IUSHR(Classfile.IUSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LUSHR(Classfile.LUSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + IAND(Classfile.IAND, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LAND(Classfile.LAND, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + IOR(Classfile.IOR, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LOR(Classfile.LOR, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + IXOR(Classfile.IXOR, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LXOR(Classfile.LXOR, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + IINC(Classfile.IINC, 3, CodeElement.Kind.INCREMENT, TypeKind.IntType, -1), + I2L(Classfile.I2L, 1, CodeElement.Kind.CONVERT, TypeKind.IntType, TypeKind.LongType), + I2F(Classfile.I2F, 1, CodeElement.Kind.CONVERT, TypeKind.IntType, TypeKind.FloatType), + I2D(Classfile.I2D, 1, CodeElement.Kind.CONVERT, TypeKind.IntType, TypeKind.DoubleType), + L2I(Classfile.L2I, 1, CodeElement.Kind.CONVERT, TypeKind.LongType, TypeKind.IntType), + L2F(Classfile.L2F, 1, CodeElement.Kind.CONVERT, TypeKind.LongType, TypeKind.FloatType), + L2D(Classfile.L2D, 1, CodeElement.Kind.CONVERT, TypeKind.LongType, TypeKind.DoubleType), + F2I(Classfile.F2I, 1, CodeElement.Kind.CONVERT, TypeKind.FloatType, TypeKind.IntType), + F2L(Classfile.F2L, 1, CodeElement.Kind.CONVERT, TypeKind.FloatType, TypeKind.LongType), + F2D(Classfile.F2D, 1, CodeElement.Kind.CONVERT, TypeKind.FloatType, TypeKind.DoubleType), + D2I(Classfile.D2I, 1, CodeElement.Kind.CONVERT, TypeKind.DoubleType, TypeKind.IntType), + D2L(Classfile.D2L, 1, CodeElement.Kind.CONVERT, TypeKind.DoubleType, TypeKind.LongType), + D2F(Classfile.D2F, 1, CodeElement.Kind.CONVERT, TypeKind.DoubleType, TypeKind.FloatType), + I2B(Classfile.I2B, 1, CodeElement.Kind.CONVERT, TypeKind.IntType, TypeKind.ByteType), + I2C(Classfile.I2C, 1, CodeElement.Kind.CONVERT, TypeKind.IntType, TypeKind.CharType), + I2S(Classfile.I2S, 1, CodeElement.Kind.CONVERT, TypeKind.IntType, TypeKind.ShortType), + LCMP(Classfile.LCMP, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), + FCMPL(Classfile.FCMPL, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + FCMPG(Classfile.FCMPG, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), + DCMPL(Classfile.DCMPL, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + DCMPG(Classfile.DCMPG, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + IFEQ(Classfile.IFEQ, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IFNE(Classfile.IFNE, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IFLT(Classfile.IFLT, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IFGE(Classfile.IFGE, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IFGT(Classfile.IFGT, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IFLE(Classfile.IFLE, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ICMPEQ(Classfile.IF_ICMPEQ, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ICMPNE(Classfile.IF_ICMPNE, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ICMPLT(Classfile.IF_ICMPLT, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ICMPGE(Classfile.IF_ICMPGE, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ICMPGT(Classfile.IF_ICMPGT, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ICMPLE(Classfile.IF_ICMPLE, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + IF_ACMPEQ(Classfile.IF_ACMPEQ, 3, CodeElement.Kind.BRANCH, TypeKind.ReferenceType), + IF_ACMPNE(Classfile.IF_ACMPNE, 3, CodeElement.Kind.BRANCH, TypeKind.ReferenceType), + GOTO(Classfile.GOTO, 3, CodeElement.Kind.BRANCH, TypeKind.VoidType), + JSR(Classfile.JSR, 3, CodeElement.Kind.UNSUPPORTED), + RET(Classfile.RET, 2, CodeElement.Kind.UNSUPPORTED), + TABLESWITCH(Classfile.TABLESWITCH, -1, CodeElement.Kind.TABLE_SWITCH), + LOOKUPSWITCH(Classfile.LOOKUPSWITCH, -1, CodeElement.Kind.LOOKUP_SWITCH), + IRETURN(Classfile.IRETURN, 1, CodeElement.Kind.RETURN, TypeKind.IntType), + LRETURN(Classfile.LRETURN, 1, CodeElement.Kind.RETURN, TypeKind.LongType), + FRETURN(Classfile.FRETURN, 1, CodeElement.Kind.RETURN, TypeKind.FloatType), + DRETURN(Classfile.DRETURN, 1, CodeElement.Kind.RETURN, TypeKind.DoubleType), + ARETURN(Classfile.ARETURN, 1, CodeElement.Kind.RETURN, TypeKind.ReferenceType), + RETURN(Classfile.RETURN, 1, CodeElement.Kind.RETURN, TypeKind.VoidType), + GETSTATIC(Classfile.GETSTATIC, 3, CodeElement.Kind.FIELD_ACCESS), + PUTSTATIC(Classfile.PUTSTATIC, 3, CodeElement.Kind.FIELD_ACCESS), + GETFIELD(Classfile.GETFIELD, 3, CodeElement.Kind.FIELD_ACCESS), + PUTFIELD(Classfile.PUTFIELD, 3, CodeElement.Kind.FIELD_ACCESS), + INVOKEVIRTUAL(Classfile.INVOKEVIRTUAL, 3, CodeElement.Kind.INVOKE), + INVOKESPECIAL(Classfile.INVOKESPECIAL, 3, CodeElement.Kind.INVOKE), + INVOKESTATIC(Classfile.INVOKESTATIC, 3, CodeElement.Kind.INVOKE), + INVOKEINTERFACE(Classfile.INVOKEINTERFACE, 5, CodeElement.Kind.INVOKE), + INVOKEDYNAMIC(Classfile.INVOKEDYNAMIC, 5, CodeElement.Kind.INVOKE_DYNAMIC), + NEW(Classfile.NEW, 3, CodeElement.Kind.NEW_OBJECT), + NEWARRAY(Classfile.NEWARRAY, 2, CodeElement.Kind.NEW_PRIMITIVE_ARRAY), + ANEWARRAY(Classfile.ANEWARRAY, 3, CodeElement.Kind.NEW_REF_ARRAY), + ARRAYLENGTH(Classfile.ARRAYLENGTH, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + ATHROW(Classfile.ATHROW, 1, CodeElement.Kind.THROW_EXCEPTION), + CHECKCAST(Classfile.CHECKCAST, 3, CodeElement.Kind.TYPE_CHECK), + INSTANCEOF(Classfile.INSTANCEOF, 3, CodeElement.Kind.TYPE_CHECK), + MONITORENTER(Classfile.MONITORENTER, 1, CodeElement.Kind.MONITOR), + MONITOREXIT(Classfile.MONITOREXIT, 1, CodeElement.Kind.MONITOR), + MULTIANEWARRAY(Classfile.MULTIANEWARRAY, 4, CodeElement.Kind.NEW_MULTI_ARRAY), + IFNULL(Classfile.IFNULL, 3, CodeElement.Kind.BRANCH, TypeKind.ReferenceType), + IFNONNULL(Classfile.IFNONNULL, 3, CodeElement.Kind.BRANCH, TypeKind.IntType), + GOTO_W(Classfile.GOTO_W, 5, CodeElement.Kind.BRANCH, TypeKind.VoidType), + JSR_W(Classfile.JSR_W, 5, CodeElement.Kind.UNSUPPORTED), + ILOAD_W(0xc400 | Classfile.ILOAD, 4, CodeElement.Kind.LOAD, TypeKind.IntType, -1), + LLOAD_W(0xc400 | Classfile.LLOAD, 4, CodeElement.Kind.LOAD, TypeKind.LongType, -1), + FLOAD_W(0xc400 | Classfile.FLOAD, 4, CodeElement.Kind.LOAD, TypeKind.FloatType, -1), + DLOAD_W(0xc400 | Classfile.DLOAD, 4, CodeElement.Kind.LOAD, TypeKind.DoubleType, -1), + ALOAD_W(0xc400 | Classfile.ALOAD, 4, CodeElement.Kind.LOAD, TypeKind.ReferenceType, -1), + ISTORE_W(0xc400 | Classfile.ISTORE, 4, CodeElement.Kind.STORE, TypeKind.IntType, -1), + LSTORE_W(0xc400 | Classfile.LSTORE, 4, CodeElement.Kind.STORE, TypeKind.LongType, -1), + FSTORE_W(0xc400 | Classfile.FSTORE, 4, CodeElement.Kind.STORE, TypeKind.FloatType, -1), + DSTORE_W(0xc400 | Classfile.DSTORE, 4, CodeElement.Kind.STORE, TypeKind.DoubleType, -1), + ASTORE_W(0xc400 | Classfile.ASTORE, 4, CodeElement.Kind.STORE, TypeKind.ReferenceType, -1), + RET_W(0xc400 | Classfile.RET, 4, CodeElement.Kind.UNSUPPORTED), + IINC_W(0xc400 | Classfile.IINC, 6, CodeElement.Kind.INCREMENT, TypeKind.IntType, -1), + // PSEUDO INSTRUCTIONS + LABEL_TARGET(0xFF01, 0, CodeElement.Kind.LABEL_TARGET), + EXCEPTION_CATCH(0xFF02, 0, CodeElement.Kind.EXCEPTION_CATCH), + CHARACTER_RANGE(0xFF03, 0, CodeElement.Kind.CHARACTER_RANGE), + LOCAL_VARIABLE(0xFF04, 0, CodeElement.Kind.LOCAL_VARIABLE), + LINE_NUMBER(0xFF05, 0, CodeElement.Kind.LINE_NUMBER), + PARAMETER_ANNO(0xFF06, 0, CodeElement.Kind.PARAMETER_ANNOTATION), + TYPE_ANNO(0xFF07, 0, CodeElement.Kind.TYPE_ANNOTATION), + LOCAL_VARIABLE_TYPE(0xFF08, 0, CodeElement.Kind.LOCAL_VARIABLE_TYPE), + END(0xFF09, 0, CodeElement.Kind.END), + ; + + private final int bytecode; + private final int sizeIfFixed; + private final CodeElement.Kind kind; + private final TypeKind primaryTypeKind; + private final TypeKind secondaryTypeKind; + private final int slot; + private final ConstantDesc constantValue; + + Opcode(int bytecode, int sizeIfFixed, CodeElement.Kind kind) { + this(bytecode, sizeIfFixed, kind, null, null, 0, null); + } + + Opcode(int bytecode, int sizeIfFixed, CodeElement.Kind kind, TypeKind typeKind) { + this(bytecode, sizeIfFixed, kind, typeKind, null, 0, null); + } + + Opcode(int bytecode, int sizeIfFixed, CodeElement.Kind kind, TypeKind typeKind, int slot) { + this(bytecode, sizeIfFixed, kind, typeKind, null, slot, null); + } + + Opcode(int bytecode, int sizeIfFixed, CodeElement.Kind kind, TypeKind typeKind, int slot, ConstantDesc constantValue) { + this(bytecode, sizeIfFixed, kind, typeKind, null, slot, constantValue); + } + + Opcode(int bytecode, int sizeIfFixed, CodeElement.Kind kind, TypeKind primaryTypeKind, TypeKind secondaryTypeKind) { + this(bytecode, sizeIfFixed, kind, primaryTypeKind, secondaryTypeKind, 0, null); + } + + Opcode(int bytecode, + int sizeIfFixed, + CodeElement.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 && !isPseudo(); } + + public boolean isPseudo() { return bytecode >= 0xFF00; } + + public int sizeIfFixed() { return sizeIfFixed; } + + public CodeElement.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() == CodeElement.Kind.RETURN; + }; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/PseudoInstruction.java b/src/java.base/share/classes/jdk/classfile/PseudoInstruction.java new file mode 100755 index 0000000000000..d1b9f4c8495fc --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.instruction.CharacterRange; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.LabelTarget; +import jdk.classfile.instruction.LineNumber; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; + +/** + * 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 { + +} diff --git a/src/java.base/share/classes/jdk/classfile/Signature.java b/src/java.base/share/classes/jdk/classfile/Signature.java new file mode 100644 index 0000000000000..b55d89bcae152 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/Signature.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.lang.constant.ClassDesc; +import jdk.classfile.impl.SignaturesImpl; + +import java.util.List; +import static java.util.Objects.requireNonNull; +import java.util.Optional; +import jdk.classfile.impl.Util; + +/** + * Models generic signatures on class, method, and field declarations, as defined + * in JVMS 4.7.9.1. Signatures encode generic information that is erased from + * field and method descriptors, so it can be recovered by compilers, + * reflection, and debuggers. + * + *

Signatures come in three kinds: class, method, and field. Signatures + * can model type parameters of a class or method, including bounds, and how + * type parameters flow into supertypes, exceptions, and types in the field + * or method descriptor. + */ +public sealed interface Signature { + + /** {@return the raw signature string} */ + String signatureString(); + + /** + * Represent an ordinary method or field descriptor as a signature + * @param descriptor the descriptor + * @return the signature + */ + public static Signature parseFrom(String descriptor) { + requireNonNull(descriptor); + return new SignaturesImpl().parseSignature(descriptor); + } + + /** + * {@return a signature for an ordinary type, with no generic information} + * @param classDesc the symbolic description of the type + * @return the 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, TypeArg, 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 Util.toClassDesc(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, Signature... 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, Signature... 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, Signature... 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, Signature... typeArgs) { + requireNonNull(className); + return new SignaturesImpl.ClassTypeSigImpl(Optional.ofNullable(outerType), className.replace(".", "/"), List.of(typeArgs)); + } + } + + /** + * Models the signature of a type argument. + */ + public sealed interface TypeArg extends RefTypeSig + permits SignaturesImpl.TypeArgImpl { + + /** + * Indicator for whether a wildcard has no bound, an upper bound, or a lower bound + */ + public enum WildcardIndicator { + UNBOUNDED('*'), EXTENDS('+'), SUPER('-'); + + public final char indicator; + + WildcardIndicator(char indicator) { + this.indicator = indicator; + } + } + + /** {@return the wildcard indicator} */ + WildcardIndicator wildcardIndicator(); + + /** {@return the signature of the type bound, if any} */ + Optional boundType(); + + /** + * {@return a signature for an unbounded wildcard} + */ + public static TypeArg unbounded() { + return new SignaturesImpl.TypeArgImpl(WildcardIndicator.UNBOUNDED, Optional.empty()); + } + + /** + * {@return a signature for an upper-bounded wildcard} + * @param boundType the upper bound + */ + public static TypeArg extendsOf(RefTypeSig boundType) { + requireNonNull(boundType); + return new SignaturesImpl.TypeArgImpl(WildcardIndicator.EXTENDS, Optional.of(boundType)); + } + + /** + * {@return a signature for a lower-bounded wildcard} + * @param boundType the lower bound + */ + public static TypeArg superOf(RefTypeSig boundType) { + requireNonNull(boundType); + return new SignaturesImpl.TypeArgImpl(WildcardIndicator.SUPER, Optional.of(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) { + requireNonNull(identifier); + return new SignaturesImpl.TypeVarSigImpl(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, 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) { + requireNonNull(identifier); + return new SignaturesImpl.TypeParamImpl(identifier, Optional.ofNullable(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/classfile/Superclass.java b/src/java.base/share/classes/jdk/classfile/Superclass.java new file mode 100755 index 0000000000000..1e1de294f9c06 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.SuperclassImpl; + +/** + * Models the superclass of a class. Delivered as a {@link + * jdk.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/classfile/TypeAnnotation.java b/src/java.base/share/classes/jdk/classfile/TypeAnnotation.java new file mode 100755 index 0000000000000..8c6ef114e9ca9 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/TypeAnnotation.java @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile; + +import java.util.List; +import java.util.Set; + +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.TargetInfoImpl; +import jdk.classfile.impl.UnboundAttribute; + +import static jdk.classfile.Classfile.TAT_CAST; +import static jdk.classfile.Classfile.TAT_CLASS_EXTENDS; +import static jdk.classfile.Classfile.TAT_CLASS_TYPE_PARAMETER; +import static jdk.classfile.Classfile.TAT_CLASS_TYPE_PARAMETER_BOUND; +import static jdk.classfile.Classfile.TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT; +import static jdk.classfile.Classfile.TAT_CONSTRUCTOR_REFERENCE; +import static jdk.classfile.Classfile.TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT; +import static jdk.classfile.Classfile.TAT_EXCEPTION_PARAMETER; +import static jdk.classfile.Classfile.TAT_FIELD; +import static jdk.classfile.Classfile.TAT_INSTANCEOF; +import static jdk.classfile.Classfile.TAT_LOCAL_VARIABLE; +import static jdk.classfile.Classfile.TAT_METHOD_FORMAL_PARAMETER; +import static jdk.classfile.Classfile.TAT_METHOD_INVOCATION_TYPE_ARGUMENT; +import static jdk.classfile.Classfile.TAT_METHOD_RECEIVER; +import static jdk.classfile.Classfile.TAT_METHOD_REFERENCE; +import static jdk.classfile.Classfile.TAT_METHOD_REFERENCE_TYPE_ARGUMENT; +import static jdk.classfile.Classfile.TAT_METHOD_RETURN; +import static jdk.classfile.Classfile.TAT_METHOD_TYPE_PARAMETER; +import static jdk.classfile.Classfile.TAT_METHOD_TYPE_PARAMETER_BOUND; +import static jdk.classfile.Classfile.TAT_NEW; +import static jdk.classfile.Classfile.TAT_RESOURCE_VARIABLE; +import static jdk.classfile.Classfile.TAT_THROWS; + +/** + * 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, AttributedElement.Kind.CLASS_ONLY), + + /** For annotations on a method type parameter declaration. */ + METHOD_TYPE_PARAMETER(TAT_METHOD_TYPE_PARAMETER, 1, AttributedElement.Kind.METHOD_ONLY), + + /** For annotations on the type of an "extends" or "implements" clause. */ + CLASS_EXTENDS(TAT_CLASS_EXTENDS, 2, AttributedElement.Kind.CLASS_ONLY), + + /** For annotations on a bound of a type parameter of a class. */ + CLASS_TYPE_PARAMETER_BOUND(TAT_CLASS_TYPE_PARAMETER_BOUND, 2, AttributedElement.Kind.CLASS_ONLY), + + /** For annotations on a bound of a type parameter of a method. */ + METHOD_TYPE_PARAMETER_BOUND(TAT_METHOD_TYPE_PARAMETER_BOUND, 2, AttributedElement.Kind.METHOD_ONLY), + + /** For annotations on a field. */ + FIELD(TAT_FIELD, 0, Set.of(AttributedElement.Kind.FIELD, AttributedElement.Kind.RECORD_COMPONENT)), + + /** For annotations on a method return type. */ + METHOD_RETURN(TAT_METHOD_RETURN, 0, AttributedElement.Kind.METHOD_ONLY), + + /** For annotations on the method receiver. */ + METHOD_RECEIVER(TAT_METHOD_RECEIVER, 0, AttributedElement.Kind.METHOD_ONLY), + + /** For annotations on a method parameter. */ + METHOD_FORMAL_PARAMETER(TAT_METHOD_FORMAL_PARAMETER, 1, AttributedElement.Kind.METHOD_ONLY), + + /** For annotations on a throws clause in a method declaration. */ + THROWS(TAT_THROWS, 2, AttributedElement.Kind.METHOD_ONLY), + + /** For annotations on a local variable. */ + LOCAL_VARIABLE(TAT_LOCAL_VARIABLE, -1, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a resource variable. */ + RESOURCE_VARIABLE(TAT_RESOURCE_VARIABLE, -1, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on an exception parameter. */ + EXCEPTION_PARAMETER(TAT_EXCEPTION_PARAMETER, 2, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a type test. */ + INSTANCEOF(TAT_INSTANCEOF, 2, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on an object creation expression. */ + NEW(TAT_NEW, 2, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a constructor reference receiver. */ + CONSTRUCTOR_REFERENCE(TAT_CONSTRUCTOR_REFERENCE, 2, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a method reference receiver. */ + METHOD_REFERENCE(TAT_METHOD_REFERENCE, 2, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a typecast. */ + CAST(TAT_CAST, 3, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a type argument of an object creation expression. */ + CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT(TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, 3, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a type argument of a method call. */ + METHOD_INVOCATION_TYPE_ARGUMENT(TAT_METHOD_INVOCATION_TYPE_ARGUMENT, 3, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a type argument of a constructor reference. */ + CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT(TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, 3, AttributedElement.Kind.CODE_ONLY), + + /** For annotations on a type argument of a method reference. */ + METHOD_REFERENCE_TYPE_ARGUMENT(TAT_METHOD_REFERENCE_TYPE_ARGUMENT, 3, AttributedElement.Kind.CODE_ONLY); + + private final int targetTypeValue; + private final int sizeIfFixed; + private final Set whereApplicable; + + private TargetType(int targetTypeValue, int sizeIfFixed, Set whereApplicable) { + this.targetTypeValue = targetTypeValue; + this.sizeIfFixed = sizeIfFixed; + this.whereApplicable = whereApplicable; + } + + public int targetTypeValue() { + return targetTypeValue; + } + + public int sizeIfFixed() { + return sizeIfFixed; + } + + Set whereApplicable() { + return whereApplicable; + } + } + + /** + * {@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 annltation elements + */ + static TypeAnnotation of(TargetInfo targetInfo, List targetPath, + Utf8Entry annotationClassUtf8Entry, + List annotationElements) { + return new UnboundAttribute.UnboundTypeAnnotation(targetInfo, targetPath, + annotationClassUtf8Entry, 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 ofClassTypeParameter(int typeParameterIndex) { + return new TargetInfoImpl.TypeParameterTargetImpl(TargetType.CLASS_TYPE_PARAMETER, typeParameterIndex); + } + + static TypeParameterTarget ofMethodTypeParameter(int typeParameterIndex) { + return new TargetInfoImpl.TypeParameterTargetImpl(TargetType.METHOD_TYPE_PARAMETER, typeParameterIndex); + } + + static SupertypeTarget ofClassExtends(int supertypeIndex) { + return new TargetInfoImpl.SupertypeTargetImpl(supertypeIndex); + } + + static TypeParameterBoundTarget ofClassTypeParameterBound(int typeParameterIndex, int boundIndex) { + return new TargetInfoImpl.TypeParameterBoundTargetImpl(TargetType.CLASS_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + } + + static TypeParameterBoundTarget ofMethodTypeParameterBound(int typeParameterIndex, int boundIndex) { + return new TargetInfoImpl.TypeParameterBoundTargetImpl(TargetType.METHOD_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + } + + static EmptyTarget ofField() { + return new TargetInfoImpl.EmptyTargetImpl(TargetType.FIELD); + } + + static EmptyTarget ofMethodReturn() { + return new TargetInfoImpl.EmptyTargetImpl(TargetType.METHOD_RETURN); + } + + static EmptyTarget ofMethodReceiver() { + return new TargetInfoImpl.EmptyTargetImpl(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 ofLocalVariable(List table) { + return new TargetInfoImpl.LocalVarTargetImpl(TargetType.LOCAL_VARIABLE, table); + } + + static LocalVarTarget ofResourceVariable(List table) { + return new TargetInfoImpl.LocalVarTargetImpl(TargetType.RESOURCE_VARIABLE, table); + } + + static CatchTarget ofExceptionParameter(int exceptionTableIndex) { + return new TargetInfoImpl.CatchTargetImpl(exceptionTableIndex); + } + + static OffsetTarget ofInstanceofExpr(Label target) { + return new TargetInfoImpl.OffsetTargetImpl(TargetType.INSTANCEOF, target); + } + + static OffsetTarget ofNewExpr(Label target) { + return new TargetInfoImpl.OffsetTargetImpl(TargetType.NEW, target); + } + + static OffsetTarget ofConstructorReference(Label target) { + return new TargetInfoImpl.OffsetTargetImpl(TargetType.CONSTRUCTOR_REFERENCE, target); + } + + static OffsetTarget ofMethodReference(Label target) { + return new TargetInfoImpl.OffsetTargetImpl(TargetType.METHOD_REFERENCE, target); + } + + static TypeArgumentTarget ofCastExpr(Label target, int typeArgumentIndex) { + return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.CAST, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofConstructorInvocationTypeArgument(Label target, int typeArgumentIndex) { + return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofMethodInvocationTypeArgument(Label target, int typeArgumentIndex) { + return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofConstructorReferenceTypeArgument(Label target, int typeArgumentIndex) { + return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); + } + + static TypeArgumentTarget ofMethodReferenceTypeArgument(Label target, int typeArgumentIndex) { + return new TargetInfoImpl.TypeArgumentTargetImpl(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.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(); + } + + /** + * type_path.path. + * + * 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; + } + } + + public static final TypePathComponent ARRAY = new UnboundAttribute.TypePathComponentImpl(Kind.ARRAY, 0); + public static final TypePathComponent INNER_TYPE = new UnboundAttribute.TypePathComponentImpl(Kind.INNER_TYPE, 0); + public static final 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(int typePathKind, int typeArgumentIndex) { + + return switch (typePathKind) { + case 0 -> ARRAY; + case 1 -> INNER_TYPE; + case 2 -> WILDCARD; + case 3 -> new UnboundAttribute.TypePathComponentImpl(Kind.TYPE_ARGUMENT, typeArgumentIndex); + default -> throw new IllegalArgumentException("Unknown type annotation path component kind: " + typePathKind); + }; + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/TypeKind.java b/src/java.base/share/classes/jdk/classfile/TypeKind.java new file mode 100755 index 0000000000000..b7ba4daa6e7d3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.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", "A", -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/classfile/WritableElement.java b/src/java.base/share/classes/jdk/classfile/WritableElement.java new file mode 100755 index 0000000000000..a81b3e0f5cf3a --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile; + +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.impl.DirectFieldBuilder; +import jdk.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/classfile/attribute/AnnotationDefaultAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/AnnotationDefaultAttribute.java new file mode 100755 index 0000000000000..534373789cf9c --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.AnnotationValue; +import jdk.classfile.Attribute; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.impl.BoundAttribute; +import jdk.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/classfile/attribute/BootstrapMethodsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/BootstrapMethodsAttribute.java new file mode 100755 index 0000000000000..578f8cb21e9a3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.impl.BoundAttribute; +import jdk.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/classfile/attribute/CharacterRangeInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/CharacterRangeInfo.java new file mode 100755 index 0000000000000..cd15bdf851b29 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/CharacterRangeInfo.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.attribute; + +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models a single character range in the {@link CharacterRangeTableAttribute}. + */ +sealed public 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(); + + /** + * A flags word, indicating the kind of range. Multiple flag bits + * may be set. Valid flags include {@link jdk.classfile.Classfile#CRT_STATEMENT}, + * {@link jdk.classfile.Classfile#CRT_BLOCK}, + * {@link jdk.classfile.Classfile#CRT_ASSIGNMENT}, + * {@link jdk.classfile.Classfile#CRT_FLOW_CONTROLLER}, + * {@link jdk.classfile.Classfile#CRT_FLOW_TARGET}, + * {@link jdk.classfile.Classfile#CRT_INVOKE}, + * {@link jdk.classfile.Classfile#CRT_CREATE}, + * {@link jdk.classfile.Classfile#CRT_BRANCH_TRUE}, + * {@link jdk.classfile.Classfile#CRT_BRANCH_FALSE}. + * + * @@@ Need reference for interpretation of flags. + * + * @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/classfile/attribute/CharacterRangeTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/CharacterRangeTableAttribute.java new file mode 100755 index 0000000000000..37a0082070673 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/CharacterRangeTableAttribute.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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models the {@code CharacterRangeTable} attribute (@@@ need spec reference), + * which appears on {@code Code} attributes. It is used by debuggers to + * determine the correspondence between the code array and the source file. + */ +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/classfile/attribute/CodeAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.java new file mode 100755 index 0000000000000..de501b4ec6e01 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.CodeModel; +import jdk.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.classfile.MethodElement} when traversing the elements of a + * {@link jdk.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(); +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java new file mode 100755 index 0000000000000..d0a00087ae41e --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement} when traversing the elements of + * a {@link jdk.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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java new file mode 100755 index 0000000000000..73ad15eb92c40 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.FieldElement; +import jdk.classfile.constantpool.ConstantValueEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.FieldElement} when traversing the elements of a + * {@link jdk.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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/DeprecatedAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/DeprecatedAttribute.java new file mode 100755 index 0000000000000..f4b070fc683ab --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.FieldElement; +import jdk.classfile.MethodElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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/classfile/attribute/EnclosingMethodAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/EnclosingMethodAttribute.java new file mode 100755 index 0000000000000..3077d761eecc0 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/EnclosingMethodAttribute.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.classfile.attribute; + +import java.util.Optional; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.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 an {@code EnclosingMethod} attribute} + * @param className the class name + * @param method the name and type of the enclosing method + */ + static EnclosingMethodAttribute of(ClassEntry className, + NameAndTypeEntry method) { + return new UnboundAttribute.UnboundEnclosingMethodAttribute(className, method); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ExceptionsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ExceptionsAttribute.java new file mode 100755 index 0000000000000..0d25fadc6573d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.MethodElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; +import jdk.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.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/classfile/attribute/InnerClassInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java new file mode 100755 index 0000000000000..e9688ec0e857f --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Optional; +import java.util.Set; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.jdktypes.AccessFlag; + +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.impl.Util; + +/** + * Models a single inner class in the {@link InnerClassesAttribute}. + */ +sealed public 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, ClassEntry outerClass, + Utf8Entry innerName, int flags) { + return new UnboundAttribute.UnboundInnerClassInfo(innerClass, Optional.ofNullable(outerClass), Optional.ofNullable(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, ClassDesc outerClass, String innerName, int flags) { + return new UnboundAttribute.UnboundInnerClassInfo(TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(innerClass))), + outerClass == null ? Optional.empty() : Optional.of(TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(outerClass)))), + innerName == null ? Optional.empty() : Optional.of(TemporaryConstantPool.INSTANCE.utf8Entry(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, ClassDesc outerClass, String innerName, AccessFlag... flags) { + return of(innerClass, outerClass, innerName, Util.flagsToBits(AccessFlag.Location.INNER_CLASS, flags)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/InnerClassesAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/InnerClassesAttribute.java new file mode 100755 index 0000000000000..c9230e452c612 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/LineNumberInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/LineNumberInfo.java new file mode 100755 index 0000000000000..7fd56617ff7f6 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models a single line number in the {@link LineNumberTableAttribute}. + */ +sealed public 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/classfile/attribute/LineNumberTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/LineNumberTableAttribute.java new file mode 100755 index 0000000000000..994ea0b4b4d39 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.instruction.LineNumber} when traversing the + * elements of a {@link jdk.classfile.CodeModel}, according to the setting of the + * {@link jdk.classfile.Classfile.Option.Key#PROCESS_LINE_NUMBERS} 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/classfile/attribute/LocalVariableInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.java new file mode 100755 index 0000000000000..680650b89aaea --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.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.classfile.attribute; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundLocalVariable; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models a single local variable in the {@link LocalVariableTableAttribute}. + */ +sealed public 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 index into the local variable array of the current frame + * which holds this local variable} + */ + int slot(); + + /** + * {@return a local variable description} + * @param startPc the starting index into the code array (inclusive) for the + * scope of this variable + * @param length the ending index into the code array (exclusive) for the scope + * of this variable + * @param name the name of the variable + * @param descriptor the field descriptor of the variable + * @param slot the local variable slot for this variable + */ + static LocalVariableInfo of(int startPc, + int length, + Utf8Entry name, + Utf8Entry descriptor, + int slot) { + return new UnboundAttribute.UnboundLocalVariableInfo(startPc, length, + name, descriptor, + slot); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTableAttribute.java new file mode 100755 index 0000000000000..814e9b0e9110d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.instruction.LocalVariable} when traversing the + * elements of a {@link jdk.classfile.CodeModel}, according to the setting of the + * {@link jdk.classfile.Classfile.Option.Key#PROCESS_DEBUG} 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/classfile/attribute/LocalVariableTypeInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTypeInfo.java new file mode 100755 index 0000000000000..009a42c8b2c10 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTypeInfo.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.classfile.attribute; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundLocalVariableType; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models a single local variable in the {@link LocalVariableTypeTableAttribute}. + */ +sealed public 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(); + + /** + * {@return a local variable type description} + * @param startPc the starting index into the code array (inclusive) for the + * scope of this variable + * @param length the ending index into the code array (exclusive) for the scope + * of this variable + * @param name the name of the variable + * @param signature the generic signature of the variable + * @param slot the local variable slot for this variable + */ + static LocalVariableTypeInfo of(int startPc, + int length, + Utf8Entry name, + Utf8Entry signature, + int slot) { + return new UnboundAttribute.UnboundLocalVariableTypeInfo(startPc, length, name, signature, slot); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTypeTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableTypeTableAttribute.java new file mode 100755 index 0000000000000..d2c4c54bbadd3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.instruction.LocalVariable} when traversing the + * elements of a {@link jdk.classfile.CodeModel}, according to the setting of the + * {@link jdk.classfile.Classfile.Option.Key#PROCESS_DEBUG} 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/classfile/attribute/MethodParameterInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java new file mode 100755 index 0000000000000..9cbcac3c29d7b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.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.classfile.attribute; + +import java.util.Optional; +import java.util.Set; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.Classfile; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; +import jdk.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 name + * @param flags the method access flags + */ + static MethodParameterInfo of(Utf8Entry name, int flags) { + return new UnboundAttribute.UnboundMethodParameterInfo(Optional.ofNullable(name), flags); + } + + /** + * {@return a method parameter description} + * @param name the method name + * @param flags the method access flags + */ + static MethodParameterInfo of(String name, AccessFlag... flags) { + return of(name == null ? null : TemporaryConstantPool.INSTANCE.utf8Entry(name), Util.flagsToBits(AccessFlag.Location.METHOD_PARAMETER, flags)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/MethodParametersAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/MethodParametersAttribute.java new file mode 100755 index 0000000000000..573924b7bbbd6 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/MethodParametersAttribute.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.classfile.attribute; + +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.MethodElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.MethodElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/ModuleAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.java new file mode 100755 index 0000000000000..18f11fd5434ca --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Collection; +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.jdktypes.PackageDesc; +import jdk.classfile.impl.ModuleAttributeBuilderImpl; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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); + } + + 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/classfile/attribute/ModuleExportInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.java new file mode 100755 index 0000000000000..7bf43ef3070c4 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.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.classfile.attribute; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.jdktypes.AccessFlag; + +import jdk.classfile.Classfile; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.impl.Util; + +/** + * Models a single "exports" declaration in the {@link jdk.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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleHashInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleHashInfo.java new file mode 100755 index 0000000000000..8169e25e59a53 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models hash information for a single module in the {@link jdk.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/classfile/attribute/ModuleHashesAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleHashesAttribute.java new file mode 100755 index 0000000000000..1d4fa608cf706 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; + +import java.util.List; + +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/ModuleMainClassAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleMainClassAttribute.java new file mode 100755 index 0000000000000..ecd1a705114c2 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleMainClassAttribute.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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.impl.Util; + +/** + * Models the {@code ModuleMainClass} attribute (JVMS 4.7.27), which can + * appear on classes that represent module descriptors. + * Delivered as a {@link jdk.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(mainClass)))); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java new file mode 100755 index 0000000000000..63ca4eb1bbac8 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.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 jdk.classfile.attribute; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.jdktypes.AccessFlag; + +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.impl.Util; + +/** + * Models a single "opens" declaration in the {@link jdk.classfile.attribute.ModuleAttribute}. + */ +public sealed interface ModuleOpenInfo + permits UnboundAttribute.UnboundModuleOpenInfo { + + /** + * {@return the package being opened} + */ + PackageEntry openedPackage(); + + /** + * @@@ opens_flags + */ + 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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModulePackagesAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModulePackagesAttribute.java new file mode 100755 index 0000000000000..97954bf14c3d3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.impl.BoundAttribute; + +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.jdktypes.PackageDesc; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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)); + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleProvideInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleProvideInfo.java new file mode 100755 index 0000000000000..ef033d46c3033 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.impl.Util; + +/** + * Models a single "provides" declaration in the {@link jdk.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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(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/classfile/attribute/ModuleRequireInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.java new file mode 100755 index 0000000000000..0e428c1992ad4 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.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.classfile.attribute; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.impl.Util; + +/** + * Models a single "requires" declaration in the {@link jdk.classfile.attribute.ModuleAttribute}. + */ +public sealed interface ModuleRequireInfo + permits UnboundAttribute.UnboundModuleRequiresInfo { + + /** + * {@return The module on which the current module depends} + */ + ModuleEntry requires(); + + /** + * @@@ + */ + 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/classfile/attribute/ModuleResolutionAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleResolutionAttribute.java new file mode 100755 index 0000000000000..a3af5b4cec594 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/ModuleTargetAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleTargetAttribute.java new file mode 100755 index 0000000000000..10fc26dcb7378 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/NestHostAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/NestHostAttribute.java new file mode 100755 index 0000000000000..c3407ace1d730 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/NestHostAttribute.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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/NestMembersAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/NestMembersAttribute.java new file mode 100755 index 0000000000000..70825672d0641 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/PermittedSubclassesAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/PermittedSubclassesAttribute.java new file mode 100644 index 0000000000000..65ef5eacdbf92 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/RecordAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RecordAttribute.java new file mode 100755 index 0000000000000..90b7e33c3929b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/RecordAttribute.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.classfile.attribute; + +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement} when + * traversing the elements of a {@link jdk.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/classfile/attribute/RecordComponentInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/RecordComponentInfo.java new file mode 100755 index 0000000000000..135db8a431cec --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/RecordComponentInfo.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.classfile.attribute; + +import java.lang.constant.ClassDesc; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.AttributedElement; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundRecordComponentInfo; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models a single record component in the {@link jdk.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()); + } + + @Override + default Kind attributedElementKind() { + return Kind.RECORD_COMPONENT; + } + + /** + * {@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/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.java new file mode 100755 index 0000000000000..fe013fca943bc --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleAnnotationsAttribute.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.classfile.attribute; + +import jdk.classfile.*; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +import java.util.Arrays; +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.classfile.ClassElement}, {@link jdk.classfile.FieldElement}, or + * {@link jdk.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)); + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleParameterAnnotationsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleParameterAnnotationsAttribute.java new file mode 100755 index 0000000000000..a193de325e161 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Annotation; +import jdk.classfile.Attribute; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models the {@code RuntimeInvisibleParameterAnnotations} attribute (JVMS + * 4.7.19), which can appear on methods. Delivered as a {@link + * jdk.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/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.java new file mode 100755 index 0000000000000..040c7b8d1fc7f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute.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.classfile.attribute; + +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.CodeElement; +import jdk.classfile.FieldElement; +import jdk.classfile.MethodElement; +import jdk.classfile.TypeAnnotation; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement}, {@link jdk.classfile.FieldElement}, + * {@link jdk.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/classfile/attribute/RuntimeVisibleAnnotationsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleAnnotationsAttribute.java new file mode 100755 index 0000000000000..babcdde76c8a9 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleAnnotationsAttribute.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.classfile.attribute; + +import jdk.classfile.*; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +import java.util.Arrays; +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.classfile.ClassElement}, {@link jdk.classfile.FieldElement}, or + * {@link jdk.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/classfile/attribute/RuntimeVisibleParameterAnnotationsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleParameterAnnotationsAttribute.java new file mode 100755 index 0000000000000..d29bff676f689 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Annotation; +import jdk.classfile.Attribute; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models the {@code RuntimeVisibleParameterAnnotations} attribute (JVMS 4.7.18), which + * can appear on methods. Delivered as a {@link jdk.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/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.java new file mode 100755 index 0000000000000..19a7b4a0b072e --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute.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.classfile.attribute; + +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.CodeElement; +import jdk.classfile.FieldElement; +import jdk.classfile.MethodElement; +import jdk.classfile.TypeAnnotation; +import jdk.classfile.impl.BoundAttribute; +import jdk.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.classfile.ClassElement}, {@link jdk.classfile.FieldElement}, + * {@link jdk.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/classfile/attribute/SignatureAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/SignatureAttribute.java new file mode 100755 index 0000000000000..69287e6177826 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassSignature; +import jdk.classfile.FieldElement; +import jdk.classfile.MethodElement; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.MethodSignature; +import jdk.classfile.Signature; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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.classfile.ClassElement}, {@link jdk.classfile.FieldElement}, or + * {@link jdk.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 siganture 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/classfile/attribute/SourceDebugExtensionAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/SourceDebugExtensionAttribute.java new file mode 100755 index 0000000000000..0725558e76b0a --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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/classfile/attribute/SourceFileAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/SourceFileAttribute.java new file mode 100755 index 0000000000000..c969cae9c6759 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models the {@code SourceFile} attribute (JVMS 4.7.10), which + * can appear on classes. Delivered as a {@link jdk.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/classfile/attribute/SourceIDAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/SourceIDAttribute.java new file mode 100755 index 0000000000000..9f9ae893aa48d --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/SourceIDAttribute.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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.UnboundAttribute; + +/** + * Models the {@code SourceFile} attribute (@@@ reference needed), which can + * appear on classes. Delivered as a {@link jdk.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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java new file mode 100755 index 0000000000000..4ea64d7f7d168 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES 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.classfile.attribute; + +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.StackMapDecoder; +import static jdk.classfile.Classfile.*; + +/** + * Models the {@code StackMapTable} attribute (JVMS 4.7.4), which can appear + * on a {@code Code} attribute. + */ +public sealed interface StackMapTableAttribute + extends Attribute + permits BoundAttribute.BoundStackMapTableAttribute { + + /** + * {@return the stack map frames} + */ + List entries(); + + /** + * {@return the initial frame} + */ + StackMapFrame initFrame(); + + /** + * The possible types for a stack slot. + */ + enum VerificationType { + ITEM_TOP(VT_TOP), + ITEM_INTEGER(VT_INTEGER), + ITEM_FLOAT(VT_FLOAT), + ITEM_DOUBLE(VT_DOUBLE, 2), + ITEM_LONG(VT_LONG, 2), + ITEM_NULL(VT_NULL), + ITEM_UNINITIALIZED_THIS(VT_UNINITIALIZED_THIS), + ITEM_OBJECT(VT_OBJECT), + ITEM_UNINITIALIZED(VT_UNINITIALIZED); + + private final int tag; + private final int width; + + VerificationType(int tag) { + this(tag, 1); + } + + VerificationType(int tag, int width) { + this.tag = tag; + this.width = width; + } + + public int tag() { + return tag; + } + } + + /** + * Kinds of stack values. + */ + enum FrameKind { + 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 int start() { return start; } + public int end() { return end; } + + FrameKind(int start, int end) { + this.start = start; + this.end = end; + } + } + + /** + * The type of a stack value. + */ + sealed interface VerificationTypeInfo { + VerificationType type(); + } + + /** + * A simple stack value. + */ + sealed interface SimpleVerificationTypeInfo extends VerificationTypeInfo + permits StackMapDecoder.SimpleVerificationTypeInfoImpl { + } + + /** + * A stack value for an object type. + */ + sealed interface ObjectVerificationTypeInfo extends VerificationTypeInfo + permits StackMapDecoder.ObjectVerificationTypeInfoImpl { + /** + * {@return the class of the value} + */ + ClassEntry className(); + } + + /** + * An uninitialized stack value. + */ + sealed interface UninitializedVerificationTypeInfo extends VerificationTypeInfo + permits StackMapDecoder.UninitializedVerificationTypeInfoImpl { + int offset(); + } + + /** + * A stack map frame. + */ + sealed interface StackMapFrame + permits StackMapDecoder.StackMapFrameImpl { + + int frameType(); + FrameKind frameKind(); + int offsetDelta(); + List declaredLocals(); + List declaredStack(); + + int absoluteOffset(); + List effectiveLocals(); + List effectiveStack(); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/SyntheticAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/SyntheticAttribute.java new file mode 100755 index 0000000000000..1949f5e11fe52 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.FieldElement; +import jdk.classfile.MethodElement; +import jdk.classfile.impl.BoundAttribute; +import jdk.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/classfile/attribute/UnknownAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/UnknownAttribute.java new file mode 100755 index 0000000000000..155b7b5cf7b53 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.attribute; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassElement; +import jdk.classfile.FieldElement; +import jdk.classfile.MethodElement; +import jdk.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/classfile/constantpool/AnnotationConstantValueEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/AnnotationConstantValueEntry.java new file mode 100755 index 0000000000000..b436040caec73 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.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. + */ +sealed public 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/classfile/constantpool/ClassEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java new file mode 100755 index 0000000000000..83d5948dc7bfc --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.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.classfile.constantpool; + +import java.lang.constant.ClassDesc; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Class_info} constant in the constant pool of a + * classfile. + */ +sealed public interface ClassEntry + extends LoadableConstantEntry + permits ConcreteEntry.ConcreteClassEntry { + + /** + * {@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/classfile/constantpool/ConstantDynamicEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.java new file mode 100755 index 0000000000000..167cf1262475c --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.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.classfile.constantpool; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicConstantDesc; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Dynamic_info} constant in the constant pool of a + * classfile. + */ +sealed public interface ConstantDynamicEntry + extends DynamicConstantPoolEntry, LoadableConstantEntry + permits ConcreteEntry.ConcreteConstantDynamicEntry { + + /** + * {@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)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPool.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPool.java new file mode 100755 index 0000000000000..f9ffc01886f02 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPool.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 License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it 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.classfile.constantpool; + +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.ClassReader; +import jdk.classfile.impl.ClassReaderImpl; + +import static jdk.classfile.Classfile.TAG_CLASS; +import static jdk.classfile.Classfile.TAG_CONSTANTDYNAMIC; +import static jdk.classfile.Classfile.TAG_DOUBLE; +import static jdk.classfile.Classfile.TAG_FIELDREF; +import static jdk.classfile.Classfile.TAG_FLOAT; +import static jdk.classfile.Classfile.TAG_INTEGER; +import static jdk.classfile.Classfile.TAG_INTERFACEMETHODREF; +import static jdk.classfile.Classfile.TAG_INVOKEDYNAMIC; +import static jdk.classfile.Classfile.TAG_LONG; +import static jdk.classfile.Classfile.TAG_METHODHANDLE; +import static jdk.classfile.Classfile.TAG_METHODREF; +import static jdk.classfile.Classfile.TAG_METHODTYPE; +import static jdk.classfile.Classfile.TAG_MODULE; +import static jdk.classfile.Classfile.TAG_NAMEANDTYPE; +import static jdk.classfile.Classfile.TAG_PACKAGE; +import static jdk.classfile.Classfile.TAG_STRING; +import static jdk.classfile.Classfile.TAG_UTF8; + +/** + * 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/classfile/constantpool/ConstantPoolBuilder.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java new file mode 100755 index 0000000000000..dab950cac8f28 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.BootstrapMethodEntry; +import jdk.classfile.BufWriter; +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassReader; +import jdk.classfile.Classfile; +import jdk.classfile.impl.ClassImpl; +import jdk.classfile.impl.ClassReaderImpl; +import jdk.classfile.impl.Options; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.jdktypes.PackageDesc; +import jdk.classfile.WritableElement; +import jdk.classfile.impl.SplitConstantPool; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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.Key#CP_SHARING}, + * (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) { + ClassReader reader = (ClassReader) classModel.constantPool(); + return reader.optionValue(Classfile.Option.Key.CP_SHARING) + ? new SplitConstantPool(reader) + : new SplitConstantPool(((ClassReaderImpl) 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 the value of the specified option} + * + * @param option the key of the option value + * @param the type of the option value + */ + T optionValue(Classfile.Option.Key option); + + boolean canWriteDirect(ConstantPool constantPool); + + 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(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 natEntry(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 natEntry(String name, ClassDesc type) { + return natEntry(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 natEntry(String name, MethodTypeDesc type) { + return natEntry(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), natEntry(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), natEntry(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), natEntry(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) { + return methodHandleEntry(descriptor.refKind(), methodRefEntry(descriptor.owner(), descriptor.methodName(), descriptor.invocationType())); + } + + /** + * {@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())), natEntry(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())), natEntry(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.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.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.getClass()); + } + + /** + * {@return a constant pool entry describing the same constant as the provided + * entry} If the entry already corresponds to a constant in this constant + * pool, it is returned, otherwise the contents are copied to this + * constant pool. + * + * @param entry the entry + * @param the type of the entry + */ + T maybeClone(T entry); + + /** + * {@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/classfile/constantpool/ConstantValueEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantValueEntry.java new file mode 100755 index 0000000000000..183928d051cd9 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantValueEntry.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 jdk.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. + */ +sealed public 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}. + */ + ConstantDesc constantValue(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java new file mode 100755 index 0000000000000..a8e901930d8e2 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Double_info} constant in the constant pool of a + * classfile. + */ +sealed public interface DoubleEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits ConcreteEntry.ConcreteDoubleEntry { + + /** + * {@return the double value} + */ + double doubleValue(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/DynamicConstantPoolEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/DynamicConstantPoolEntry.java new file mode 100755 index 0000000000000..4abbd1096b78e --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.BootstrapMethodEntry; + +/** + * Models a dynamic constant pool entry, which is either {@link ConstantDynamicEntry} + * or {@link InvokeDynamicEntry}. + */ +sealed public 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/classfile/constantpool/FieldRefEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/FieldRefEntry.java new file mode 100755 index 0000000000000..bcf72872f9cd8 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Fieldref_info} constant in the constant pool of a + * classfile. + */ +sealed public interface FieldRefEntry extends MemberRefEntry + permits ConcreteEntry.ConcreteFieldRefEntry { + +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java new file mode 100755 index 0000000000000..921f8a3632a8b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.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 jdk.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Float_info} constant in the constant pool of a + * classfile. + */ +sealed public interface FloatEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits ConcreteEntry.ConcreteFloatEntry { + + /** + * {@return the float value} + */ + + float floatValue(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java new file mode 100755 index 0000000000000..7a633134cf466 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Integer_info} constant in the constant pool of a + * classfile. + */ +sealed public interface IntegerEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits ConcreteEntry.ConcreteIntegerEntry { + + /** + * {@return the integer value} + */ + int intValue(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/InterfaceMethodRefEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/InterfaceMethodRefEntry.java new file mode 100755 index 0000000000000..cea6adfc993c5 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_InterfaceMethodRef_info} constant in the constant pool of a + * classfile. + */ +sealed public interface InterfaceMethodRefEntry + extends MemberRefEntry + permits ConcreteEntry.ConcreteInterfaceMethodRefEntry { + +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java new file mode 100755 index 0000000000000..8c8731de57b5d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a constant pool entry for a dynamic call site. + */ +sealed public interface InvokeDynamicEntry + extends DynamicConstantPoolEntry + permits ConcreteEntry.ConcreteInvokeDynamicEntry { + + /** + * {@return a symbolic descriptor for the dynamic call site} + */ + default DynamicCallSiteDesc asSymbol() { + return DynamicCallSiteDesc.of(bootstrap().bootstrapMethod().asSymbol(), + name().stringValue(), + MethodTypeDesc.ofDescriptor(name().stringValue()), + bootstrap().arguments().stream() + .map(LoadableConstantEntry::constantValue) + .toArray(ConstantDesc[]::new)); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java new file mode 100755 index 0000000000000..42ee4aae97d14 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.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 jdk.classfile.constantpool; + +import java.lang.constant.ConstantDesc; + +/** + * Marker interface for constant pool entries suitable for loading via the + * {@code LDC} instructions. + */ +sealed public interface LoadableConstantEntry extends PoolEntry + permits ClassEntry, ConstantDynamicEntry, ConstantValueEntry, MethodHandleEntry, MethodTypeEntry { + + /** + * {@return the constant described by this entry} + */ + ConstantDesc constantValue(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.java new file mode 100755 index 0000000000000..4338f6a794748 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_Long_info} constant in the constant pool of a + * classfile. + */ +sealed public interface LongEntry + extends AnnotationConstantValueEntry, ConstantValueEntry + permits ConcreteEntry.ConcreteLongEntry { + + /** + * {@return the long value} + */ + long longValue(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/MemberRefEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/MemberRefEntry.java new file mode 100755 index 0000000000000..7af420bb9ce77 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/MemberRefEntry.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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a member reference constant in the constant pool of a classfile, + * which includes references to fields, methods, and interface methods. + */ +sealed public interface MemberRefEntry extends PoolEntry + permits FieldRefEntry, InterfaceMethodRefEntry, MethodRefEntry, ConcreteEntry.MemberRefEntry { + /** + * {@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(); + } + + /** + * {@return whether this member is a method} + */ + boolean isMethod(); + + /** + * {@return whether this member is an interface method} + */ + boolean isInterface(); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/MethodHandleEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/MethodHandleEntry.java new file mode 100755 index 0000000000000..a6d737178ef75 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/MethodHandleEntry.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.classfile.constantpool; + +import java.lang.constant.DirectMethodHandleDesc; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_MethodHandle_info} constant in the constant pool of a + * classfile. + */ +sealed public interface MethodHandleEntry + extends LoadableConstantEntry + permits ConcreteEntry.ConcreteMethodHandleEntry { + + /** + * {@return the reference kind of this method handle} + */ + 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/classfile/constantpool/MethodRefEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/MethodRefEntry.java new file mode 100755 index 0000000000000..8aa1acf861d0a --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_MethodRef_info} constant in the constant pool of a + * classfile. + */ +sealed public interface MethodRefEntry extends MemberRefEntry + permits ConcreteEntry.ConcreteMethodRefEntry { + +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/MethodTypeEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/MethodTypeEntry.java new file mode 100755 index 0000000000000..6c473313dd7f1 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/MethodTypeEntry.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.classfile.constantpool; + +import java.lang.constant.MethodTypeDesc; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_MethodType_info} constant in the constant pool of a + * classfile. + */ +sealed public interface MethodTypeEntry + extends LoadableConstantEntry + permits ConcreteEntry.ConcreteMethodTypeEntry { + + /** + * {@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/classfile/constantpool/ModuleEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ModuleEntry.java new file mode 100755 index 0000000000000..fa95889c3f3d8 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; +import jdk.classfile.jdktypes.ModuleDesc; + +/** + * Models a {@code CONSTANT_Module_info} constant in the constant pool of a + * classfile. + */ +sealed public interface ModuleEntry extends PoolEntry + permits ConcreteEntry.ConcreteModuleEntry { + /** + * {@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/classfile/constantpool/NameAndTypeEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/NameAndTypeEntry.java new file mode 100755 index 0000000000000..1dc044b8f33bd --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_NameAndType_info} constant in the constant pool of a + * classfile. + */ +sealed public interface NameAndTypeEntry extends PoolEntry + permits ConcreteEntry.ConcreteNameAndTypeEntry { + + /** + * {@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/classfile/constantpool/PackageEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/PackageEntry.java new file mode 100755 index 0000000000000..2cffae774e203 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; +import jdk.classfile.jdktypes.PackageDesc; + +/** + * Models a {@code CONSTANT_Package_info} constant in the constant pool of a + * classfile. + */ +sealed public interface PackageEntry extends PoolEntry + permits ConcreteEntry.ConcretePackageEntry { + /** + * {@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/classfile/constantpool/PoolEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.java new file mode 100755 index 0000000000000..7e8602fef8681 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.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.classfile.constantpool; + +import jdk.classfile.WritableElement; + +/** + * Models an entry in the constant pool of a classfile. + */ +sealed public 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} + * @return + */ + int index(); + + /** + * {@return the number of constant pool slots this entry consumes} + */ + int poolEntries(); + + /** + * {@return a constant pool entry, with the same contents as this entry, + * compatible with the specified constant pool} If {@code cp} describes + * the pool this entry is from, or a pool this constant pool has been + * copied from, then {@code this} is returned, otherwise the entry is + * cloned to the specified pool. + * + * @param cp the constant pool with which a compatible entry is desired + */ + PoolEntry clone(ConstantPoolBuilder cp); +} diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/StringEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/StringEntry.java new file mode 100755 index 0000000000000..9d06a29cd0640 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_String_info} constant in the constant pool of a + * classfile. + */ +sealed public interface StringEntry + extends ConstantValueEntry + permits ConcreteEntry.ConcreteStringEntry { + /** + * {@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/classfile/constantpool/Utf8Entry.java b/src/java.base/share/classes/jdk/classfile/constantpool/Utf8Entry.java new file mode 100755 index 0000000000000..00ff0121957a4 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.constantpool; + +import jdk.classfile.impl.ConcreteEntry; + +/** + * Models a {@code CONSTANT_UTF8_info} constant in the constant pool of a + * classfile. + */ +sealed public interface Utf8Entry + extends CharSequence, AnnotationConstantValueEntry + permits ConcreteEntry.ConcreteUtf8Entry { + + /** + * {@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/classfile/impl/AbstractAttributeMapper.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java new file mode 100755 index 0000000000000..7ba55f4c143a7 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.Set; + +import jdk.classfile.Attribute; +import jdk.classfile.AttributeMapper; +import jdk.classfile.AttributedElement; +import jdk.classfile.BufWriter; + +import static jdk.classfile.Classfile.JAVA_1_VERSION; + +/** + * AbstractAttributeMapper + */ +public abstract class AbstractAttributeMapper> + implements AttributeMapper { + + private final String name; + private final Set whereApplicable; + private final boolean allowMultiple; + private final int majorVersion; + + protected abstract void writeBody(BufWriter buf, T attr); + + public AbstractAttributeMapper(String name, + Set whereApplicable) { + this(name, whereApplicable, false); + } + + public AbstractAttributeMapper(String name, + Set whereApplicable, + boolean allowMultiple) { + this(name, whereApplicable, allowMultiple, JAVA_1_VERSION); + } + + public AbstractAttributeMapper(String name, + Set whereApplicable, + int majorVersion) { + this(name, whereApplicable, false, majorVersion); + } + + public AbstractAttributeMapper(String name, + Set whereApplicable, + boolean allowMultiple, + int majorVersion) { + this.name = name; + this.whereApplicable = whereApplicable; + this.allowMultiple = allowMultiple; + this.majorVersion = majorVersion; + } + + @Override + public Set whereApplicable() { + return whereApplicable; + } + + @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; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java new file mode 100755 index 0000000000000..2e2144cb23291 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.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.classfile.impl; + +import jdk.classfile.BufWriter; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.Label; +import jdk.classfile.constantpool.Utf8Entry; + +/** + * AbstractLocalVariableImpl + */ +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; + } + + public int sizeInBytes() { + return 0; + } + + public CodeElement.Kind codeKind() { + return CodeElement.Kind.LOCAL_VARIABLE; + } + + 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 void writeTo(BufWriter b, CodeBuilder builder) { + int startBci = builder.labelToBci(startScope()); + int endBci = builder.labelToBci(endScope()); + 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()); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractDirectBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractDirectBuilder.java new file mode 100755 index 0000000000000..75a2947d39e4e --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractDirectBuilder.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.classfile.impl; + +import java.util.Optional; + +import jdk.classfile.Attribute; +import jdk.classfile.constantpool.ConstantPoolBuilder; + +/** + * AbstractDirectBuilder + */ +public class AbstractDirectBuilder { + protected final ConstantPoolBuilder constantPool; + protected final AttributeHolder attributes = new AttributeHolder(); + protected M original; + + public AbstractDirectBuilder(ConstantPoolBuilder constantPool) { + this.constantPool = constantPool; + } + + public ConstantPoolBuilder 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/classfile/impl/AbstractElement.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractElement.java new file mode 100755 index 0000000000000..a87e9566ff1a0 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractElement.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.classfile.impl; + +/** + * AbstractElement + */ +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/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java new file mode 100755 index 0000000000000..195bf002d6e86 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java @@ -0,0 +1,1548 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import jdk.classfile.BufWriter; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.instruction.SwitchCase; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.instruction.ArrayLoadInstruction; +import jdk.classfile.instruction.ArrayStoreInstruction; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.CharacterRange; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.ConvertInstruction; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.InvokeDynamicInstruction; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.MonitorInstruction; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewObjectInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import jdk.classfile.instruction.NopInstruction; +import jdk.classfile.instruction.OperatorInstruction; +import jdk.classfile.instruction.ReturnInstruction; +import jdk.classfile.instruction.StackInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.instruction.ThrowInstruction; +import jdk.classfile.instruction.TypeCheckInstruction; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; + + +/** + * AbstractInstruction. + */ +public abstract sealed class AbstractInstruction + extends AbstractElement + implements CodeElement { + final Opcode op; + final int size; + + @Override + public Opcode opcode() { + return op; + } + + @Override + public int sizeInBytes() { + return size; + } + + @Override + public Kind codeKind() { + return op.kind(); + } + + public AbstractInstruction(Opcode op, int size) { + this.op = op; + this.size = size; + } + + 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); + } + + 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("Load[OP=%s, slot=%d]", 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("Store[OP=%s, slot=%d]", 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("Inc[OP=%s, slot=%d, val=%d]", 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("Branch[OP=%s, kind=%s]", this.opcode(), codeKind()); + } + + } + + 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("LookupSwitch[OP=%s]", 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("TableSwitch[OP=%s]", 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); + } + + 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("Field[OP=%s, field=%s.%s:%s]", 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); + } + + 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("Invoke[OP=%s, m=%s.%s%s]", 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); + } + + 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("InvokeInterface[OP=%s, m=%s.%s%s]", 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); + } + + 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("InvokeDynamic[OP=%s, bsm=%s %s]", 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); + } + + 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("NewObj[OP=%s, type=%s]", 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); + } + + public TypeKind typeKind() { + return TypeKind.fromNewArrayCode(code.classReader.readU1(pos + 1)); + } + + @Override + public String toString() { + return String.format("NewPrimitiveArray[OP=%s, type=%s]", 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); + } + + 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("NewRefArray[OP=%s, type=%s]", 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); + } + + 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("NewMultiArray[OP=%s, type=%s[%d]]", 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); + } + + 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("TypeCheck[OP=%s, type=%s]", 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("ArgumentConstant[OP=%s, val=%s]", 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("LoadConstant[OP=%s, val=%s]", this.opcode(), constantValue()); + } + + } + + public static abstract sealed class UnboundInstruction extends AbstractInstruction { + UnboundInstruction(Opcode op) { + super(op, op.sizeIfFixed()); + } + + public UnboundInstruction(Opcode op, int size) { + super(op, size); + } + + @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("%s[op=%s]", 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("Load[OP=%s, slot=%d]", 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("Store[OP=%s, slot=%d]", 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("Increment[OP=%s, slot=%d, constant=%d]", 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("Branch[OP=%s]", 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("LookupSwitch[OP=%s]", 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("LookupSwitch[OP=%s]", 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("Return[OP=%s]", this.opcode()); + } + + } + + public static final class UnboundThrowInstruction + extends UnboundInstruction implements ThrowInstruction { + + public UnboundThrowInstruction() { + super(Opcode.ATHROW); + } + + @Override + public String toString() { + return String.format("Throw[OP=%s]", 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("FieldAccess[OP=%s, field=%s.%s:%s]", 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("Invoke[OP=%s, m=%s.%s%s]", 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("InvokeDynamic[OP=%s, bsm=%s %s]", 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; + } + + public ClassEntry className() { + return classEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewObject(className()); + } + + @Override + public String toString() { + return String.format("NewObj[OP=%s, type=%s]", 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; + } + + public TypeKind typeKind() { + return typeKind; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewPrimitiveArray(typeKind.newarraycode()); + } + + @Override + public String toString() { + return String.format("NewPrimitiveArray[OP=%s, type=%s]", 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; + } + + public ClassEntry componentType() { + return componentTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewReferenceArray(componentType()); + } + + @Override + public String toString() { + return String.format("NewRefArray[OP=%s, type=%s]", 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; + } + + public ClassEntry arrayType() { + return arrayTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewMultidimensionalArray(dimensions(), arrayType()); + } + + @Override + public String toString() { + return String.format("NewMultiArray[OP=%s, type=%s[%d]]", 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; + } + + public ClassEntry type() { + return typeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTypeCheck(op, type()); + } + + @Override + public String toString() { + return String.format("TypeCheck[OP=%s, type=%s]", 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(); + } + + public UnboundIntrinsicConstantInstruction(byte byteVal) { + super(Opcode.BIPUSH); + constant = (int) byteVal; + } + + public UnboundIntrinsicConstantInstruction(short shortVal) { + super(Opcode.SIPUSH); + constant = (int) shortVal; + } + + public UnboundIntrinsicConstantInstruction(ConstantDesc constant) { + super(Opcode.LDC); + this.constant = constant; + } + + @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("ArgumentConstant[OP=%s, val=%s]", 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("LoadConstant[OP=%s, val=%s]", 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); + } + + } + + public static final class ExceptionCatchImpl + extends AbstractInstruction + 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) { + super(Opcode.EXCEPTION_CATCH, 0); + this.catchTypeEntry = catchTypeEntry; + this.handler = handler; + this.tryStart = tryStart; + this.tryEnd = tryEnd; + } + + public ExceptionCatchImpl(Label handler, Label tryStart, Label tryEnd, + Optional catchTypeEntry) { + super(Opcode.EXCEPTION_CATCH, 0); + 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); + } + } + + public static final class UnboundCharacterRange + extends AbstractInstruction + 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) { + super(Opcode.CHARACTER_RANGE, 0); + 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 AbstractInstruction { + protected final int slot; + protected final Utf8Entry name; + protected final Utf8Entry descriptor; + protected final Label startScope; + protected final Label endScope; + + public AbstractLocalPseudo(Opcode op, int slot, Utf8Entry name, Utf8Entry descriptor, Label startScope, Label endScope) { + super(op, 0); + 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 void writeTo(BufWriter b, CodeBuilder builder) { + int startBci = builder.labelToBci(startScope()); + int endBci = builder.labelToBci(endScope()); + int length = endBci - startBci; + b.writeU2(startBci); + b.writeU2(length); + b.writeIndex(name); + b.writeIndex(descriptor); + b.writeU2(slot()); + } + } + + public static final class UnboundLocalVariable extends AbstractLocalPseudo + implements LocalVariable { + + public UnboundLocalVariable(int slot, Utf8Entry name, Utf8Entry descriptor, Label startScope, Label endScope) { + super(Opcode.LOCAL_VARIABLE, 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(Opcode.LOCAL_VARIABLE_TYPE, 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/classfile/impl/AbstractUnboundModel.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractUnboundModel.java new file mode 100755 index 0000000000000..41f0ae0c3ac87 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractUnboundModel.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import jdk.classfile.Attribute; +import jdk.classfile.AttributedElement; +import jdk.classfile.ClassfileElement; +import jdk.classfile.CompoundElement; + +/** + * AbstractUnboundModel + */ +public abstract sealed class AbstractUnboundModel + extends AbstractElement + implements CompoundElement, AttributedElement + permits BufferedCodeBuilder.Model, BufferedFieldBuilder.Model, BufferedMethodBuilder.Model { + private final List elements; + private final Kind kind; + private List> attributes; + + public AbstractUnboundModel(List elements, Kind kind) { + this.elements = elements; + this.kind = kind; + } + + @Override + public Kind attributedElementKind() { + return kind; + } + + @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/classfile/impl/AccessFlagsImpl.java b/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java new file mode 100755 index 0000000000000..fa7e18cfc76d6 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.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. + */ +package jdk.classfile.impl; + +import java.util.Set; +import jdk.classfile.AccessFlags; +import jdk.classfile.jdktypes.AccessFlag; +/** + * AccessFlagsImpl + */ +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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java b/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java new file mode 100644 index 0000000000000..dc207ab8a30ff --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import jdk.classfile.*; +import jdk.classfile.constantpool.AnnotationConstantValueEntry; +import jdk.classfile.constantpool.Utf8Entry; + +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 record OfConstantImpl(char tag, AnnotationConstantValueEntry constant) + implements AnnotationValue.OfConstant { + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(constant); + } + + @Override + public ConstantDesc constantValue() { + return constant.constantValue(); + } + + } + + 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/classfile/impl/AnnotationReader.java b/src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java new file mode 100644 index 0000000000000..4a49f8877ba3f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import jdk.classfile.Annotation; +import jdk.classfile.AnnotationElement; +import jdk.classfile.AnnotationValue; +import jdk.classfile.ClassReader; +import jdk.classfile.constantpool.AnnotationConstantValueEntry; +import jdk.classfile.TypeAnnotation; +import static jdk.classfile.Classfile.*; +import static jdk.classfile.TypeAnnotation.TargetInfo.*; + +import java.util.List; +import jdk.classfile.Label; +import jdk.classfile.constantpool.Utf8Entry; + +class AnnotationReader { + private AnnotationReader() { } + + public static List readAnnotations(ClassReader classReader, int p) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int pos = p; + int numAnnotations = classReader.readU2(pos); + Annotation[] annos = new Annotation[numAnnotations]; + pos += 2; + for (int i = 0; i < numAnnotations; ++i) { + annos[i] = readAnnotation(classReader, pos); + pos = skipAnnotation(classReader, pos); + } + return List.of(annos); + } + + public static AnnotationValue readElementValue(ClassReader classReader, int p) { + char tag = (char) classReader.readU1(p); + ++p; + return switch (tag) { + case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z' -> new AnnotationImpl.OfConstantImpl(tag, (AnnotationConstantValueEntry) classReader.readEntry(p)); + case 's' -> new AnnotationImpl.OfConstantImpl(tag, 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 '[' -> { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int numValues = classReader.readU2(p); + p += 2; + AnnotationValue[] values = new AnnotationValue[numValues]; + for (int i = 0; i < numValues; ++i) { + values[i] = readElementValue(classReader, p); + p = skipElementValue(classReader, p); + } + yield new AnnotationImpl.OfArrayImpl(List.of(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) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int numTypeAnnotations = classReader.readU2(p); + p += 2; + TypeAnnotation[] annotations = new TypeAnnotation[numTypeAnnotations]; + for (int i = 0; i < numTypeAnnotations; ++i) { + annotations[i] = readTypeAnnotation(classReader, p, lc); + p = skipTypeAnnotation(classReader, p); + } + return List.of(annotations); + } + + public static List> readParameterAnnotations(ClassReader classReader, int p, boolean isVisible) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int cnt = classReader.readU1(p++); + @SuppressWarnings("unchecked") + List[] pas = (List[]) new List[cnt]; + for (int i = 0; i < cnt; ++i) { + pas[i] = readAnnotations(classReader, p); + p = skipAnnotations(classReader, p); + } + return List.of(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) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int numElementValuePairs = classReader.readU2(p); + p += 2; + AnnotationElement[] annotationElements = new AnnotationElement[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 List.of(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) { + typePath[i] = TypeAnnotation.TypePathComponent.of(classReader.readU1(p++), classReader.readU1(p++)); + } + // 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) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int tableLength = classReader.readU2(p); + p += 2; + TypeAnnotation.LocalVarTargetInfo[] entries = new TypeAnnotation.LocalVarTargetInfo[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 List.of(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/classfile/impl/AttributeHolder.java b/src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.java new file mode 100755 index 0000000000000..a2cfb3894ad39 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.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.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +import jdk.classfile.Attribute; +import jdk.classfile.AttributeMapper; +import jdk.classfile.BufWriter; + +public class AttributeHolder { + private final List> attributes = new ArrayList<>(); + + public AttributeHolder() { + } + + 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); + } + + private boolean isPresent(AttributeMapper am) { + for (Attribute a : attributes) + if (a.attributeMapper() == am) + return true; + return false; + } + + private void remove(AttributeMapper am) { + for (ListIterator> iterator = attributes.listIterator(); + iterator.hasNext(); ) { + Attribute a = iterator.next(); + if (a.attributeMapper() == am) + iterator.remove(); + } + } + + List> attributes() { + return attributes; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java new file mode 100755 index 0000000000000..cfdeae57093f2 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.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.classfile.impl; + +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.LabelTarget; + +/** + * BlockCodeBuilder + */ +public final class BlockCodeBuilder + extends NonterminalCodeBuilder + implements CodeBuilder { + private final CodeBuilder parent; + private final Label startLabel, endLabel; + private boolean reachable = true; + private int topLocal; + private int terminalMaxLocals; + + public BlockCodeBuilder(CodeBuilder parent) { + super(parent); + this.parent = parent; + startLabel = terminal.newLabel(); + endLabel = terminal.newLabel(); + } + + 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; + } + + private int topLocal(CodeBuilder parent) { + return switch (parent) { + case BlockCodeBuilder b -> b.topLocal; + case ChainedCodeBuilder b -> topLocal(b.terminal); + case DirectCodeBuilder b -> b.curTopLocal(); + case BufferedCodeBuilder b -> b.curTopLocal(); + }; + } + + @Override + public CodeBuilder with(CodeElement element) { + Opcode op = element.opcode(); + parent.with(element); + if (reachable) { + if (op.isUnconditionalBranch()) + reachable = false; + } + else if (op == Opcode.LABEL_TARGET) { + 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; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java new file mode 100755 index 0000000000000..59d6649978565 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java @@ -0,0 +1,1062 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import jdk.classfile.*; +import jdk.classfile.attribute.*; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantValueEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.Utf8Entry; + +/** + * BoundAttribute + */ +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; + } + + List readEntryList(int p) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int cnt = classReader.readU2(p); + p += 2; + @SuppressWarnings("unchecked") + E[] entries = (E[]) new Object[cnt]; + int end = p + (cnt * 2); + for (int i = 0; p < end; i++, p += 2) { + @SuppressWarnings("unchecked") + var entry = (E) classReader.readEntry(p); + entries[i] = entry; + } + return List.of(entries); + } + + public static > List readAttributes(AttributedElement enclosing, ClassReader reader, int pos, + Function> customAttributes) { + int size = reader.readU2(pos); + @SuppressWarnings({"unchecked", "rawtypes"}) + A[] filled = (A[])new Attribute[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; + + @SuppressWarnings("unchecked") + AttributeMapper mapper = (AttributeMapper) Attributes.standardAttribute(name); + if (mapper == null) { + @SuppressWarnings("unchecked") + AttributeMapper m = (AttributeMapper) customAttributes.apply(name); + mapper = m; + } + if (mapper != null && (enclosing == null || mapper.whereApplicable().contains(enclosing.attributedElementKind()))) { + filled[i] = mapper.readAttribute(enclosing, reader, p); + } + else if (reader.optionValue(Classfile.Option.Key.PROCESS_UNKNOWN_ATTRIBUTES)) { + AttributeMapper fakeMapper + = new AttributeMapper<>() { + @Override + public Set whereApplicable() { + return AttributedElement.Kind.EVERYWHERE; + } + + @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) { + // Do nothing + } + + @Override + public boolean allowMultiple() { + return true; + } + }; + + // @@@ Needs a writer / need to thread policy regarding unknown attrs + @SuppressWarnings("unchecked") + A a = (A)new BoundUnknownAttribute(reader, fakeMapper, p); + filled[i] = a; + } + p += len; + } + + return List.of(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) { + // @@@ Use option toggle to throw? + if (builder.canWriteDirect(classReader)) + super.writeTo(builder); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + // @@@ Use option toggle to throw? + if (builder.canWriteDirect(classReader)) + super.writeTo(builder); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + // @@@ Use option toggle to throw? + if (builder.canWriteDirect(classReader)) + super.writeTo(builder); + } + + @Override + public void writeTo(BufWriter buf) { + if (!buf.canWriteDirect(classReader)) + // @@@ Toggle exception / blind ignore based on configuration + throw new UnsupportedOperationException("Write of unknown attribute " + attributeName() + " not supported to alien constant pool"); + else + super.writeTo(buf); + } + } + + public static final class BoundStackMapTableAttribute + extends BoundAttribute + implements StackMapTableAttribute { + final MethodModel method; + List entries = null; + StackMapFrame initFrame = null; + + public BoundStackMapTableAttribute(CodeModel code, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + method = code.parent().orElseThrow(); + } + + @Override + public StackMapFrame initFrame() { + if (initFrame == null) + initFrame = StackMapDecoder.initFrame(method); + return initFrame; + } + + @Override + public List entries() { + if (entries == null) { + entries = new StackMapDecoder(classReader, payloadStart, initFrame()).entries(); + } + return entries; + } + + } + + public static final class BoundSyntheticAttribute extends BoundAttribute + implements SyntheticAttribute { + public BoundSyntheticAttribute(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(name, accessFlags); + } + parameters = List.of(elements); + } + return parameters; + } + } + + public static final class BoundModuleHashesAttribute extends BoundAttribute + implements ModuleHashesAttribute { + private List hashes = null; + + public BoundModuleHashesAttribute(AttributedElement enclosing, 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(AttributedElement enclosing, 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++) { + int endP = classReader.skipAttributeHolder(p + 4); + elements[i] = new BoundRecordComponentInfo(classReader, p, endP); + p = endP; + } + components = List.of(elements); + } + return components; + } + } + + public static final class BoundDeprecatedAttribute extends BoundAttribute + implements DeprecatedAttribute { + public BoundDeprecatedAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundSignatureAttribute extends BoundAttribute + implements SignatureAttribute { + public BoundSignatureAttribute(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundConstantValueAttribute extends BoundAttribute + implements ConstantValueAttribute { + public BoundConstantValueAttribute(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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(AttributedElement enclosing, 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 ConcreteEntry.ConcreteMethodHandleEntry handle + = (ConcreteEntry.ConcreteMethodHandleEntry) classReader.readMethodHandleEntry(p); + final List args = readEntryList(p + 2); + p += 4 + args.size() * 2; + int hash = ConcreteBootstrapMethodEntry.computeHashCode(handle, args); + bs[i] = new ConcreteBootstrapMethodEntry(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(AttributedElement enclosing, 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, outerClass, innerName, flags); + } + classes = List.of(elements); + } + return classes; + } + } + + public static final class BoundEnclosingMethodAttribute extends BoundAttribute + implements EnclosingMethodAttribute { + public BoundEnclosingMethodAttribute(AttributedElement enclosing, 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(AttributedElement enclosing, 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); + } + + @Override + public Kind codeKind() { + return Kind.TYPE_ANNOTATION; + } + + @Override + public Opcode opcode() { + return Opcode.TYPE_ANNO; + } + + @Override + public int sizeInBytes() { + return 0; + } + } + + 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); + } + + @Override + public Kind codeKind() { + return Kind.TYPE_ANNOTATION; + } + + @Override + public Opcode opcode() { + return Opcode.TYPE_ANNO; + } + + @Override + public int sizeInBytes() { + return 0; + } + } + + public static final class BoundRuntimeVisibleParameterAnnotationsAttribute + extends BoundAttribute + implements RuntimeVisibleParameterAnnotationsAttribute { + private final AttributedElement enclosing; + + public BoundRuntimeVisibleParameterAnnotationsAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.enclosing = enclosing; + } + + @Override + public List> parameterAnnotations() { + return AnnotationReader.readParameterAnnotations(classReader, payloadStart, true); + } + } + + public static final class BoundRuntimeInvisibleParameterAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleParameterAnnotationsAttribute { + private final AttributedElement enclosing; + + public BoundRuntimeInvisibleParameterAnnotationsAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.enclosing = enclosing; + } + + @Override + public List> parameterAnnotations() { + return AnnotationReader.readParameterAnnotations(classReader, payloadStart, false); + } + } + + 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(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List permittedSubclasses() { + if (permittedSubclasses == null) { + permittedSubclasses = readEntryList(payloadStart); + } + return permittedSubclasses; + } + } + + public static sealed abstract 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/classfile/impl/BoundCharacterRange.java b/src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.java new file mode 100644 index 0000000000000..6617b737a442e --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.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.classfile.impl; + +import jdk.classfile.CodeElement; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.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; + } + + @Override + public Opcode opcode() { + return Opcode.CHARACTER_RANGE; + } + + 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 Kind codeKind() { + return CodeElement.Kind.CHARACTER_RANGE; + } + + @Override + public int sizeInBytes() { + return 0; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java new file mode 100755 index 0000000000000..63a85657a99c5 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.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.classfile.impl; + +import jdk.classfile.Opcode; +import jdk.classfile.attribute.LocalVariableInfo; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.instruction.LocalVariable; + +/** + * LocalVariableImpl + */ +public final class BoundLocalVariable + extends AbstractBoundLocalVariable + implements LocalVariableInfo, + LocalVariable { + + public BoundLocalVariable(CodeImpl code, int offset) { + super(code, offset); + } + + @Override + public Opcode opcode() { + return Opcode.LOCAL_VARIABLE; + } + + @Override + public Utf8Entry type() { + return secondaryEntry(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariable(this); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.java b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.java new file mode 100755 index 0000000000000..cec66630be90f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.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.classfile.impl; + +import jdk.classfile.Opcode; +import jdk.classfile.attribute.LocalVariableTypeInfo; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.instruction.LocalVariableType; + +/** + * LocalVariableTypeImpl + */ +public final class BoundLocalVariableType + extends AbstractBoundLocalVariable + implements LocalVariableTypeInfo, + LocalVariableType { + + public BoundLocalVariableType(CodeImpl code, int offset) { + super(code, offset); + } + + @Override + public Opcode opcode() { + return Opcode.LOCAL_VARIABLE_TYPE; + } + + @Override + public Utf8Entry signature() { + return secondaryEntry(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariableType(this); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundRecordComponentInfo.java b/src/java.base/share/classes/jdk/classfile/impl/BoundRecordComponentInfo.java new file mode 100644 index 0000000000000..d883350005a5d --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundRecordComponentInfo.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.classfile.impl; + +import java.util.List; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassReader; +import jdk.classfile.attribute.RecordComponentInfo; +import jdk.classfile.constantpool.Utf8Entry; + +public final class BoundRecordComponentInfo + implements RecordComponentInfo { + + private final ClassReader reader; + private final int startPos, endPos, attributesPos; + private List> attributes; + + public BoundRecordComponentInfo(ClassReader reader, int startPos, int endPos) { + this.reader = reader; + this.startPos = startPos; + this.endPos = endPos; + 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) { + @SuppressWarnings("unchecked") + var res = (List>) BoundAttribute.readAttributes(null, reader, attributesPos, reader.customAttributes()); + attributes = res; + } + return attributes; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java new file mode 100755 index 0000000000000..77f5ea2739b9c --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + + +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.BufWriter; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.WritableElement; + +import java.nio.ByteBuffer; + +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.Utf8Entry; + +public final class BufWriterImpl implements BufWriter { + + private final ConstantPoolBuilder constantPool; + private LabelResolver labelResolver; + 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 LabelResolver labelResolver() { + return labelResolver; + } + + public void setLabelResolver(LabelResolver labelResolver) { + this.labelResolver = labelResolver; + } + @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 = constantPool.maybeClone(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/classfile/impl/BufferedCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java new file mode 100755 index 0000000000000..6593f465b42a3 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import jdk.classfile.BufWriter; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.TypeKind; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.Label; +import jdk.classfile.MethodModel; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.StoreInstruction; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * BufferedCodeBuilder + */ +public final class BufferedCodeBuilder + implements TerminalCodeBuilder, LabelContext { + private final ConstantPoolBuilder 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, + ConstantPoolBuilder 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; + } + + 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, Kind.CODE_ATTRIBUTE); + } + + @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 int labelToBci(Label label) { + throw new UnsupportedOperationException("nyi"); + } + + @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); + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.java new file mode 100755 index 0000000000000..5bfb35d3045a2 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.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.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.*; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.Utf8Entry; + +/** + * BufferedFieldBuilder + */ +public final class BufferedFieldBuilder + implements TerminalFieldBuilder { + private final ConstantPoolBuilder constantPool; + private final Utf8Entry name; + private final Utf8Entry desc; + private final List elements = new ArrayList<>(); + private AccessFlags flags; + private final FieldModel original; + + public BufferedFieldBuilder(ConstantPoolBuilder 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, Kind.FIELD); + } + + @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); + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.java new file mode 100755 index 0000000000000..afc638501b8ea --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.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.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.AccessFlags; + +import jdk.classfile.BufWriter; +import jdk.classfile.ClassModel; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.constantpool.Utf8Entry; + + +/** + * BufferedMethodBuilder + */ +public final class BufferedMethodBuilder + implements TerminalMethodBuilder, MethodInfo { + private final List elements = new ArrayList<>(); + private final ConstantPoolBuilder constantPool; + private final ClassEntry thisClass; + private final Utf8Entry name; + private final Utf8Entry desc; + private AccessFlags flags; + private final MethodModel original; + private int[] parameterSlots; + + public BufferedMethodBuilder(ConstantPoolBuilder constantPool, + ClassEntry thisClass, + Utf8Entry nameInfo, + Utf8Entry typeInfo, + MethodModel original) { + this.constantPool = constantPool; + this.thisClass = thisClass; + 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, Kind.METHOD); + } + + @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); + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java new file mode 100755 index 0000000000000..aaf927e0bd426 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; + +import static java.lang.invoke.MethodHandleInfo.REF_putStatic; +import static jdk.classfile.Opcode.IFEQ; +import static jdk.classfile.Opcode.IFGE; +import static jdk.classfile.Opcode.IFGT; +import static jdk.classfile.Opcode.IFLE; +import static jdk.classfile.Opcode.IFLT; +import static jdk.classfile.Opcode.IFNE; +import static jdk.classfile.Opcode.IFNONNULL; +import static jdk.classfile.Opcode.IFNULL; +import static jdk.classfile.Opcode.IF_ACMPEQ; +import static jdk.classfile.Opcode.IF_ACMPNE; +import static jdk.classfile.Opcode.IF_ICMPEQ; +import static jdk.classfile.Opcode.IF_ICMPGE; +import static jdk.classfile.Opcode.IF_ICMPGT; +import static jdk.classfile.Opcode.IF_ICMPLE; +import static jdk.classfile.Opcode.IF_ICMPLT; +import static jdk.classfile.Opcode.IF_ICMPNE; + +/** + * BytecodeHelpers + */ +public class BytecodeHelpers { + public static Map constantsToOpcodes = new HashMap<>(16); + + public 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"); + }; + } + + static Opcode reverseBranchOpcode(Opcode op) { + return switch (op) { + case IFEQ -> IFNE; + case IFNE -> IFEQ; + case IFLT -> IFGE; + case IFGE -> IFLT; + case IFGT -> IFLE; + case IFLE -> IFGT; + case IF_ICMPEQ -> IF_ICMPNE; + case IF_ICMPNE -> IF_ICMPEQ; + case IF_ICMPLT -> IF_ICMPGE; + case IF_ICMPGE -> IF_ICMPLT; + case IF_ICMPGT -> IF_ICMPLE; + case IF_ICMPLE -> IF_ICMPGT; + case IF_ACMPEQ -> IF_ACMPNE; + case IF_ACMPNE -> IF_ACMPEQ; + case IFNULL -> IFNONNULL; + case IFNONNULL -> 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.natEntry(bootstrapMethod.methodName(), + MethodTypeDesc.ofDescriptor(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 <= 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.natEntry(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.natEntry(desc.constantName(), + desc.constantType())); + } + + public static void validateValue(Opcode opcode, ConstantDesc v) { + switch (opcode) { + case ACONST_NULL -> { + if (v != null) + throw new IllegalArgumentException("value must be 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) { + // @@@ 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); + } + + private static void constantMapping(ConstantDesc c, Opcode o) { + constantsToOpcodes.put(c, o); + } + + static { + constantMapping(-1, Opcode.ICONST_M1); + constantMapping(0, Opcode.ICONST_0); + constantMapping(1, Opcode.ICONST_1); + constantMapping(2, Opcode.ICONST_2); + constantMapping(3, Opcode.ICONST_3); + constantMapping(4, Opcode.ICONST_4); + constantMapping(5, Opcode.ICONST_5); + constantMapping(0L, Opcode.LCONST_0); + constantMapping(1L, Opcode.LCONST_1); + constantMapping(0.0f, Opcode.FCONST_0); + constantMapping(1.0f, Opcode.FCONST_1); + constantMapping(2.0f, Opcode.FCONST_2); + constantMapping(0.0d, Opcode.DCONST_0); + constantMapping(1.0d, Opcode.DCONST_1); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/ChainedClassBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/ChainedClassBuilder.java new file mode 100755 index 0000000000000..5fef82b54256f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ChainedClassBuilder.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.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.*; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.Utf8Entry; + +/** + * ChainedClassBuilder + */ +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; + ClassBuilder b = downstream; + while (b instanceof ChainedClassBuilder cb) + b = cb.downstream; + terminal = (DirectClassBuilder) b; + } + + @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, terminal.thisClassEntry, + name, descriptor, null) + .run(handler) + .toModel()); + } + + @Override + public ClassBuilder transformMethod(MethodModel method, MethodTransform transform) { + BufferedMethodBuilder builder = new BufferedMethodBuilder(terminal.constantPool, terminal.thisClassEntry, + 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/classfile/impl/ChainedCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.java new file mode 100755 index 0000000000000..bccd4d082f3cd --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.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.classfile.impl; + +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.TypeKind; +import jdk.classfile.Label; + +import java.util.function.Consumer; + +/** + * PipedCodeBuilder + */ +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 terminal.allocateLocal(typeKind); + } + + @Override + public CodeBuilder with(CodeElement element) { + consumer.accept(element); + return this; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/ChainedFieldBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/ChainedFieldBuilder.java new file mode 100755 index 0000000000000..6d7817a8fe25f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ChainedFieldBuilder.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.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.FieldBuilder; +import jdk.classfile.FieldElement; +import jdk.classfile.FieldModel; +import jdk.classfile.constantpool.ConstantPoolBuilder; + +/** + * ChainedFieldBuilder + */ +public final class ChainedFieldBuilder implements FieldBuilder { + private final FieldBuilder downstream; + private final TerminalFieldBuilder terminal; + private final Consumer consumer; + + public ChainedFieldBuilder(FieldBuilder downstream, + Consumer consumer) { + this.downstream = downstream; + this.consumer = consumer; + FieldBuilder b = downstream; + while (b instanceof ChainedFieldBuilder cb) + b = cb.downstream; + terminal = (TerminalFieldBuilder) b; + } + + @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/classfile/impl/ChainedMethodBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/ChainedMethodBuilder.java new file mode 100755 index 0000000000000..ddc5ec2fe3472 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ChainedMethodBuilder.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.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.constantpool.ConstantPoolBuilder; + +/** + * ChainedMethodBuilder + */ +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; + MethodBuilder b = downstream; + while (b instanceof ChainedMethodBuilder cb) + b = cb.downstream; + terminal = (TerminalMethodBuilder) b; + } + + @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/classfile/impl/ClassHierarchyImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassHierarchyImpl.java new file mode 100755 index 0000000000000..e036b6ec6e038 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.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.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.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 ? Util.toClassDesc(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/classfile/impl/ClassImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java new file mode 100755 index 0000000000000..717d00e57204b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.ClassBuilder; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.AccessFlags; +import jdk.classfile.Attribute; +import jdk.classfile.AttributeMapper; +import jdk.classfile.Attributes; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassReader; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.ClassfileVersion; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.FieldModel; +import jdk.classfile.Interfaces; +import jdk.classfile.MethodModel; +import jdk.classfile.Superclass; + +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) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + int pos = reader.thisClassPos() + 4; + int cnt = reader.readU2(pos); + pos += 2; + ClassEntry[] arr = new ClassEntry[cnt]; + for (int i = 0; i < cnt; ++i) { + arr[i] = reader.readClassEntry(pos); + pos += 2; + } + this.interfaces = List.of(arr); + } + return interfaces; + } + + @Override + public List> attributes() { + if (attributes == null) { + @SuppressWarnings("unchecked") + var res = (List>) BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + attributes = res; + } + return attributes; + } + + // ClassModel + + @Override + public Kind attributedElementKind() { + return Kind.CLASS; + } + + @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(); + } + + 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.values()); + return found.isEmpty(); + } + + private static final Set> allowedModuleAttributes + = Set.of(Attributes.MODULE, + Attributes.MODULE_PACKAGES, + Attributes.MODULE_MAIN_CLASS, + 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/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java new file mode 100644 index 0000000000000..fe1d926d45bcf --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java @@ -0,0 +1,725 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.lang.constant.DirectMethodHandleDesc; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.classfile.AnnotationElement; +import jdk.classfile.AnnotationValue; +import jdk.classfile.AnnotationValue.*; +import jdk.classfile.Attribute; +import jdk.classfile.ClassModel; +import jdk.classfile.constantpool.AnnotationConstantValueEntry; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.MethodTypeEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.util.ClassPrinter; +import jdk.classfile.Instruction; +import jdk.classfile.instruction.*; +import jdk.classfile.MethodModel; +import jdk.classfile.TypeAnnotation; +import jdk.classfile.TypeKind; +import jdk.classfile.attribute.*; +import jdk.classfile.attribute.StackMapTableAttribute.*; + +import static jdk.classfile.Classfile.TAG_CLASS; +import static jdk.classfile.Classfile.TAG_CONSTANTDYNAMIC; +import static jdk.classfile.Classfile.TAG_DOUBLE; +import static jdk.classfile.Classfile.TAG_FIELDREF; +import static jdk.classfile.Classfile.TAG_FLOAT; +import static jdk.classfile.Classfile.TAG_INTEGER; +import static jdk.classfile.Classfile.TAG_INTERFACEMETHODREF; +import static jdk.classfile.Classfile.TAG_INVOKEDYNAMIC; +import static jdk.classfile.Classfile.TAG_LONG; +import static jdk.classfile.Classfile.TAG_METHODHANDLE; +import static jdk.classfile.Classfile.TAG_METHODREF; +import static jdk.classfile.Classfile.TAG_METHODTYPE; +import static jdk.classfile.Classfile.TAG_MODULE; +import static jdk.classfile.Classfile.TAG_NAMEANDTYPE; +import static jdk.classfile.Classfile.TAG_PACKAGE; +import static jdk.classfile.Classfile.TAG_STRING; +import static jdk.classfile.Classfile.TAG_UTF8; + +/** + * ClassPrinterImpl + */ +public final class ClassPrinterImpl implements ClassPrinter { + + public record Format(char quotes, boolean quoteFlagsAndAttrs, boolean quoteTypes, String mandatoryDelimiter, String inlineDelimiter, + Block classForm, Block constantPool, String valueEntry, String stringEntry, String namedEntry, String memberEntry, + String nameAndTypeEntry, String methodHandleEntry, String methodTypeEntry, String dynamicEntry, + String fieldsHeader, Block field, String methodsHeader, Block method, String simpleAttr, String simpleQuotedAttr, + String annotationDefault, Table annotations, Table typeAnnotations, String typeAnnotationInline, Table parameterAnnotations, + Table annotationValuePair, Table bootstrapMethods, String enclosingMethod, Table innerClasses, Table methodParameters, + Table recordComponents, String recordComponentTail, Block module, Table requires, Table exports, Table opens, Table provides, + String modulePackages, String moduleMain, String nestHost, String nestMembers, + Block code, String plainInstruction, String localVariableInstruction, String incInstruction, String memberInstruction, String invokeDynamicInstruction, + String branchInstruction, String switchInstruction, String newArrayInstruction, String typeInstruction, String constantInstruction, + Table exceptionHandlers, String tryStartInline, String tryEndInline, String handlerInline, Table localVariableTable, String localVariableInline, + String frameInline, Table stackMapTable, Table lineNumberTable, Table characterRangeTable, Table localVariableTypeTable, + Function escapeFunction) {} + + public record Block(String header, String footer){} + + public record Table(String header, String footer, String element){} + + private record ExceptionHandler(int start, int end, int handler, String catchType) {} + + public static final Format JSON = new Format('"', true, true, ",", ", ", + new Block(" { \"class name\": \"%s\",%n \"version\": \"%d.%d\",%n \"flags\": %s,%n \"superclass\": \"%s\",%n \"interfaces\": %s,%n \"attributes\": %s", "]%n }"), + new Block(",%n \"constant pool\": {", " }"), + "%n \"%d\": [\"%s\", \"%s\"]", + "%n \"%d\": [\"%s\", { \"value index:\": %d, \"value:\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"name index:\": %d, \"name:\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"owner index:\": %d, \"name and type index:\": %d, \"owner:\": \"%s\", \"name:\": \"%s\", \"type:\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"name index:\": %d, \"type index:\": %d, \"name:\": \"%s\", \"type:\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"reference kind:\": \"%s\", \"reference index:\": %d, \"owner:\": \"%s\", \"name:\": \"%s\", \"type:\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"descriptor index:\": %d, \"descriptor:\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"bootstrap method handle index:\": %d, \"bootstrap method arguments indexes:\": %s, \"name and type index:\": %d, \"name:\": \"%s\", \"type:\": \"%s\" }]", + ",%n \"fields\": [", + new Block("%n { \"field name\": \"%s\",%n \"flags\": %s,%n \"descriptor\": \"%s\",%n \"attributes\": %s", " }"), + "],%n \"methods\": [", + new Block("%n { \"method name\": \"%s\",%n \"flags\": %s,%n \"descriptor\": \"%s\",%n \"attributes\": %s", " }"), + ",%n%s\"%s\": %s", + ",%n%s\"%s\": \"%s\"", + ",%n%s\"annotation default\": \"%s\"", + new Table(",%n%s\"%s annotations\": [", "]", "%n%s {\"class\": \"%s\"%s}"), + new Table(",%n%s\"%s type annotations\": [", "]", "%n%s {\"class\": \"%s\", \"target type\": \"%s\"%s}"), + "",//JSON does not allow comments + new Table(",%n%s\"%s parameter %d annotations\": [", "]", "%n%s {\"class\": \"%s\"%s}"), + new Table(", \"values\": {", "}", "\"%s\": \"%s\""), + new Table(",%n \"bootstrap methods\": [", "]", "%n { \"bootstrap method kind\": \"%s\", \"owner\": \"%s\", \"method name\": \"%s\", \"invocation type\": \"%s\", \"is interface\": %b, \"is methods\": %b }"), + ",%n%s\"enclosing method\": { \"class\": \"%s\", \"method name\": \"%s\", \"type\": \"s\" }", + new Table(",%n \"inner classes\": [", "]", "%n { \"inner class\": \"%s\", \"outer class\": \"%s\", \"inner class entry\": \"%s\", \"flags\": %s }"), + new Table(",%n \"method parameters\": [", "]", "%n { \"parameter name\": \"%s\", \"flags\": %s }"), + new Table(",%n \"record components\": [", "]", "%n { \"name\": \"%s\", \"type\": \"%s\", \"attributes\": %s"), + " }", + new Block(",%n \"module\": {%n \"name\": \"%s\",%n \"flags\": %s,%n \"version\": \"%s\",%n \"uses\": %s", " }"), + new Table(",%n \"requires\": [", "]", "%n { \"name\": \"%s\", \"flags\": %s, \"version\": \"%s\" }"), + new Table(",%n \"exports\": [", "]", "%n { \"package\": \"%s\", \"flags\": %s, \"to\": %s }"), + new Table(",%n \"opens\": [", "]", "%n { \"package\": \"%s\", \"flags\": %s, \"to\": %s }"), + new Table(",%n \"provides\": [", "]", "%n { \"class\": \"%s\", \"with\": %s }"), + ",%n \"module packages\": %s", + ",%n \"module main class\": \"%s\"", + ",%n \"nest host\": \"%s\"", + ",%n \"nest members\": %s", + new Block(",%n \"code\": {%n \"max stack\": %d,%n \"max locals\": %d,%n \"attributes\": %s", " }"), + "%n \"%d\": [\"%s\"]", + "%n \"%d\": [\"%s\", { \"slot\": %d%s }]", + "%n \"%d\": [\"%s\" , { \"slot\": %d, \"const\": \"%+d\"%s }]", + "%n \"%d\": [\"%s\", { \"owner\": \"%s\", \"name\": \"%s\", \"descriptor\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"name\": \"%s\", \"descriptor\": \"%s\", \"bootstrap method kind\": \"%s\", \"owner\": \"%s\", \"method name\": \"%s\", \"invocation type\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"target\": %d }]", + "%n \"%d\": [\"%s\", { \"targets\": %s }]", + "%n \"%d\": [\"%s\", { \"dimensions\": %d, \"descriptor\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"type\": \"%s\" }]", + "%n \"%d\": [\"%s\", { \"constant value\": \"%s\" }]", + new Table(",%n \"exception handlers\": [", "]", "%n [%d, %d, %d, \"%s\"]"), + "", //JSON does not allow comments + "", + "", + new Table(",%n \"local variables%s\": [", "]", "%n [%d, %d, %d, \"%s\", \"%s\"]"), + ", \"type\": \"%s\", \"name\": \"%s\"", + "", + new Table(",%n \"stack map frames\": {", " }", "%n \"%d\": { \"locals\": %s, \"stack\": %s }"), + new Table(",%n \"line numbers%s\": [", "]", "%n [%d, %d]"), + new Table(",%n \"character ranges\": [", "]", "%n [%d, %d, %d, %d, %d]"), + new Table(",%n \"local variable types%s\": [", "]", "%n [%d, %d, %d, \"%s\", \"%s\"]"), + ClassPrinterImpl::escapeJson); + + public static final Format XML = new Format('\'', false, false, "", "", + new Block("%n ", "%n"), + new Block("%n ", ""), + "%n <:>%d<%s>%s", + "%n <:>%d<%s value_index='%d' value='%s'/>", + "%n <:>%d<%s name_index='%d' name='%s'/>", + "%n <:>%d<%s owner_index='%d' name_and_type_index='%d' owner='%s' name='%s' type='%s'/>", + "%n <:>%d<%s name_index='%d' type_index='%d' name='%s' type='%s'/>", + "%n <:>%d<%s reference_kind='%s' reference_index='%d' owner='%s' name='%s' type='%s'/>", + "%n <:>%d<%s descriptor_index='%d' descriptor='%s'/>", + "%n <:>%d<%s bootstrap_method_handle_index='%d' bootstrap_method_arguments_indexes:='%s' name_and_type_index='%d' name='%s' type='%s'/>", + "%n ", + new Block("%n ", ""), + "%n ", + new Block("%n ", ""), + "%n%s<%s>%s", + "%n%s<%s>%s", + "%n%s'%s'", + new Table(",%n%s<%s_annotations>", "", "%n%s %s"), + new Table(",%n%s<%s_type_annotations>", "", "%n%s %s"), + "%n ", + new Table(",%n%s<%s_parameter_%d_annotations>", "", "%n%s %s"), + new Table("", "", "%s"), + new Table(",%n ", "", "%n "), + "%n%s", + new Table(",%n ", "", "%n "), + new Table(",%n ", "", "%n "), + new Table(",%n ", "", "%n "), + "", + new Block(",%n ", ""), + new Table(",%n ", "", "%n "), + new Table(",%n ", "", "%n "), + new Table(",%n ", "", "%n "), + new Table(",%n ", "", "%n "), + ",%n %s", + ",%n %s", + ",%n %s", + ",%n %s", + new Block("%n ", ""), + "%n <:>%d<%s/>", + "%n <:>%d<%s slot='%d'%s/>", + "%n <:>%d<%s slot='%d' const='%+d'%s/>", + "%n <:>%d<%s owner='%s' name='%s' descriptor='%s'/>", + "%n <:>%d<%s name='%s' descriptor='%s' bootstrap_method_kind='%s' owner='%s' method_name='%s' invocation_type='%s'/>", + "%n <:>%d<%s target='%d'/>", + "%n <:>%d<%s targets='%s'/>", + "%n <:>%d<%s dimensions='%d' descriptor:='%s'/>", + "%n <:>%d<%s type='%s'/>", + "%n <:>%d<%s constant_value='%s'/>", + new Table("%n ", "", "%n "), + "%n ", + "%n ", + "%n ", + new Table("%n ", "", "%n "), + " type='%s' variable_name='%s'", + "%n ", + new Table("%n ", "", "%n <:>%d"), + new Table("%n ", "", "%n "), + new Table("%n ", "", "%n "), + new Table("%n ", "", "%n "), + ClassPrinterImpl::escapeXml); + + public static final Format YAML = new Format('\'', false, true, "", ", ", + new Block(" - class name: '%s'%n version: '%d.%d'%n flags: %s%n superclass: '%s'%n interfaces: %s%n attributes: %s", "%n"), + new Block("%n constant pool:", ""), + "%n %d: [%s, '%s']", + "%n %d: [%s, {value index: %d, value: '%s'}]", + "%n %d: [%s, {name index: %d, name: '%s'}]", + "%n %d: [%s, {owner index: %d, name and type index: %d, owner: '%s', name: '%s', type: '%s'}]", + "%n %d: [%s, {name index: %d, type index: %d, name: '%s', type: '%s'}]", + "%n %d: [%s, {reference kind: '%s', reference index: %d, owner: '%s', name: '%s', type: '%s'}]", + "%n %d: [%s, {descriptor index: %d, descriptor: '%s'}]", + "%n %d: [%s, {bootstrap method handle index: %d, bootstrap method arguments indexes: %s, name and type index: '%d', name: '%s', type: '%s'}]", + "%n fields:", + new Block("%n - field name: '%s'%n flags: %s%n descriptor: '%s'%n attributes: %s", ""), + "%n methods:", + new Block("%n - method name: '%s'%n flags: %s%n descriptor: '%s'%n attributes: %s", ""), + "%n%s%s: %s", + "%n%s%s: '%s'", + "%n%sannotation default: '%s'", + new Table("%n%s%s annotations:", "", "%n%s - {class: '%s'%s}"), + new Table("%n%s%s type annotations:", "", "%n%s - {class: '%s', target type: '%s'%s}"), + "%n #%s type annotation: {class: '%s', target type: '%s'%s}", + new Table("%n%s%s parameter %d annotations:", "", "%n%s - {class: '%s'%s}"), + new Table(", values: {", "}", "'%s': '%s'"), + new Table("%n bootstrap methods: #[, , , , , ]", "", "%n - ['%s', '%s', '%s', '%s', %b, %b]"), + "%n%senclosing method: {class: '%s', method name: '%s', type: 's'}", + new Table("%n inner classes: #[, , , ]", "", "%n - ['%s', '%s', '%s', %s]"), + new Table("%n method parameters: #[, ]", "", "%n - ['%s', %s]"), + new Table("%n record components:", "", "%n - name: '%s'%n type: '%s'%n attributes: %s"), + "", + new Block("%n module:%n name: '%s'%n flags: %s%n version: '%s'%n uses: %s", ""), + new Table("%n requires:", "", "%n - { name: '%s', flags: %s, version: '%s' }"), + new Table("%n exports:", "", "%n - { package: '%s', flags: %s, to: %s }"), + new Table("%n opens:", "", "%n - { package: '%s', flags: %s, to: %s }"), + new Table("%n provides:", "", "%n - { class: '%s', with: %s }"), + "%n module packages: %s", + "%n module main class: '%s'", + "%n nest host: '%s'", + "%n nest members: %s", + new Block("%n code:%n max stack: %d%n max locals: %d%n attributes: %s", ""), + "%n %d: [%s]", + "%n %d: [%s, {slot: %d%s}]", + "%n %d: [%s, {slot: %d, const: %+d%s}]", + "%n %d: [%s, {owner: '%s', name: '%s', descriptor: '%s'}]", + "%n %d: [%s, {name: '%s', descriptor: '%s', bootstrap method kind: %s, owner: '%s', method name: '%s', invocation type: '%s'}]", + "%n %d: [%s, {target: %d}]", + "%n %d: [%s, {targets: %s}]", + "%n %d: [%s, {dimensions: %d, descriptor: '%s'}]", + "%n %d: [%s, {type: '%s'}]", + "%n %d: [%s, {constant value: '%s'}]", + new Table("%n exception handlers: #[, , , ]", "", "%n - [%d, %d, %d, %s]"), + "%n #try block start: {start: %d, end: %d, handler: %d, catch type: %s}", + "%n #try block end: {start: %d, end: %d, handler: %d, catch type: %s}", + "%n #exception handler start: {start: %d, end: %d, handler: %d, catch type: %s}", + new Table("%n local variables: #[, , '', ]", "", "%n - [%d, %d, %d, '%s', '%s']"), + ", type: '%s', variable name: '%s'", + "%n #stack map frame locals: %s, stack: %s", + new Table("%n stack map frames:", "", "%n %d: {locals: %s, stack: %s}"), + new Table("%n line numbers: #[, ]", "", "%n - [%d, %d]"), + new Table("%n character ranges: #[, , , , ]", "", "%n - [%d, %d, %d, '%s', '%s']"), + new Table("%n local variable types: #[, , '', ]", "", "%n - [%d, %d, %d, '%s', '%s']"), + ClassPrinterImpl::escapeYaml); + + private static final char[] DIGITS = "0123456789ABCDEF".toCharArray(); + + private List quoteFlags(Set> flags) { + return flags.stream().map(f -> format.quoteFlagsAndAttrs ? format.quotes + f.name() + format.quotes : f.name()).toList(); + } + + private String escape(String s) { + return format.escapeFunction.apply(s); + } + + private String typesToString(Stream strings) { + return strings.map(s -> format.quoteTypes ? format.quotes + s + format.quotes : s).collect(Collectors.joining(", ", "[", "]")); + } + + private String attributeNames(List> attributes) { + return attributes.stream().map(a -> format.quoteFlagsAndAttrs ? format.quotes + a.attributeName() + format.quotes : a.attributeName()).collect(Collectors.joining(", ", "[", "]")); + } + + private String elementValueToString(AnnotationValue v) { + return switch (v) { + case OfConstant cv -> format.escapeFunction().apply(v.tag() == 'Z' ? String.valueOf((int)cv.constantValue() != 0) : String.valueOf(cv.constantValue())); + case OfClass clv -> clv.className().stringValue(); + case OfEnum ev -> ev.className().stringValue() + "." + ev.constantName().stringValue(); + case OfAnnotation av -> v.tag() + av.annotation().className().stringValue(); + case OfArray av -> av.values().stream().map(ev -> elementValueToString(ev)).collect(Collectors.joining(", ", "[", "]")); + }; + } + + private String elementValuePairsToString(List evps) { + return evps.isEmpty() ? "" : evps.stream().map(evp -> format.annotationValuePair.element.formatted(evp.name().stringValue(), elementValueToString(evp.value()))) + .collect(Collectors.joining(format.inlineDelimiter, format.annotationValuePair.header, format.annotationValuePair.footer)); + } + + private static String escapeXml(String s) { + 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 escapeYaml(String s) { + var sb = new StringBuilder(s.length() << 1); + s.chars().forEach(c -> { + switch (c) { + case '\'' -> sb.append("''"); + default -> escape(c, sb); + }}); + return sb.toString(); + } + + private static String escapeJson(String s) { + var sb = new StringBuilder(s.length() << 1); + s.chars().forEach(c -> escape(c, sb)); + return sb.toString(); + } + + 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]); + } + } + } + } + + private static String formatDescriptor(String desc) { + int i = desc.lastIndexOf('['); + if (i >= 0) desc = desc.substring(i + 1); + desc = switch (desc) { + case "I", "B", "Z", "F", "S", "C", "J", "D" -> TypeKind.fromDescriptor(desc).typeName(); + default -> desc = Util.descriptorToClass(desc); + }; + if (i >= 0) { + var ret = new StringBuilder(desc.length() + 2*i + 2).append(desc); + while (i-- >= 0) ret.append('[').append(']'); + return ret.toString(); + } + return desc; + } + + private static Stream convertVTIs(List vtis) { + return vtis.stream().mapMulti((vti, ret) -> { + var s = formatDescriptor(vti.toString()); + ret.accept(s); + if (vti.type() == StackMapTableAttribute.VerificationType.ITEM_DOUBLE || vti.type() == StackMapTableAttribute.VerificationType.ITEM_LONG) + ret.accept(s + "2"); + }); + } + + private final Format format; + private final VerbosityLevel verbosity; + private final Consumer out; + + public ClassPrinterImpl(Format format, VerbosityLevel verbosity, Consumer out) { + this.format = format; + this.verbosity = verbosity; + this.out = out; + } + + @Override + public void printClass(ClassModel clm) { + out.accept(format.classForm.header().formatted(clm.thisClass().asInternalName(), clm.majorVersion(), clm.minorVersion(), quoteFlags(clm.flags().flags()), clm.superclass().map(ClassEntry::asInternalName).orElse(null), typesToString(clm.interfaces().stream().map(ClassEntry::asInternalName)), attributeNames(clm.attributes()))); + if (verbosity == VerbosityLevel.TRACE_ALL) { + out.accept(format.constantPool.header.formatted()); + for (int i = 1; i < clm.constantPool().entryCount();) { + if (i > 1) out.accept(format.mandatoryDelimiter); + var e = clm.constantPool().entryByIndex(i); + printCPEntry(e); + i += e.poolEntries(); + } + out.accept(format.constantPool.footer.formatted()); + } + if (verbosity != VerbosityLevel.MEMBERS_ONLY) printAttributes(" ", clm.attributes()); + out.accept(format.fieldsHeader.formatted()); + boolean first = true; + for (var f : clm.fields()) { + if (first) first = false; else out.accept(format.mandatoryDelimiter); + out.accept(format.field.header().formatted(f.fieldName().stringValue(), quoteFlags(f.flags().flags()), f.fieldType().stringValue(), attributeNames(f.attributes()))); + if (verbosity != VerbosityLevel.MEMBERS_ONLY) printAttributes(" ", f.attributes()); + out.accept(format.field.footer().formatted()); + } + out.accept(format.methodsHeader.formatted()); + first = true; + for (var m : clm.methods()) { + if (first) first = false; else out.accept(format.mandatoryDelimiter); + printMethod(m); + } + out.accept(format.classForm.footer().formatted()); + } + + + public static String tagName(byte tag) { + return switch (tag) { + case TAG_UTF8 -> "CONSTANT_Utf8"; + case TAG_INTEGER -> "CONSTANT_Integer"; + case TAG_FLOAT -> "CONSTANT_Float"; + case TAG_LONG -> "CONSTANT_Long"; + case TAG_DOUBLE -> "CONSTANT_Double"; + case TAG_CLASS -> "CONSTANT_Class"; + case TAG_STRING -> "CONSTANT_String"; + case TAG_FIELDREF -> "CONSTANT_Fieldref"; + case TAG_METHODREF -> "CONSTANT_Methodref"; + case TAG_INTERFACEMETHODREF -> "CONSTANT_InterfaceMethodref"; + case TAG_NAMEANDTYPE -> "CONSTANT_NameAndType"; + case TAG_METHODHANDLE -> "CONSTANT_MethodHandle"; + case TAG_METHODTYPE -> "CONSTANT_MethodType"; + case TAG_CONSTANTDYNAMIC -> "CONSTANT_Dynamic"; + case TAG_INVOKEDYNAMIC -> "CONSTANT_InvokeDynamic"; + case TAG_MODULE -> "CONSTANT_Module"; + case TAG_PACKAGE -> "CONSTANT_Package"; + default -> null; + }; + } + + private void printCPEntry(PoolEntry e) { + out.accept(switch (e) { + case Utf8Entry ve -> printValueEntry(ve); + case IntegerEntry ve -> printValueEntry(ve); + case FloatEntry ve -> printValueEntry(ve); + case LongEntry ve -> printValueEntry(ve); + case DoubleEntry ve -> printValueEntry(ve); + case ClassEntry ce -> printNamedEntry(e, ce.name()); + case StringEntry se -> format.stringEntry.formatted(e.index(), tagName(e.tag()), se.utf8().index(), escape(se.stringValue())); + case MemberRefEntry mre -> format.memberEntry.formatted(e.index(), tagName(e.tag()), mre.owner().index(), mre.nameAndType().index(), escape(mre.owner().asInternalName()), escape(mre.name().stringValue()), escape(mre.type().stringValue())); + case NameAndTypeEntry nte -> format.nameAndTypeEntry.formatted(e.index(), tagName(e.tag()), nte.name().index(), nte.type().index(), escape(nte.name().stringValue()), escape(nte.type().stringValue())); + case MethodHandleEntry mhe -> format.methodHandleEntry.formatted(e.index(), tagName(e.tag()), DirectMethodHandleDesc.Kind.valueOf(mhe.kind()), mhe.reference().index(), escape(mhe.reference().owner().asInternalName()), escape(mhe.reference().name().stringValue()), escape(mhe.reference().type().stringValue())); + case MethodTypeEntry mte -> format.methodTypeEntry.formatted(e.index(), tagName(e.tag()), mte.descriptor().index(), escape(mte.descriptor().stringValue())); + case ConstantDynamicEntry cde -> printDynamicEntry(cde); + case InvokeDynamicEntry ide -> printDynamicEntry(ide); + case ModuleEntry me -> printNamedEntry(e, me.name()); + case PackageEntry pe -> printNamedEntry(e, pe.name()); + }); + } + + private String printValueEntry(AnnotationConstantValueEntry e) { + return format.valueEntry.formatted(e.index(), tagName(e.tag()), escape(String.valueOf(e.constantValue()))); + } + + private String printNamedEntry(PoolEntry e, Utf8Entry name) { + return format.namedEntry.formatted(e.index(), tagName(e.tag()), name.index(), escape(name.stringValue())); + } + + private String printDynamicEntry(DynamicConstantPoolEntry dcpe) { + return format.dynamicEntry.formatted(dcpe.index(), tagName(dcpe.tag()), dcpe.bootstrap().bootstrapMethod().index(), + dcpe.bootstrap().arguments().stream().map(en -> en.index()).toList(), + dcpe.nameAndType().index(), escape(dcpe.name().stringValue()), escape(dcpe.type().stringValue())); + } + + private void printAttributes(String indentSpace, List> attributes) { + for (var attr : attributes) { + switch (attr) { + case BootstrapMethodsAttribute bma -> + printTable(format.bootstrapMethods, bma.bootstrapMethods(), bm -> { + var mh = bm.bootstrapMethod(); + var mref = mh.reference(); + return new Object[] {DirectMethodHandleDesc.Kind.valueOf(mh.kind(), mref.isInterface()), mref.owner().asInternalName(), mref.nameAndType().name().stringValue(), mref.nameAndType().type().stringValue(), mref.isInterface(), mref.isMethod()}; + }); + case ConstantValueAttribute cva -> + out.accept(format.simpleQuotedAttr.formatted(indentSpace, "value", escape(cva.constant().constantValue().toString()))); + case NestHostAttribute nha -> + out.accept(format.nestHost.formatted(nha.nestHost().asInternalName())); + case NestMembersAttribute nma -> + out.accept(format.nestMembers.formatted(typesToString(nma.nestMembers().stream().map(mp -> mp.asInternalName())))); + case PermittedSubclassesAttribute psa -> + out.accept(format.simpleAttr.formatted(indentSpace, "subclasses", typesToString(psa.permittedSubclasses().stream().map(e -> e.asInternalName())))); + default -> {} + } + if (verbosity == VerbosityLevel.TRACE_ALL) switch (attr) { + case EnclosingMethodAttribute ema -> + out.accept(format.enclosingMethod.formatted(indentSpace, ema.enclosingClass().asInternalName(), ema.enclosingMethod().map(e -> escape(e.name().stringValue())).orElse(null), ema.enclosingMethod().map(e -> escape(e.type().stringValue())).orElse(null))); + case ExceptionsAttribute exa -> + out.accept(format.simpleAttr.formatted(indentSpace, "exceptions", typesToString(exa.exceptions().stream().map(e -> e.asInternalName())))); + case InnerClassesAttribute ica -> + printTable(format.innerClasses, ica.classes(), ic -> new Object[] {ic.innerClass().asInternalName(), ic.outerClass().map(e -> escape(e.asInternalName())).orElse(null), ic.innerName().map(e -> escape(e.stringValue())).orElse(null), quoteFlags(ic.flags())}); + case MethodParametersAttribute mpa -> + printTable(format.methodParameters, mpa.parameters(), mp -> new Object[]{mp.name().map(e -> escape(e.stringValue())).orElse(null), quoteFlags(mp.flags())}); + case ModuleAttribute ma -> { + out.accept(format.module.header.formatted(ma.moduleName().name().stringValue(), quoteFlags(ma.moduleFlags()), ma.moduleVersion().map(Utf8Entry::stringValue).orElse(""), typesToString(ma.uses().stream().map(ce -> ce.asInternalName())))); + printTable(format.requires, ma.requires(), req -> new Object[] {req.requires().name().stringValue(), quoteFlags(req.requiresFlags()), req.requiresVersion().map(Utf8Entry::stringValue).orElse(null)}); + printTable(format.exports, ma.exports(), exp -> new Object[] {exp.exportedPackage().name().stringValue(), quoteFlags(exp.exportsFlags()), typesToString(exp.exportsTo().stream().map(me -> me.name().stringValue()))}); + printTable(format.opens, ma.opens(), open -> new Object[] {open.openedPackage().name().stringValue(), quoteFlags(open.opensFlags()), typesToString(open.opensTo().stream().map(me -> me.name().stringValue()))}); + printTable(format.provides, ma.provides(), provide -> new Object[] {provide.provides().asInternalName(), typesToString(provide.providesWith().stream().map(me -> me.asInternalName()))}); + out.accept(format.module.footer.formatted()); + } + case ModulePackagesAttribute mopa -> + out.accept(format.modulePackages.formatted(typesToString(mopa.packages().stream().map(mp -> mp.name().stringValue())))); + case ModuleMainClassAttribute mmca -> + out.accept(format.moduleMain.formatted(mmca.mainClass().asInternalName())); + case RecordAttribute ra -> + printTable(format.recordComponents, ra.components(), rc -> new Object[]{rc.name().stringValue(), rc.descriptor().stringValue(), attributeNames(rc.attributes())}, rc -> { + printAttributes(" ", rc.attributes()); + out.accept(format.recordComponentTail); + }); + case AnnotationDefaultAttribute ada -> + out.accept(format.annotationDefault.formatted(indentSpace, elementValueToString(ada.defaultValue()))); + case RuntimeInvisibleAnnotationsAttribute riaa -> + printTable(format.annotations, riaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "invisible"); + case RuntimeVisibleAnnotationsAttribute rvaa -> + printTable(format.annotations, rvaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "visible"); + case RuntimeInvisibleParameterAnnotationsAttribute ripaa -> { + int i = 0; + for (var pa : ripaa.parameterAnnotations()) { + i++; + if (!pa.isEmpty()) printTable(format.parameterAnnotations, pa, a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "invisible", i); + } + } + case RuntimeVisibleParameterAnnotationsAttribute rvpaa -> { + int i = 0; + for (var pa : rvpaa.parameterAnnotations()) { + i++; + if (!pa.isEmpty()) printTable(format.parameterAnnotations, pa, a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "visible", i); + } + } + case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> + printTable(format.typeAnnotations, ritaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements())}, indentSpace, "invisible"); + case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> + printTable(format.typeAnnotations, rvtaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements())}, indentSpace, "visible"); + case SignatureAttribute sa -> + out.accept(format.simpleQuotedAttr.formatted(indentSpace, "signature", escape(sa.signature().stringValue()))); + case SourceFileAttribute sfa -> + out.accept(format.simpleQuotedAttr.formatted(indentSpace, "source", escape(sfa.sourceFile().stringValue()))); + default -> {} + } + } + } + + private String findLocal(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 format.localVariableInline.formatted(formatDescriptor(l.type().stringValue()), l.name().stringValue()); + } + } + } + return ""; + } + + private void printTable(Table tfmt, Collection elements, Function arguments, Object ... headerArgs) { + printTable(tfmt, elements, arguments, null, headerArgs); + } + + private void printTable(Table tfmt, Collection elements, Function arguments, Consumer afterElement, Object ... headerAndFooterArgs) { + out.accept(tfmt.header().formatted(headerAndFooterArgs)); + boolean first = true; + for (var e : elements) { + if (first) first = false; else out.accept(format.mandatoryDelimiter); + out.accept(tfmt.element().formatted(arguments.apply(e))); + if (afterElement != null) afterElement.accept(e); + } + out.accept(tfmt.footer().formatted(headerAndFooterArgs)); + } + + private void forEachOffset(TypeAnnotation ta, LabelResolver 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 -> {} + } + } + + @Override + public void printMethod(MethodModel m) { + out.accept(format.method.header().formatted(escape(m.methodName().stringValue()), quoteFlags(m.flags().flags()), m.methodType().stringValue(), attributeNames(m.attributes()))); + if (verbosity != VerbosityLevel.MEMBERS_ONLY) { + printAttributes(" ", m.attributes()); + m.code().ifPresent(com -> { + out.accept(format.code.header().formatted(((CodeAttribute)com).maxStack(), ((CodeAttribute)com).maxLocals(), attributeNames(com.attributes()))); + var stackMap = new LinkedHashMap(); + var visibleTypeAnnos = new LinkedHashMap(); + var invisibleTypeAnnos = new LinkedHashMap(); + List locals = List.of(); + int lnc =0, lvc = 0, lvtc = 0; + for (var attr : com.attributes()) { + if (attr instanceof StackMapTableAttribute smta) { + for (var smf : smta.entries()) { + stackMap.put(smf.absoluteOffset(), smf); + } + printTable(format.stackMapTable, stackMap.values(), f -> new Object[]{f.absoluteOffset(), typesToString(convertVTIs(f.effectiveLocals())), typesToString(convertVTIs(f.effectiveStack()))}); + } else if (verbosity == VerbosityLevel.TRACE_ALL) switch (attr) { + case LocalVariableTableAttribute lvta -> + printTable(format.localVariableTable, locals = lvta.localVariables(), lv -> new Object[]{lv.startPc(), lv.startPc() + lv.length(), lv.slot(), lv.name().stringValue(), formatDescriptor(lv.type().stringValue())}, ++lvc < 2 ? "" : " #" + lvc); + case LineNumberTableAttribute lnta -> + printTable(format.lineNumberTable, lnta.lineNumbers(), lni -> new Object[] {lni.startPc(), lni.lineNumber()}, ++lnc < 2 ? "" : " #"+lnc); + case CharacterRangeTableAttribute crta -> + printTable(format.characterRangeTable, crta.characterRangeTable(), chr -> new Object[] {chr.startPc(), chr.endPc(), chr.characterRangeStart(), chr.characterRangeEnd(), chr.flags()}); + case LocalVariableTypeTableAttribute lvtta -> + printTable(format.localVariableTypeTable, lvtta.localVariableTypes(), lvt -> new Object[]{lvt.startPc(), lvt.startPc() + lvt.length(), lvt.slot(), lvt.name().stringValue(), formatDescriptor(lvt.signature().stringValue())}, ++lvtc < 2 ? "" : " #" + lvtc); + case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> + rvtaa.annotations().forEach(a -> forEachOffset(a, com, visibleTypeAnnos::put)); + case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> + ritaa.annotations().forEach(a -> forEachOffset(a, com, invisibleTypeAnnos::put)); + case Object o -> {} + } + } + printAttributes(" ", com.attributes()); + stackMap.putIfAbsent(0, StackMapDecoder.initFrame(m)); + 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.asInternalName()).orElse(null))).toList(); + int bci = 0; + for (var coe : com) { + if (coe instanceof Instruction ins) { + var frame = stackMap.get(bci); + if (frame != null) { + out.accept(format.frameInline.formatted(typesToString(convertVTIs(frame.effectiveLocals())), typesToString(convertVTIs(frame.effectiveStack())))); + } + var a = invisibleTypeAnnos.get(bci); + if (a != null) { + out.accept(format.typeAnnotationInline.formatted("invisible", a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements()))); + } + a = visibleTypeAnnos.get(bci); + if (a != null) { + out.accept(format.typeAnnotationInline.formatted("visible", a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements()))); + } + for (var exc : excHandlers) { + if (exc.start() == bci) { + out.accept(format.tryStartInline.formatted(exc.start(), exc.end(), exc.handler(), exc.catchType())); + } + if (exc.end() == bci) { + out.accept(format.tryEndInline.formatted(exc.start(), exc.end(), exc.handler(), exc.catchType())); + } + if (exc.handler() == bci) { + out.accept(format.handlerInline.formatted(exc.start(), exc.end(), exc.handler(), exc.catchType())); + } + } + out.accept(format.mandatoryDelimiter); + switch (coe) { + case IncrementInstruction inc -> + out.accept(format.incInstruction.formatted(bci, ins.opcode().name(), inc.slot(), inc.constant(), findLocal(locals, inc.slot(), bci))); + case LoadInstruction lv -> + out.accept(format.localVariableInstruction.formatted(bci, ins.opcode().name(), lv.slot(), findLocal(locals, lv.slot(), bci))); + case StoreInstruction lv -> + out.accept(format.localVariableInstruction.formatted(bci, ins.opcode().name(), lv.slot(), findLocal(locals, lv.slot(), bci))); + case FieldInstruction fa -> + out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), fa.owner(), fa.name().stringValue(), fa.type())); + case InvokeInstruction inv -> + out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), inv.owner().asInternalName(), escape(inv.name().stringValue()), inv.type().stringValue())); + case InvokeDynamicInstruction invd -> { + var bm = invd.bootstrapMethod(); + out.accept(format.invokeDynamicInstruction.formatted(bci, ins.opcode().name(), invd.name().stringValue(), invd.type().stringValue(), bm.kind(), bm.owner().descriptorString(), escape(bm.methodName()), bm.invocationType().descriptorString())); + } + case NewObjectInstruction newo -> + out.accept(format.typeInstruction.formatted(bci, ins.opcode().name(), newo.className().asInternalName())); + case NewPrimitiveArrayInstruction newa -> out.accept(format.newArrayInstruction.formatted(bci, ins.opcode().name(), 1, newa.typeKind().descriptor())); + case NewReferenceArrayInstruction newa -> out.accept(format.newArrayInstruction.formatted(bci, ins.opcode().name(), 1, newa.componentType().asInternalName())); + case NewMultiArrayInstruction newa -> out.accept(format.newArrayInstruction.formatted(bci, ins.opcode().name(), newa.dimensions(), newa.arrayType().asInternalName())); + case TypeCheckInstruction tch -> + out.accept(format.typeInstruction.formatted(bci, ins.opcode().name(), tch.type().asInternalName())); + case ConstantInstruction cons -> + out.accept(format.constantInstruction.formatted(bci, ins.opcode().name(), escape(String.valueOf(cons.constantValue())))); + case BranchInstruction br -> + out.accept(format.branchInstruction.formatted(bci, ins.opcode().name(), com.labelToBci(br.target()))); + case LookupSwitchInstruction ls -> + out.accept(format.switchInstruction.formatted(bci, ins.opcode().name(), Stream.concat(Stream.of(ls.defaultTarget()).map(com::labelToBci), ls.cases().stream().map(c -> com.labelToBci(c.target()))).toList())); + case TableSwitchInstruction ts -> + out.accept(format.switchInstruction.formatted(bci, ins.opcode().name(), Stream.concat(Stream.of(ts.defaultTarget()).map(com::labelToBci), ts.cases().stream().map(c -> com.labelToBci(c.target()))).toList())); + default -> + out.accept(format.plainInstruction.formatted(bci, ins.opcode().name())); + } + bci += ins.sizeInBytes(); + } + } + out.accept(format.code.footer().formatted()); + if (excHandlers.size() > 0) { + printTable(format.exceptionHandlers, excHandlers, exc -> new Object[]{exc.start(), exc.end(), exc.handler(), exc.catchType()}); + } + }); + } + out.accept(format.method.footer().formatted()); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassReaderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassReaderImpl.java new file mode 100755 index 0000000000000..44094ed1ff16f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassReaderImpl.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.*; +import jdk.classfile.attribute.BootstrapMethodsAttribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.Utf8Entry; + +import static jdk.classfile.Classfile.TAG_CLASS; +import static jdk.classfile.Classfile.TAG_CONSTANTDYNAMIC; +import static jdk.classfile.Classfile.TAG_DOUBLE; +import static jdk.classfile.Classfile.TAG_FIELDREF; +import static jdk.classfile.Classfile.TAG_FLOAT; +import static jdk.classfile.Classfile.TAG_INTEGER; +import static jdk.classfile.Classfile.TAG_INTERFACEMETHODREF; +import static jdk.classfile.Classfile.TAG_INVOKEDYNAMIC; +import static jdk.classfile.Classfile.TAG_LONG; +import static jdk.classfile.Classfile.TAG_METHODHANDLE; +import static jdk.classfile.Classfile.TAG_METHODREF; +import static jdk.classfile.Classfile.TAG_METHODTYPE; +import static jdk.classfile.Classfile.TAG_MODULE; +import static jdk.classfile.Classfile.TAG_NAMEANDTYPE; +import static jdk.classfile.Classfile.TAG_PACKAGE; +import static jdk.classfile.Classfile.TAG_STRING; +import static jdk.classfile.Classfile.TAG_UTF8; + +/** + * ClassReaderImpl + */ +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; + + @SuppressWarnings("unchecked") + 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]; + + p = metadataStart; + 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 T optionValue(Classfile.Option.Key option) { + return options.value(option); + } + + @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 ConcreteBootstrapMethodEntry bootstrapMethodEntry(int index) { + return bsmEntries().get(index); + } + + public int readU1(int p) { + return buffer[p] & 0xFF; + } + + public int readU2(int p) { + int b1 = buffer[p] & 0xFF; + int b2 = buffer[p + 1] & 0xFF; + return (b1 << 8) + b2; + } + + public int readS1(int p) { + return buffer[p]; + } + + public int readS2(int p) { + int b1 = buffer[p]; + int b2 = buffer[p + 1] & 0xFF; + return (b1 << 8) + b2; + } + + 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 << 0); + } + + 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) << 0); + } + + public float readFloat(int p) { + return Float.intBitsToFloat(readInt(p)); + } + + public double readDouble(int p) { + return Double.longBitsToDouble(readLong(p)); + } + + public byte[] readBytes(int p, int len) { + return Arrays.copyOfRange(buffer, p, p + len); + } + + 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.size() > 0) { + for (BootstrapMethodEntry bm : list) { + ConcreteEntry.ConcreteMethodHandleEntry handle = (ConcreteEntry.ConcreteMethodHandleEntry) bm.bootstrapMethod(); + List args = bm.arguments(); + int hash = ConcreteBootstrapMethodEntry.computeHashCode(handle, args); + bsmEntries.add(new ConcreteBootstrapMethodEntry(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; + } + + WritableElement bootstrapMethodsWriter() { + return containedClass.findAttribute(Attributes.BOOTSTRAP_METHODS) + .orElse(null); + } + + void writeConstantPoolEntries(BufWriter buf) { + copyBytesTo(buf, ClassReaderImpl.CP_ITEM_START, + metadataStart - ClassReaderImpl.CP_ITEM_START); + } + + // Constantpool + 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 ConcreteEntry.ConcreteUtf8Entry(this, index, buffer, q + 2, readU2(q)); + case TAG_INTEGER -> new ConcreteEntry.ConcreteIntegerEntry(this, index, readInt(q)); + case TAG_FLOAT -> new ConcreteEntry.ConcreteFloatEntry(this, index, readFloat(q)); + case TAG_LONG -> new ConcreteEntry.ConcreteLongEntry(this, index, readLong(q)); + case TAG_DOUBLE -> new ConcreteEntry.ConcreteDoubleEntry(this, index, readDouble(q)); + case TAG_CLASS -> new ConcreteEntry.ConcreteClassEntry(this, index, (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q)); + case TAG_STRING -> new ConcreteEntry.ConcreteStringEntry(this, index, (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q)); + case TAG_FIELDREF -> new ConcreteEntry.ConcreteFieldRefEntry(this, index, (ConcreteEntry.ConcreteClassEntry) readClassEntry(q), + (ConcreteEntry.ConcreteNameAndTypeEntry) readNameAndTypeEntry(q + 2)); + case TAG_METHODREF -> new ConcreteEntry.ConcreteMethodRefEntry(this, index, (ConcreteEntry.ConcreteClassEntry) readClassEntry(q), + (ConcreteEntry.ConcreteNameAndTypeEntry) readNameAndTypeEntry(q + 2)); + case TAG_INTERFACEMETHODREF -> new ConcreteEntry.ConcreteInterfaceMethodRefEntry(this, index, (ConcreteEntry.ConcreteClassEntry) readClassEntry(q), + (ConcreteEntry.ConcreteNameAndTypeEntry) readNameAndTypeEntry(q + 2)); + case TAG_NAMEANDTYPE -> new ConcreteEntry.ConcreteNameAndTypeEntry(this, index, (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q), + (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q + 2)); + case TAG_METHODHANDLE -> new ConcreteEntry.ConcreteMethodHandleEntry(this, index, readU1(q), + (ConcreteEntry.MemberRefEntry) readEntry(q + 1)); + case TAG_METHODTYPE -> new ConcreteEntry.ConcreteMethodTypeEntry(this, index, (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q)); + case TAG_CONSTANTDYNAMIC -> new ConcreteEntry.ConcreteConstantDynamicEntry(this, index, readU2(q), (ConcreteEntry.ConcreteNameAndTypeEntry) readNameAndTypeEntry(q + 2)); + case TAG_INVOKEDYNAMIC -> new ConcreteEntry.ConcreteInvokeDynamicEntry(this, index, readU2(q), (ConcreteEntry.ConcreteNameAndTypeEntry) readNameAndTypeEntry(q + 2)); + case TAG_MODULE -> new ConcreteEntry.ConcreteModuleEntry(this, index, (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q)); + case TAG_PACKAGE -> new ConcreteEntry.ConcretePackageEntry(this, index, (ConcreteEntry.ConcreteUtf8Entry) readUtf8Entry(q)); + default -> throw new IllegalStateException( + "Bad tag (" + tag + ") at index (" + index + ") position (" + offset + ")"); + }; + cp[index] = info; + } + return info; + } + + @Override + public ConcreteEntry.ConcreteUtf8Entry 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); + ConcreteEntry.ConcreteUtf8Entry uinfo + = new ConcreteEntry.ConcreteUtf8Entry(this, index, buffer, q + 2, readU2(q)); + cp[index] = uinfo; + return uinfo; + } + return (ConcreteEntry.ConcreteUtf8Entry) 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) { + return (ModuleEntry) readEntry(pos); + } + + @Override + public PackageEntry readPackageEntry(int pos) { + return (PackageEntry) readEntry(pos); + } + + @Override + public ClassEntry readClassEntry(int pos) { + return (ClassEntry) readEntry(pos); + } + + @Override + public NameAndTypeEntry readNameAndTypeEntry(int pos) { + return (NameAndTypeEntry) readEntry(pos); + } + + @Override + public MethodHandleEntry readMethodHandleEntry(int pos) { + return (MethodHandleEntry) readEntry(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/classfile/impl/ClassfileVersionImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.java new file mode 100755 index 0000000000000..52aab7b73bca4 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.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.classfile.impl; + +import jdk.classfile.ClassfileVersion; + +/** + * ClassfileVersionImpl + */ +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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java new file mode 100755 index 0000000000000..b319f10995443 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.*; +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.attribute.StackMapTableAttribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.instruction.ExceptionCatch; + +import static jdk.classfile.Classfile.ALOAD; +import static jdk.classfile.Classfile.ANEWARRAY; +import static jdk.classfile.Classfile.ASTORE; +import static jdk.classfile.Classfile.BIPUSH; +import static jdk.classfile.Classfile.CHECKCAST; +import static jdk.classfile.Classfile.DLOAD; +import static jdk.classfile.Classfile.DSTORE; +import static jdk.classfile.Classfile.FLOAD; +import static jdk.classfile.Classfile.FSTORE; +import static jdk.classfile.Classfile.GETFIELD; +import static jdk.classfile.Classfile.GETSTATIC; +import static jdk.classfile.Classfile.GOTO; +import static jdk.classfile.Classfile.GOTO_W; +import static jdk.classfile.Classfile.IFEQ; +import static jdk.classfile.Classfile.IFGE; +import static jdk.classfile.Classfile.IFGT; +import static jdk.classfile.Classfile.IFLE; +import static jdk.classfile.Classfile.IFLT; +import static jdk.classfile.Classfile.IFNE; +import static jdk.classfile.Classfile.IFNONNULL; +import static jdk.classfile.Classfile.IFNULL; +import static jdk.classfile.Classfile.IF_ACMPEQ; +import static jdk.classfile.Classfile.IF_ACMPNE; +import static jdk.classfile.Classfile.IF_ICMPEQ; +import static jdk.classfile.Classfile.IF_ICMPGE; +import static jdk.classfile.Classfile.IF_ICMPGT; +import static jdk.classfile.Classfile.IF_ICMPLE; +import static jdk.classfile.Classfile.IF_ICMPLT; +import static jdk.classfile.Classfile.IF_ICMPNE; +import static jdk.classfile.Classfile.IINC; +import static jdk.classfile.Classfile.ILOAD; +import static jdk.classfile.Classfile.INSTANCEOF; +import static jdk.classfile.Classfile.INVOKEDYNAMIC; +import static jdk.classfile.Classfile.INVOKEINTERFACE; +import static jdk.classfile.Classfile.INVOKESPECIAL; +import static jdk.classfile.Classfile.INVOKESTATIC; +import static jdk.classfile.Classfile.INVOKEVIRTUAL; +import static jdk.classfile.Classfile.ISTORE; +import static jdk.classfile.Classfile.JSR; +import static jdk.classfile.Classfile.JSR_W; +import static jdk.classfile.Classfile.LDC; +import static jdk.classfile.Classfile.LDC2_W; +import static jdk.classfile.Classfile.LDC_W; +import static jdk.classfile.Classfile.LLOAD; +import static jdk.classfile.Classfile.LOOKUPSWITCH; +import static jdk.classfile.Classfile.LSTORE; +import static jdk.classfile.Classfile.MULTIANEWARRAY; +import static jdk.classfile.Classfile.NEW; +import static jdk.classfile.Classfile.NEWARRAY; +import static jdk.classfile.Classfile.PUTFIELD; +import static jdk.classfile.Classfile.PUTSTATIC; +import static jdk.classfile.Classfile.RET; +import static jdk.classfile.Classfile.SIPUSH; +import static jdk.classfile.Classfile.TABLESWITCH; +import static jdk.classfile.Classfile.WIDE; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; + +/** + * CodeAttr + */ +public final class CodeImpl + extends BoundAttribute.BoundCodeAttribute + implements CodeModel, LabelContext { + 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.getContextInfo(); + } + + private void inflateMetadata() { + if (!inflated) { + if (labels == null) + labels = new LabelImpl[codeLength + 1]; + if (classReader.optionValue(Classfile.Option.Key.PROCESS_LINE_NUMBERS)) + inflateLineNumbers(); + inflateJumpTargets(); + inflateTypeAnnotations(); + inflated = true; + } + } + + // CodeAttribute + + @Override + public List> attributes() { + if (attributes == null) { + @SuppressWarnings("unchecked") + List> l = (List>) BoundAttribute.readAttributes(this, classReader, attributePos, classReader.customAttributes()); + attributes = l; + } + 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); + } + }, + buf.constantPool(), + null).writeTo(buf); + } + } + + // CodeModel + + @Override + public Optional parent() { + return Optional.of(enclosingMethod); + } + + @Override + public Kind attributedElementKind() { + return Kind.CODE_ATTRIBUTE; + } + + @Override + public void forEachElement(Consumer consumer) { + inflateMetadata(); + boolean doLineNumbers = (lineNumbers != null); + generateCatchTargets(consumer); + if (classReader.optionValue(Classfile.Option.Key.PROCESS_DEBUG)) + 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 AbstractInstruction.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 static int adjustForLongDouble(int vt, int v) { + return (vt == 7 || vt == 8) ? v + 2 : v; + } + + 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 += adjustForLongDouble(classReader.readU1(p + 1), 2); + } + else + switch (frameType) { + case 247 -> { + offsetDelta = classReader.readU2(p + 1); + p += adjustForLongDouble(classReader.readU1(p + 3), 4); + } + 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 += adjustForLongDouble(classReader.readU1(p), 1); + } + } + case 255 -> { + offsetDelta = classReader.readU2(p + 1); + p += 3; + int k = classReader.readU2(p); + p += 2; + for (int c = 0; c < k; ++c) { + p += adjustForLongDouble(classReader.readU1(p), 1); + } + k = classReader.readU2(p); + p += 2; + for (int c = 0; c < k; ++c) { + p += adjustForLongDouble(classReader.readU1(p), 1); + } + } + 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 AbstractInstruction.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); + } + } + 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 = InstructionData.singletonInstructions[bc]; + if (instr == null) + throw new UnsupportedOperationException("unknown instruction: " + bc); + yield instr; + } + }; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/ConcreteBootstrapMethodEntry.java b/src/java.base/share/classes/jdk/classfile/impl/ConcreteBootstrapMethodEntry.java new file mode 100755 index 0000000000000..6d23744cac9cd --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ConcreteBootstrapMethodEntry.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.classfile.impl; + +import java.util.ArrayList; +import java.util.List; + +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.BufWriter; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MethodHandleEntry; + +import static jdk.classfile.impl.ConcreteEntry.ConcreteMethodHandleEntry; + +public final class ConcreteBootstrapMethodEntry implements BootstrapMethodEntry { + + final int index; + final int hash; + private final ConstantPool constantPool; + private final ConcreteMethodHandleEntry handle; + private final List arguments; + + ConcreteBootstrapMethodEntry(ConstantPool constantPool, int bsmIndex, int hash, + ConcreteMethodHandleEntry 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) { + if (obj instanceof BootstrapMethodEntry e + && e.bootstrapMethod() == handle + && e.arguments().size() == arguments.size()) { + for (int i = 0; i < arguments.size(); ++i) { + if (e.arguments().get(i) != arguments.get(i)) { + return false; + } + } + return true; + } + else + return false; + } + + static int computeHashCode(ConcreteMethodHandleEntry handle, + List arguments) { + int hash = handle.hashCode(); + for (LoadableConstantEntry a : arguments) { + hash = 31 * hash + a.hashCode(); + } + return ConcreteEntry.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/classfile/impl/ConcreteEntry.java b/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java new file mode 100755 index 0000000000000..bead58d7618b5 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java @@ -0,0 +1,1048 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.lang.constant.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.BufWriter; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.MethodRefEntry; +import jdk.classfile.constantpool.MethodTypeEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.jdktypes.PackageDesc; + +public abstract sealed class ConcreteEntry { + /* + 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); + } + + final ConstantPool constantPool; + public final byte tag; + private final int index; + private final int hash; + + private ConcreteEntry(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; } + + public int hashCode() { + return hash; + } + + public byte tag() { + return tag; + } + + public int poolEntries() { + return (tag == Classfile.TAG_LONG || tag == Classfile.TAG_DOUBLE) ? 2 : 1; + } + + public static final class ConcreteUtf8Entry extends ConcreteEntry 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; + + ConcreteUtf8Entry(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; + } + + ConcreteUtf8Entry(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()); + } + + ConcreteUtf8Entry(ConstantPool cpm, int index, ConcreteUtf8Entry 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) << 0)); + 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 ConcreteUtf8Entry clone(ConstantPoolBuilder cp) { + if (cp.canWriteDirect(constantPool)) + return this; + return (state == State.STRING && rawBytes == null) + ? (ConcreteUtf8Entry) 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); + } + + public boolean equalsUtf8(ConcreteUtf8Entry 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 RefEntry extends ConcreteEntry { + protected final T ref1; + + public RefEntry(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()); + } + + public String toString() { + return tag() + " " + ref1(); + } + } + + static abstract sealed class RefsEntry + extends ConcreteEntry { + protected final T ref1; + protected final U ref2; + + public RefsEntry(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 NamedEntry extends RefEntry { + + public NamedEntry(ConstantPool constantPool, int tag, int index, ConcreteUtf8Entry ref1) { + super(constantPool, tag, index, ref1); + } + + public Utf8Entry name() { + return ref1; + } + + public String asInternalName() { + return ref1.stringValue(); + } + } + + public static final class ConcreteClassEntry extends NamedEntry implements ClassEntry { + + ConcreteClassEntry(ConstantPool cpm, int index, ConcreteUtf8Entry name) { + super(cpm, Classfile.TAG_CLASS, index, name); + } + + @Override + public ConstantDesc constantValue() { + return ClassDesc.ofDescriptor(Util.classToDescriptor(asInternalName())); + } + + @Override + public ClassEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.classEntry(ref1); + } + + @Override + public ClassDesc asSymbol() { + return Util.toClassDesc(asInternalName()); + } + } + + public static final class ConcretePackageEntry extends NamedEntry implements PackageEntry { + + ConcretePackageEntry(ConstantPool cpm, int index, ConcreteUtf8Entry 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()); + } + } + + public static final class ConcreteModuleEntry extends NamedEntry implements ModuleEntry { + + ConcreteModuleEntry(ConstantPool cpm, int index, ConcreteUtf8Entry 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()); + } + } + + public static final class ConcreteNameAndTypeEntry extends RefsEntry + implements NameAndTypeEntry { + + ConcreteNameAndTypeEntry(ConstantPool cpm, int index, ConcreteUtf8Entry name, ConcreteUtf8Entry 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.natEntry(ref1, ref2); + } + } + + public static sealed abstract class MemberRefEntry + extends RefsEntry + implements jdk.classfile.constantpool.MemberRefEntry { + + MemberRefEntry(ConstantPool cpm, int tag, int index, ConcreteClassEntry owner, + ConcreteNameAndTypeEntry nameAndType) { + super(cpm, tag, index, owner, nameAndType); + } + + @Override + public ConcreteClassEntry owner() { + return ref1; + } + + @Override + public ConcreteNameAndTypeEntry nameAndType() { + return ref2; + } + + @Override + public String toString() { + return tag() + " " + owner().asInternalName() + "." + nameAndType().name().stringValue() + + "-" + nameAndType().type().stringValue(); + } + } + + public static final class ConcreteFieldRefEntry extends MemberRefEntry implements FieldRefEntry { + + ConcreteFieldRefEntry(ConstantPool cpm, int index, + ConcreteClassEntry owner, ConcreteNameAndTypeEntry nameAndType) { + super(cpm, Classfile.TAG_FIELDREF, index, owner, nameAndType); + } + + @Override + public FieldRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.fieldRefEntry(ref1, ref2); + } + + @Override + public boolean isMethod() { + return false; + } + + @Override + public boolean isInterface() { + return false; + } + } + + public static final class ConcreteMethodRefEntry extends MemberRefEntry implements MethodRefEntry { + + ConcreteMethodRefEntry(ConstantPool cpm, int index, + ConcreteClassEntry owner, ConcreteNameAndTypeEntry nameAndType) { + super(cpm, Classfile.TAG_METHODREF, index, owner, nameAndType); + } + + @Override + public MethodRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodRefEntry(ref1, ref2); + } + + @Override + public boolean isInterface() { + return false; + } + + @Override + public boolean isMethod() { + return true; + } + } + + public static final class ConcreteInterfaceMethodRefEntry extends MemberRefEntry implements InterfaceMethodRefEntry { + + ConcreteInterfaceMethodRefEntry(ConstantPool cpm, int index, ConcreteClassEntry owner, + ConcreteNameAndTypeEntry nameAndType) { + super(cpm, Classfile.TAG_INTERFACEMETHODREF, index, owner, nameAndType); + } + + @Override + public InterfaceMethodRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.interfaceMethodRefEntry(ref1, ref2); + } + + @Override + public boolean isInterface() { + return true; + } + + @Override + public boolean isMethod() { + return true; + } + } + + public static sealed abstract class AbstractDynamicConstantPoolEntry extends ConcreteEntry { + + private final int bsmIndex; + private ConcreteBootstrapMethodEntry bootstrapMethod; + private final ConcreteNameAndTypeEntry nameAndType; + + AbstractDynamicConstantPoolEntry(ConstantPool cpm, int tag, int index, int hash, ConcreteBootstrapMethodEntry bootstrapMethod, + ConcreteNameAndTypeEntry 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, + ConcreteNameAndTypeEntry nameAndType) { + super(cpm, tag, index, hash); + this.bsmIndex = bsmIndex; + this.bootstrapMethod = null; + this.nameAndType = nameAndType; + } + + /** + * @return the bootstrapMethod + */ + public ConcreteBootstrapMethodEntry bootstrap() { + if (bootstrapMethod == null) { + bootstrapMethod = (ConcreteBootstrapMethodEntry) constantPool.bootstrapMethodEntry(bsmIndex); + } + return bootstrapMethod; + } + + /** + * @return the nameAndType + */ + public ConcreteNameAndTypeEntry 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(); + } + } + + public static final class ConcreteInvokeDynamicEntry + extends AbstractDynamicConstantPoolEntry + implements InvokeDynamicEntry { + + ConcreteInvokeDynamicEntry(ConstantPool cpm, int index, int hash, ConcreteBootstrapMethodEntry bootstrapMethod, + ConcreteNameAndTypeEntry nameAndType) { + super(cpm, Classfile.TAG_INVOKEDYNAMIC, index, hash, bootstrapMethod, nameAndType); + } + + ConcreteInvokeDynamicEntry(ConstantPool cpm, int index, int bsmIndex, + ConcreteNameAndTypeEntry 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 ConcreteConstantDynamicEntry extends AbstractDynamicConstantPoolEntry + implements ConstantDynamicEntry { + + ConcreteConstantDynamicEntry(ConstantPool cpm, int index, int hash, ConcreteBootstrapMethodEntry bootstrapMethod, + ConcreteNameAndTypeEntry nameAndType) { + super(cpm, Classfile.TAG_CONSTANTDYNAMIC, index, hash, bootstrapMethod, nameAndType); + } + + ConcreteConstantDynamicEntry(ConstantPool cpm, int index, int bsmIndex, + ConcreteNameAndTypeEntry 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()); + } + + @Override + public ConstantDesc constantValue() { + List args = bootstrap().arguments(); + int argsSize = args.size(); + ConstantDesc[] staticArgs = new ConstantDesc[argsSize]; + for (int i = 0; i < argsSize; i++) + staticArgs[i] = args.get(i).constantValue(); + + return DynamicConstantDesc.ofCanonical(bootstrap().bootstrapMethod().asSymbol(), + nameAndType().name().stringValue(), ClassDesc.of(Util.toClassString(Util.descriptorToClass(nameAndType().type().stringValue()))), staticArgs); + } + } + + public static final class ConcreteMethodHandleEntry extends ConcreteEntry + implements MethodHandleEntry { + + private final int refKind; + private final ConcreteEntry.MemberRefEntry reference; + + ConcreteMethodHandleEntry(ConstantPool cpm, int index, int hash, int refKind, ConcreteEntry.MemberRefEntry + reference) { + super(cpm, Classfile.TAG_METHODHANDLE, index, hash); + this.refKind = refKind; + this.reference = reference; + } + + ConcreteMethodHandleEntry(ConstantPool cpm, int index, int refKind, ConcreteEntry.MemberRefEntry + 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 ConstantDesc constantValue() { + return asSymbol(); + } + + @Override + public ConcreteEntry.MemberRefEntry reference() { + return reference; + } + + @Override + public DirectMethodHandleDesc asSymbol() { + return MethodHandleDesc.of( + DirectMethodHandleDesc.Kind.valueOf(kind(), reference().isInterface()), + ClassDesc.of(Util.toClassString(((jdk.classfile.constantpool.MemberRefEntry) reference()).owner().asInternalName())), + ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().name().stringValue(), + ((jdk.classfile.constantpool.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() + ":" + ((jdk.classfile.constantpool.MemberRefEntry) reference()).owner().asInternalName() + "." + ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().name().stringValue() + + "-" + ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().type().stringValue(); + } + } + + public static final class ConcreteMethodTypeEntry + extends RefEntry + implements MethodTypeEntry { + + ConcreteMethodTypeEntry(ConstantPool cpm, int index, ConcreteUtf8Entry descriptor) { + super(cpm, Classfile.TAG_METHODTYPE, index, descriptor); + } + + public Utf8Entry descriptor() { + return ref1; + } + + @Override + public ConstantDesc constantValue() { + return MethodTypeDesc.ofDescriptor(descriptor().stringValue()); + } + + @Override + public MethodTypeEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodTypeEntry(ref1); + } + } + + public static final class ConcreteStringEntry + extends RefEntry + implements StringEntry { + + ConcreteStringEntry(ConstantPool cpm, int index, ConcreteUtf8Entry utf8) { + super(cpm, Classfile.TAG_STRING, index, utf8); + } + + @Override + public ConcreteUtf8Entry 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() + "\""; + } + } + + static abstract sealed class PrimitiveEntry + extends ConcreteEntry { + 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 ConcreteIntegerEntry extends PrimitiveEntry + implements IntegerEntry { + + ConcreteIntegerEntry(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(); + } + } + + public static final class ConcreteFloatEntry extends PrimitiveEntry + implements FloatEntry { + + ConcreteFloatEntry(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(); + } + } + + public static final class ConcreteLongEntry extends PrimitiveEntry implements LongEntry { + + ConcreteLongEntry(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(); + } + } + + public static final class ConcreteDoubleEntry extends PrimitiveEntry implements DoubleEntry { + + ConcreteDoubleEntry(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(); + } + } + + static class CpException extends RuntimeException { + static final long serialVersionUID = 32L; + + CpException(String s) { + super(s); + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/DirectClassBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectClassBuilder.java new file mode 100755 index 0000000000000..34e2646ec64ba --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/DirectClassBuilder.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.BufWriter; +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.FieldBuilder; +import jdk.classfile.FieldModel; +import jdk.classfile.FieldTransform; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodModel; +import jdk.classfile.MethodTransform; +import jdk.classfile.WritableElement; +import jdk.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(ConstantPoolBuilder constantPool, + ClassEntry thisClass) { + super(constantPool); + this.thisClassEntry = constantPool.maybeClone(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 = constantPool.maybeClone(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(constantPool.maybeClone(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/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java new file mode 100755 index 0000000000000..2ebb3d696f638 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java @@ -0,0 +1,703 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.Attribute; +import jdk.classfile.Attributes; +import jdk.classfile.BufWriter; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.SwitchCase; +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.attribute.LineNumberTableAttribute; +import jdk.classfile.attribute.StackMapTableAttribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.instruction.CharacterRange; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; + +import static jdk.classfile.Opcode.GOTO; +import static jdk.classfile.Opcode.GOTO_W; +import static jdk.classfile.Opcode.IINC; +import static jdk.classfile.Opcode.IINC_W; +import static jdk.classfile.Opcode.LDC2_W; +import static jdk.classfile.Opcode.LDC_W; + +/** + * CodeWriter + */ +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, + ConstantPoolBuilder constantPool, + CodeModel original) { + DirectCodeBuilder cb; + try { + handler.accept(cb = new DirectCodeBuilder(methodInfo, constantPool, original, false)); + cb.buildContent(); + } catch (LabelOverflowException loe) { + if (constantPool.optionValue(Classfile.Option.Key.FIX_SHORT_JUMPS)) { + handler.accept(cb = new DirectCodeBuilder(methodInfo, constantPool, original, true)); + cb.buildContent(); + } + else + throw loe; + } + return cb.content; + } + + private DirectCodeBuilder(MethodInfo methodInfo, + ConstantPoolBuilder constantPool, + CodeModel original, + boolean transformFwdJumps) { + super(constantPool); + setOriginal(original); + this.methodInfo = methodInfo; + this.transformFwdJumps = transformFwdJumps; + this.transformBackJumps = constantPool.optionValue(Classfile.Option.Key.FIX_SHORT_JUMPS); + 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(); + } + + private Attribute content = null; + + private void writeExceptionHandlers(BufWriter buf) { + buf.writeU2(handlers.size()); + for (AbstractInstruction.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) + throw new IllegalStateException("Unbound label in exception handler"); + buf.writeU2(startPc); + buf.writeU2(endPc); + buf.writeU2(handlerPc); + buf.writeIndexOrZero(h.catchTypeEntry()); + } + } + + private void buildContent() { + if (content != null) return; + setLabelTarget(endLabel); + + // Backfill branches for which Label didn't have position yet + processDeferredLabels(); + + if (constantPool.optionValue(Classfile.Option.Key.PROCESS_DEBUG)) { + if (!characterRanges.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.CHARACTER_RANGE_TABLE) { + + @Override + public void writeBody(BufWriter b) { + // @@@ Filter out CRs whose boundary labels are not defined? + b.writeU2(characterRanges.size()); + for (CharacterRange cr : characterRanges) { + b.writeU2(labelToBci(cr.startScope())); + b.writeU2(labelToBci(cr.endScope()) - 1); + b.writeInt(cr.characterRangeStart()); + b.writeInt(cr.characterRangeEnd()); + b.writeU2(cr.flags()); + } + // @@@ If we're filtering, then also have to patch count + } + }; + attributes.withAttribute(a); + } + + if (!localVariables.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.LOCAL_VARIABLE_TABLE) { + @Override + public void writeBody(BufWriter b) { + // @@@ Filter out LVs whose boundary labels are not defined? + b.writeU2(localVariables.size()); + for (LocalVariable l : localVariables) { + l.writeTo(b, DirectCodeBuilder.this); + } + // @@@ If we're filtering, then also have to patch count + } + }; + + attributes.withAttribute(a); + } + + if (!localVariableTypes.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.LOCAL_VARIABLE_TYPE_TABLE) { + @Override + public void writeBody(BufWriter b) { + // @@@ Filter out LVs whose boundary labels are not defined? + b.writeU2(localVariableTypes.size()); + for (LocalVariableType l : localVariableTypes) { + l.writeTo(b, DirectCodeBuilder.this); + } + // @@@ If we're filtering, then also have to patch count + } + }; + + 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; + + int codeLength = curPc(); + int maxStack, maxLocals; + Attribute stackMapAttr; + boolean canReuseStackmaps = codeAndExceptionsMatch(codeLength); + + if (!constantPool.optionValue(Classfile.Option.Key.GENERATE_STACK_MAPS)) { + 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.setLabelResolver(DirectCodeBuilder.this); + buf.writeU2(maxStack); + buf.writeU2(maxLocals); + buf.writeInt(codeLength); + buf.writeBytes(bytecodesBufWriter); + writeExceptionHandlers(b); + attributes.writeTo(b); + buf.setLabelResolver(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 = constantPool.maybeClone(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.getContextInfo(); + } + else if (context == mruParent) { + return mruParentTable[lab.getContextInfo()] - 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; + return mruParentTable[lab.getContextInfo()] - 1; + } + else if (context instanceof BufferedCodeBuilder) { + // Hijack the label + return lab.getContextInfo(); + } + 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.getContextInfo() != -1) + throw new IllegalStateException("Setting label target for already-set label"); + lab.setContextInfo(bci); + } + else if (context == mruParent) { + mruParentTable[lab.getContextInfo()] = 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.getContextInfo()] = bci + 1; + } + else if (context instanceof BufferedCodeBuilder) { + // Hijack the label + lab.setContextInfo(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) { + AbstractInstruction.ExceptionCatchImpl el = (AbstractInstruction.ExceptionCatchImpl) element; + ClassEntry type = el.catchTypeEntry(); + if (type != null && !constantPool.canWriteDirect(type.constantPool())) + el = new AbstractInstruction.ExceptionCatchImpl(element.handler(), element.tryStart(), element.tryEnd(), constantPool.maybeClone(type)); + handlers.add(el); + } + + public void addLocalVariable(LocalVariable element) { + localVariables.add(element); + } + + public void addLocalVariableType(LocalVariableType element) { + localVariableTypes.add(element); + } + + //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/classfile/impl/DirectFieldBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectFieldBuilder.java new file mode 100755 index 0000000000000..818889f03064b --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.impl; + +import java.util.function.Consumer; + +import jdk.classfile.BufWriter; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.FieldBuilder; +import jdk.classfile.FieldElement; +import jdk.classfile.FieldModel; +import jdk.classfile.WritableElement; +import jdk.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(ConstantPoolBuilder 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; + } + + 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/classfile/impl/DirectMethodBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectMethodBuilder.java new file mode 100755 index 0000000000000..8eb7bb4b26c70 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/DirectMethodBuilder.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 jdk.classfile.impl; + +import java.util.function.Consumer; + +import jdk.classfile.BufWriter; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.WritableElement; +import jdk.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(ConstantPoolBuilder 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/classfile/impl/EntryMap.java b/src/java.base/share/classes/jdk/classfile/impl/EntryMap.java new file mode 100755 index 0000000000000..de88a04f028b9 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/EntryMap.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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/implementing-world-fastest-java-int-to-int-hash-map/). + * + * 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 ) { + if ( x == 0 ) return 1; + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ( x | x >> 32 ) + 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; + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java b/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java new file mode 100755 index 0000000000000..edaaf06903b6a --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import jdk.classfile.*; +import jdk.classfile.constantpool.Utf8Entry; + +/** + * FieldImpl -- merged implementation of FieldLow and FieldModel + */ +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) { + @SuppressWarnings("unchecked") + var res = (List>) BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + attributes = res; + } + 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 Kind attributedElementKind() { + return Kind.FIELD; + } + + @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); + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/InstructionData.java b/src/java.base/share/classes/jdk/classfile/impl/InstructionData.java new file mode 100755 index 0000000000000..9b3c0cded8c64 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/InstructionData.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.classfile.impl; + +import java.util.List; + +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.instruction.ArrayLoadInstruction; +import jdk.classfile.instruction.ArrayStoreInstruction; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.ConvertInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.MonitorInstruction; +import jdk.classfile.instruction.NopInstruction; +import jdk.classfile.instruction.OperatorInstruction; +import jdk.classfile.instruction.ReturnInstruction; +import jdk.classfile.instruction.StackInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.ThrowInstruction; + +/** + * InstructionData + */ +public class InstructionData { + static final Instruction[] singletonInstructions = new Instruction[256]; + + static { + for (Opcode o : List.of(Opcode.NOP)) + singletonInstructions[o.bytecode()] = NopInstruction.of(); + for (Opcode o : List.of(Opcode.ACONST_NULL, + Opcode.ICONST_M1, + Opcode.ICONST_0, Opcode.ICONST_1, Opcode.ICONST_2, Opcode.ICONST_3, Opcode.ICONST_4, Opcode.ICONST_5, + Opcode.LCONST_0, Opcode.LCONST_1, + Opcode.FCONST_0, Opcode.FCONST_1, Opcode.FCONST_2, + Opcode.DCONST_0, Opcode.DCONST_1)) + singletonInstructions[o.bytecode()] = ConstantInstruction.ofIntrinsic(o); + for (Opcode o : List.of(Opcode.ILOAD_0, Opcode.ILOAD_1, Opcode.ILOAD_2, Opcode.ILOAD_3, + Opcode.LLOAD_0, Opcode.LLOAD_1, Opcode.LLOAD_2, Opcode.LLOAD_3, + Opcode.FLOAD_0, Opcode.FLOAD_1, Opcode.FLOAD_2, Opcode.FLOAD_3, + Opcode.DLOAD_0, Opcode.DLOAD_1, Opcode.DLOAD_2, Opcode.DLOAD_3, + Opcode.ALOAD_0, Opcode.ALOAD_1, Opcode.ALOAD_2, Opcode.ALOAD_3)) + singletonInstructions[o.bytecode()] = LoadInstruction.of(o, o.slot()); + for (Opcode o : List.of(Opcode.ISTORE_0, Opcode.ISTORE_1, Opcode.ISTORE_2, Opcode.ISTORE_3, + Opcode.LSTORE_0, Opcode.LSTORE_1, Opcode.LSTORE_2, Opcode.LSTORE_3, + Opcode.FSTORE_0, Opcode.FSTORE_1, Opcode.FSTORE_2, Opcode.FSTORE_3, + Opcode.DSTORE_0, Opcode.DSTORE_1, Opcode.DSTORE_2, Opcode.DSTORE_3, + Opcode.ASTORE_0, Opcode.ASTORE_1, Opcode.ASTORE_2, Opcode.ASTORE_3)) + singletonInstructions[o.bytecode()] = StoreInstruction.of(o, o.slot()); + for (Opcode o : List.of(Opcode.IALOAD, Opcode.LALOAD, Opcode.FALOAD, Opcode.DALOAD, Opcode.AALOAD, Opcode.BALOAD, Opcode.CALOAD, Opcode.SALOAD)) + singletonInstructions[o.bytecode()] = ArrayLoadInstruction.of(o); + for (Opcode o : List.of(Opcode.IASTORE, Opcode.LASTORE, Opcode.FASTORE, Opcode.DASTORE, Opcode.AASTORE, Opcode.BASTORE, Opcode.CASTORE, Opcode.SASTORE)) + singletonInstructions[o.bytecode()] = ArrayStoreInstruction.of(o); + for (Opcode o : List.of(Opcode.POP, Opcode.POP2, Opcode.DUP, Opcode.DUP_X1, Opcode.DUP_X2, Opcode.DUP2, Opcode.DUP2_X1, Opcode.DUP2_X2, Opcode.SWAP)) + singletonInstructions[o.bytecode()] = StackInstruction.of(o); + for (Opcode o : List.of(Opcode.IADD, Opcode.LADD, Opcode.FADD, Opcode.DADD, Opcode.ISUB, + Opcode.LSUB, Opcode.FSUB, Opcode.DSUB, + Opcode.IMUL, Opcode.LMUL, Opcode.FMUL, Opcode.DMUL, + Opcode.IDIV, Opcode.LDIV, Opcode.FDIV, Opcode.DDIV, + Opcode.IREM, Opcode.LREM, Opcode.FREM, Opcode.DREM, + Opcode.INEG, Opcode.LNEG, Opcode.FNEG, Opcode.DNEG, + Opcode.ISHL, Opcode.LSHL, Opcode.ISHR, Opcode.LSHR, Opcode.IUSHR, Opcode.LUSHR, + Opcode.IAND, Opcode.LAND, Opcode.IOR, Opcode.LOR, Opcode.IXOR, Opcode.LXOR, + Opcode.LCMP, Opcode.FCMPL, Opcode.FCMPG, Opcode.DCMPL, Opcode.DCMPG, + Opcode.ARRAYLENGTH)) + singletonInstructions[o.bytecode()] = OperatorInstruction.of(o); + + for (Opcode o : List.of(Opcode.I2L, Opcode.I2F, Opcode.I2D, + Opcode.L2I, Opcode.L2F, Opcode.L2D, + Opcode.F2I, Opcode.F2L, Opcode.F2D, + Opcode.D2I, Opcode.D2L, Opcode.D2F, + Opcode.I2B, Opcode.I2C, Opcode.I2S)) + singletonInstructions[o.bytecode()] = ConvertInstruction.of(o); + for (Opcode o : List.of(Opcode.IRETURN, Opcode.LRETURN, Opcode.FRETURN, Opcode.DRETURN, Opcode.ARETURN, Opcode.RETURN)) + singletonInstructions[o.bytecode()] = ReturnInstruction.of(o); + for (Opcode o : List.of(Opcode.ATHROW)) + singletonInstructions[o.bytecode()] = ThrowInstruction.of(); + for (Opcode o : List.of(Opcode.MONITORENTER, Opcode.MONITOREXIT)) + singletonInstructions[o.bytecode()] = MonitorInstruction.of(o); + } + + private InstructionData() { + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.java b/src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.java new file mode 100755 index 0000000000000..16ad3d1389ebd --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.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.classfile.impl; + +import java.util.List; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Interfaces; + +/** + * InterfacesImpl + */ +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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/LabelContext.java b/src/java.base/share/classes/jdk/classfile/impl/LabelContext.java new file mode 100755 index 0000000000000..c7b8fb1d9905c --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/LabelContext.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.classfile.impl; + +import jdk.classfile.Label; + +/** + * LabelContext + */ +public sealed interface LabelContext extends LabelResolver + permits BufferedCodeBuilder, CodeImpl, DirectCodeBuilder { + Label newLabel(); + Label getLabel(int bci); + void setLabelTarget(Label label, int bci); +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/LabelImpl.java b/src/java.base/share/classes/jdk/classfile/impl/LabelImpl.java new file mode 100755 index 0000000000000..c6e6514ee4304 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/LabelImpl.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.classfile.impl; + +import java.util.Objects; + +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.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 contextInfo; + + public LabelImpl(LabelContext labelContext, int contextInfo) { + this.labelContext = Objects.requireNonNull(labelContext); + this.contextInfo = contextInfo; + } + + public LabelContext labelContext() { + return labelContext; + } + + public int getContextInfo() { + return contextInfo; + } + + public void setContextInfo(int contextInfo) { + this.contextInfo = contextInfo; + } + + @Override + public Kind codeKind() { + return Kind.LABEL_TARGET; + } + + @Override + public Opcode opcode() { + return Opcode.LABEL_TARGET; + } + + @Override + public int sizeInBytes() { + return 0; + } + + @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, contextInfo=%d]", labelContext, contextInfo); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/LabelResolver.java b/src/java.base/share/classes/jdk/classfile/impl/LabelResolver.java new file mode 100755 index 0000000000000..27589c328ec5d --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/LabelResolver.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.classfile.impl; + +import jdk.classfile.CodeModel; +import jdk.classfile.Label; + +/** + * LabelResolver + */ +public sealed interface LabelResolver permits CodeModel, LabelContext { + int labelToBci(Label label); +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java b/src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java new file mode 100755 index 0000000000000..aefd3e6039151 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import jdk.classfile.Opcode; +import jdk.classfile.instruction.LineNumber; + +/** + * LineNumberImpl + */ +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) { + @SuppressWarnings("unchecked") + var res = (List>) BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + attributes = res; + } + 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 Kind attributedElementKind() { + return Kind.METHOD; + } + + @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); + } + }); + } + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/impl/MethodInfo.java b/src/java.base/share/classes/jdk/classfile/impl/MethodInfo.java new file mode 100755 index 0000000000000..29920253fe9fd --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/MethodInfo.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.classfile.impl; + +import jdk.classfile.constantpool.Utf8Entry; + +import static jdk.classfile.Classfile.ACC_STATIC; + +/** + * MethodInfo + */ +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/classfile/impl/ModuleAttributeBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ModuleAttributeBuilderImpl.java new file mode 100644 index 0000000000000..c5ab23d491a74 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.impl; + +import jdk.classfile.attribute.*; +import jdk.classfile.attribute.ModuleAttribute.ModuleAttributeBuilder; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.jdktypes.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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(seq)))); + return provides(ModuleProvideInfo.of(TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(service))), impls)); + } + + @Override + public ModuleAttributeBuilder provides(ModuleProvideInfo provides) { + Objects.requireNonNull(provides); + this.provides.add(provides); + return this; + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/impl/ModuleDescImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ModuleDescImpl.java new file mode 100644 index 0000000000000..c2e3a93fd1e6d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.impl; + +import jdk.classfile.jdktypes.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/classfile/impl/NonterminalCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java new file mode 100755 index 0000000000000..790cee50c8684 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.Optional; + +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeModel; +import jdk.classfile.Label; +import jdk.classfile.constantpool.ConstantPoolBuilder; + +/** + * NonterminalCodeBuilder + */ +public abstract sealed class NonterminalCodeBuilder implements CodeBuilder + permits ChainedCodeBuilder, BlockCodeBuilder { + protected final TerminalCodeBuilder terminal; + + public NonterminalCodeBuilder(CodeBuilder downstream) { + this.terminal = switch (downstream) { + case ChainedCodeBuilder cb -> cb.terminal; + case BlockCodeBuilder 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(); + } + + @Override + public int labelToBci(Label label) { + return terminal.labelToBci(label); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/Options.java b/src/java.base/share/classes/jdk/classfile/impl/Options.java new file mode 100755 index 0000000000000..a492d3b017de0 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/Options.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.classfile.impl; + +import java.util.Collection; +import java.util.function.Function; + +import jdk.classfile.AttributeMapper; +import jdk.classfile.ClassHierarchyResolver; +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.Utf8Entry; + +import static jdk.classfile.ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER; + +/** + * Options + */ +public class Options { + + public record OptionValue(Classfile.Option.Key key, V 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 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 (Classfile.Option v : options) + switch (((Options.OptionValue) v).key()) { + case GENERATE_STACK_MAPS -> generateStackmaps = (Boolean) v.value(); + case PROCESS_DEBUG -> processDebug = (Boolean) v.value(); + case PROCESS_LINE_NUMBERS -> processLineNumbers = (Boolean) v.value(); + case PROCESS_UNKNOWN_ATTRIBUTES -> processUnknownAttributes = (Boolean) v.value(); + case CP_SHARING -> cpSharing = (Boolean) v.value(); + case FIX_SHORT_JUMPS -> fixJumps = (Boolean) v.value(); + case PATCH_DEAD_CODE -> patchCode = (Boolean) v.value(); + case HIERARCHY_RESOLVER -> classHierarchyResolver = (ClassHierarchyResolver) v.value(); + case ATTRIBUTE_MAPPER -> attributeMapper = (Function>) v.value(); + } + } + + @SuppressWarnings("unchecked") + public T value(Classfile.Option.Key key) { + return switch (key) { + case PROCESS_DEBUG -> (T) processDebug; + case PROCESS_LINE_NUMBERS -> (T) processLineNumbers; + case PROCESS_UNKNOWN_ATTRIBUTES -> (T) processUnknownAttributes; + case CP_SHARING -> (T) cpSharing; + case FIX_SHORT_JUMPS -> (T) fixJumps; + case PATCH_DEAD_CODE -> (T) patchCode; + case ATTRIBUTE_MAPPER -> (T) attributeMapper; + case GENERATE_STACK_MAPS -> (T) generateStackmaps; + case HIERARCHY_RESOLVER -> (T) classHierarchyResolver; + }; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/PackageDescImpl.java b/src/java.base/share/classes/jdk/classfile/impl/PackageDescImpl.java new file mode 100644 index 0000000000000..155cc6179df2c --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.impl; + +import jdk.classfile.jdktypes.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/classfile/impl/SignaturesImpl.java b/src/java.base/share/classes/jdk/classfile/impl/SignaturesImpl.java new file mode 100644 index 0000000000000..55cbb034aa0cd --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/SignaturesImpl.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. + */ +package jdk.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import jdk.classfile.ClassSignature; +import jdk.classfile.MethodSignature; +import jdk.classfile.Signature; +import jdk.classfile.Signature.*; + +public final class SignaturesImpl { + + public SignaturesImpl() { + } + + private String sig; + private int sigp; + + // @@@ Move to ClassSignatureImpl to avoid list copying + public ClassSignature parseClassSignature(String sig) { + this.sig = sig; + sigp = 0; + List typeParamTypes = parseParamTypes(); + RefTypeSig superclass = referenceTypeSig(); + List superinterfaces = null; + while (sigp < sig.length()) { + if (superinterfaces == null) + superinterfaces = new ArrayList<>(); + superinterfaces.add(referenceTypeSig()); + } + return new ClassSignatureImpl(typeParamTypes, superclass, null2Empty(superinterfaces)); + } + + // @@@ Move to ClassSignatureImpl to avoid list copying + public MethodSignature parseMethodSignature(String sig) { + this.sig = sig; + sigp = 0; + List typeParamTypes = parseParamTypes(); + assert sig.charAt(sigp) == '('; + sigp++; + List paramTypes = new ArrayList<>(); + while (sig.charAt(sigp) != ')') + paramTypes.add(typeSig()); + sigp++; + Signature returnType = typeSig(); + List 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, paramTypes, returnType, null2Empty(throwsTypes)); + } + + public Signature parseSignature(String sig) { + this.sig = sig; + sigp = 0; + return typeSig(); + } + + private List parseParamTypes() { + List 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; + List 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(); + List argTypes = null; + Signature.ClassTypeSig t = null; + char sigch ; + do { + switch (sigch = sig.charAt(sigp++)) { + case '<' -> { + argTypes = new ArrayList<>(); + while (sig.charAt(sigp) != '>') + argTypes.add(typeSig()); + 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()); + case '*': return TypeArg.unbounded(); + case '+': return TypeArg.extendsOf(referenceTypeSig()); + case '-': return TypeArg.superOf(referenceTypeSig()); + } + throw new IllegalStateException("not a valid type signature: " + sig); + } + + 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 { + + public ClassTypeSigImpl(Optional outerType, String className, List typeArgs) { + this.outerType = outerType; + this.className = className; + this.typeArgs = List.copyOf(typeArgs); + } + + @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 != null && !typeArgs.isEmpty()) { + var sb = new StringBuilder(); + sb.append('<'); + for (var ta : typeArgs) + sb.append(ta.signatureString()); + suffix = sb.append(">;").toString(); + } + return prefix + className + suffix; + } + } + + public static record TypeArgImpl(WildcardIndicator wildcardIndicator, Optional boundType) implements Signature.TypeArg { + + @Override + public String signatureString() { + return switch (wildcardIndicator) { + case EXTENDS, SUPER -> wildcardIndicator.indicator + boundType.get().signatureString(); + case UNBOUNDED -> "*"; + }; + } + } + + public static record TypeParamImpl(String identifier, Optional classBound, List interfaceBounds) + implements TypeParam { + public TypeParamImpl(String identifier, Optional classBound, List interfaceBounds) { + this.identifier = identifier; + this.classBound = classBound; + this.interfaceBounds = List.copyOf(interfaceBounds); + } + } + + 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 { + + public ClassSignatureImpl(List typeParameters, RefTypeSig superclassSignature, + List superinterfaceSignatures) { + this.typeParameters = List.copyOf(typeParameters); + this.superclassSignature = superclassSignature; + this.superinterfaceSignatures = List.copyOf(superinterfaceSignatures); + } + + @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 arguments, + Signature result, + List throwableSignatures) implements MethodSignature { + + public MethodSignatureImpl(List typeParameters, List arguments, Signature result, + List throwableSignatures) { + this.typeParameters = List.copyOf(typeParameters); + this.arguments = List.copyOf(arguments); + this.result = result; + this.throwableSignatures = List.copyOf(throwableSignatures); + } + + @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 != null && !throwableSignatures.isEmpty()) + for (var t : throwableSignatures) + sb.append('^').append(t.signatureString()); + return sb.toString(); + } + } + + public static List null2Empty(List l) { + return l == null ? List.of() : l; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java new file mode 100755 index 0000000000000..ec156c90d5e79 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java @@ -0,0 +1,613 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.Attribute; +import jdk.classfile.Attributes; +import jdk.classfile.ClassReader; +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.BufWriter; +import jdk.classfile.attribute.BootstrapMethodsAttribute; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.MethodRefEntry; +import jdk.classfile.constantpool.MethodTypeEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; + +import static jdk.classfile.Classfile.TAG_CLASS; +import static jdk.classfile.Classfile.TAG_CONSTANTDYNAMIC; +import static jdk.classfile.Classfile.TAG_DOUBLE; +import static jdk.classfile.Classfile.TAG_FIELDREF; +import static jdk.classfile.Classfile.TAG_FLOAT; +import static jdk.classfile.Classfile.TAG_INTEGER; +import static jdk.classfile.Classfile.TAG_INTERFACEMETHODREF; +import static jdk.classfile.Classfile.TAG_INVOKEDYNAMIC; +import static jdk.classfile.Classfile.TAG_LONG; +import static jdk.classfile.Classfile.TAG_METHODHANDLE; +import static jdk.classfile.Classfile.TAG_METHODREF; +import static jdk.classfile.Classfile.TAG_METHODTYPE; +import static jdk.classfile.Classfile.TAG_MODULE; +import static jdk.classfile.Classfile.TAG_NAMEANDTYPE; +import static jdk.classfile.Classfile.TAG_PACKAGE; +import static jdk.classfile.Classfile.TAG_STRING; + +/** + * ConstantPool. + */ +public final class SplitConstantPool implements ConstantPoolBuilder { + + private final ClassReaderImpl parent; + private final int parentSize, parentBsmSize; + private final Options options; + + private int size, bsmSize; + private PoolEntry[] myEntries; + private ConcreteBootstrapMethodEntry[] 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 ConcreteBootstrapMethodEntry[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 ConcreteBootstrapMethodEntry[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 ConcreteBootstrapMethodEntry bootstrapMethodEntry(int index) { + return (index < parentBsmSize) + ? parent.bootstrapMethodEntry(index) + : myBsmEntries[index - parentBsmSize]; + } + + @SuppressWarnings("unchecked") + @Override + public T maybeClone(T entry) { + return canWriteDirect(entry.constantPool()) + ? entry + : (T) entry.clone(this); + } + + @Override + public T optionValue(Classfile.Option.Key option) { + return options.value(option); + } + + @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); + return true; + } + 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.poolEntries(); + } + } + + 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 ConcreteBootstrapMethodEntry 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.poolEntries(); + map().put(hash, cpi.index()); + return cpi; + } + + private ConcreteBootstrapMethodEntry internalAdd(ConcreteBootstrapMethodEntry bsm, int hash) { + int newIndex = bsmSize-parentBsmSize; + if (newIndex + 2 > myBsmEntries.length) { + myBsmEntries = Arrays.copyOf(myBsmEntries, 2 * newIndex, ConcreteBootstrapMethodEntry[].class); + } + myBsmEntries[newIndex] = bsm; + bsmSize += 1; + bsmMap().put(hash, bsm.index); + return bsm; + } + + private PoolEntry findPrimitiveEntry(int tag, T val) { + int hash = ConcreteEntry.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 ConcreteEntry.PrimitiveEntry ce + && ce.value().equals(val)) + return e; + } + if (!doneFullScan) { + fullScan(); + return findPrimitiveEntry(tag, val); + } + return null; + } + + private ConcreteEntry findEntry(int tag, T ref1) { + // invariant: canWriteDirect(ref1.constantPool()) + int hash = ConcreteEntry.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 ConcreteEntry.RefEntry re + && re.ref1 == ref1) + return re; + } + if (!doneFullScan) { + fullScan(); + return findEntry(tag, ref1); + } + return null; + } + + private + ConcreteEntry findEntry(int tag, T ref1, U ref2) { + // invariant: canWriteDirect(ref1.constantPool()), canWriteDirect(ref2.constantPool()) + int hash = ConcreteEntry.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 ConcreteEntry.RefsEntry re + && re.ref1 == ref1 + && re.ref2 == ref2) { + return re; + } + } + if (!doneFullScan) { + fullScan(); + return findEntry(tag, ref1, ref2); + } + return null; + } + + private ConcreteEntry.ConcreteUtf8Entry 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 ConcreteEntry.ConcreteUtf8Entry ce + && ce.hashCode() == hash + && target.equals(ce.stringValue())) + return ce; + } + if (!doneFullScan) { + fullScan(); + return tryFindUtf8(hash, target); + } + return null; + } + + private ConcreteEntry.ConcreteUtf8Entry tryFindUtf8(int hash, ConcreteEntry.ConcreteUtf8Entry 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 ConcreteEntry.ConcreteUtf8Entry ce + && target.equalsUtf8(ce)) + return ce; + } + if (!doneFullScan) { + fullScan(); + return tryFindUtf8(hash, target); + } + return null; + } + + @Override + public ConcreteEntry.ConcreteUtf8Entry utf8Entry(String s) { + var ce = tryFindUtf8(ConcreteEntry.hashString(s.hashCode()), s); + return ce == null ? internalAdd(new ConcreteEntry.ConcreteUtf8Entry(this, size, s)) : ce; + } + + ConcreteEntry.ConcreteUtf8Entry maybeCloneUtf8Entry(Utf8Entry entry) { + ConcreteEntry.ConcreteUtf8Entry e = (ConcreteEntry.ConcreteUtf8Entry) entry; + if (e.constantPool == this || e.constantPool == parent) + return e; + ConcreteEntry.ConcreteUtf8Entry ce = tryFindUtf8(e.hashCode(), e); + return ce == null ? internalAdd(new ConcreteEntry.ConcreteUtf8Entry(this, size, e)) : ce; + } + + @Override + public ConcreteEntry.ConcreteClassEntry classEntry(Utf8Entry nameEntry) { + ConcreteEntry.ConcreteUtf8Entry ne = maybeCloneUtf8Entry(nameEntry); + var e = (ConcreteEntry.ConcreteClassEntry) findEntry(TAG_CLASS, ne); + return e == null ? internalAdd(new ConcreteEntry.ConcreteClassEntry(this, size, ne)) : e; + } + + @Override + public PackageEntry packageEntry(Utf8Entry nameEntry) { + ConcreteEntry.ConcreteUtf8Entry ne = maybeCloneUtf8Entry(nameEntry); + var e = (ConcreteEntry.ConcretePackageEntry) findEntry(TAG_PACKAGE, ne); + return e == null ? internalAdd(new ConcreteEntry.ConcretePackageEntry(this, size, ne)) : e; + } + + @Override + public ModuleEntry moduleEntry(Utf8Entry nameEntry) { + ConcreteEntry.ConcreteUtf8Entry ne = maybeCloneUtf8Entry(nameEntry); + var e = (ConcreteEntry.ConcreteModuleEntry) findEntry(TAG_MODULE, ne); + return e == null ? internalAdd(new ConcreteEntry.ConcreteModuleEntry(this, size, ne)) : e; + } + + @Override + public ConcreteEntry.ConcreteNameAndTypeEntry natEntry(Utf8Entry nameEntry, Utf8Entry typeEntry) { + ConcreteEntry.ConcreteUtf8Entry ne = maybeCloneUtf8Entry(nameEntry); + ConcreteEntry.ConcreteUtf8Entry te = maybeCloneUtf8Entry(typeEntry); + var e = (ConcreteEntry.ConcreteNameAndTypeEntry) findEntry(TAG_NAMEANDTYPE, ne, te); + return e == null ? internalAdd(new ConcreteEntry.ConcreteNameAndTypeEntry(this, size, ne, te)) : e; + } + + @Override + public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + ConcreteEntry.ConcreteClassEntry oe = (ConcreteEntry.ConcreteClassEntry) owner; + ConcreteEntry.ConcreteNameAndTypeEntry ne = (ConcreteEntry.ConcreteNameAndTypeEntry) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = natEntry(nameAndType.name(), nameAndType.type()); + var e = (ConcreteEntry.ConcreteFieldRefEntry) findEntry(TAG_FIELDREF, oe, ne); + return e == null ? internalAdd(new ConcreteEntry.ConcreteFieldRefEntry(this, size, oe, ne)) : e; + } + + @Override + public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + ConcreteEntry.ConcreteClassEntry oe = (ConcreteEntry.ConcreteClassEntry) owner; + ConcreteEntry.ConcreteNameAndTypeEntry ne = (ConcreteEntry.ConcreteNameAndTypeEntry) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = natEntry(nameAndType.name(), nameAndType.type()); + var e = (ConcreteEntry.ConcreteMethodRefEntry) findEntry(TAG_METHODREF, oe, ne); + return e == null ? internalAdd(new ConcreteEntry.ConcreteMethodRefEntry(this, size, oe, ne)) : e; + } + + @Override + public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + ConcreteEntry.ConcreteClassEntry oe = (ConcreteEntry.ConcreteClassEntry) owner; + ConcreteEntry.ConcreteNameAndTypeEntry ne = (ConcreteEntry.ConcreteNameAndTypeEntry) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = natEntry(nameAndType.name(), nameAndType.type()); + var e = (ConcreteEntry.ConcreteInterfaceMethodRefEntry) findEntry(TAG_INTERFACEMETHODREF, oe, ne); + return e == null ? internalAdd(new ConcreteEntry.ConcreteInterfaceMethodRefEntry(this, size, oe, ne)) : e; + } + + @Override + public MethodTypeEntry methodTypeEntry(MethodTypeDesc descriptor) { + return methodTypeEntry(utf8Entry(descriptor.descriptorString())); + } + + @Override + public MethodTypeEntry methodTypeEntry(Utf8Entry descriptor) { + ConcreteEntry.ConcreteUtf8Entry de = maybeCloneUtf8Entry(descriptor); + var e = (ConcreteEntry.ConcreteMethodTypeEntry) findEntry(TAG_METHODTYPE, de); + return e == null ? internalAdd(new ConcreteEntry.ConcreteMethodTypeEntry(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 = ConcreteEntry.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 ConcreteEntry.ConcreteMethodHandleEntry ce + && ce.kind() == refKind && ce.reference() == reference) + return ce; + } + if (!doneFullScan) { + fullScan(); + return methodHandleEntry(refKind, reference); + } + return internalAdd(new ConcreteEntry.ConcreteMethodHandleEntry(this, size, hash, refKind, (ConcreteEntry.MemberRefEntry) 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 = natEntry(nameAndType.name(), nameAndType.type()); + int hash = ConcreteEntry.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 ConcreteEntry.ConcreteInvokeDynamicEntry ce + && ce.bootstrap() == bootstrapMethodEntry && ce.nameAndType() == nameAndType) + return ce; + } + if (!doneFullScan) { + fullScan(); + return invokeDynamicEntry(bootstrapMethodEntry, nameAndType); + } + + ConcreteEntry.ConcreteInvokeDynamicEntry ce = new ConcreteEntry.ConcreteInvokeDynamicEntry(this, size, hash, (ConcreteBootstrapMethodEntry) bootstrapMethodEntry, (ConcreteEntry.ConcreteNameAndTypeEntry) 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 = natEntry(nameAndType.name(), nameAndType.type()); + int hash = ConcreteEntry.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 ConcreteEntry.ConcreteConstantDynamicEntry ce + && ce.bootstrap() == bootstrapMethodEntry && ce.nameAndType() == nameAndType) + return ce; + } + if (!doneFullScan) { + fullScan(); + return constantDynamicEntry(bootstrapMethodEntry, nameAndType); + } + + ConcreteEntry.ConcreteConstantDynamicEntry ce = new ConcreteEntry.ConcreteConstantDynamicEntry(this, size, hash, (ConcreteBootstrapMethodEntry) bootstrapMethodEntry, (ConcreteEntry.ConcreteNameAndTypeEntry) nameAndType); + internalAdd(ce, hash); + return ce; + } + + @Override + public IntegerEntry intEntry(int value) { + var e = (IntegerEntry) findPrimitiveEntry(TAG_INTEGER, value); + return e == null ? internalAdd(new ConcreteEntry.ConcreteIntegerEntry(this, size, value)) : e; + } + + @Override + public FloatEntry floatEntry(float value) { + var e = (FloatEntry) findPrimitiveEntry(TAG_FLOAT, value); + return e == null ? internalAdd(new ConcreteEntry.ConcreteFloatEntry(this, size, value)) : e; + } + + @Override + public LongEntry longEntry(long value) { + var e = (LongEntry) findPrimitiveEntry(TAG_LONG, value); + return e == null ? internalAdd(new ConcreteEntry.ConcreteLongEntry(this, size, value)) : e; + } + + @Override + public DoubleEntry doubleEntry(double value) { + var e = (DoubleEntry) findPrimitiveEntry(TAG_DOUBLE, value); + return e == null ? internalAdd(new ConcreteEntry.ConcreteDoubleEntry(this, size, value)) : e; + } + + @Override + public StringEntry stringEntry(Utf8Entry utf8) { + ConcreteEntry.ConcreteUtf8Entry ue = maybeCloneUtf8Entry(utf8); + var e = (ConcreteEntry.ConcreteStringEntry) findEntry(TAG_STRING, ue); + return e == null ? internalAdd(new ConcreteEntry.ConcreteStringEntry(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] = (LoadableConstantEntry) arr[i].clone(this); + arguments = List.of(arr); + + break; + } + } + ConcreteEntry.ConcreteMethodHandleEntry mre = (ConcreteEntry.ConcreteMethodHandleEntry) methodReference; + int hash = ConcreteBootstrapMethodEntry.computeHashCode(mre, arguments); + EntryMap map = bsmMap(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + ConcreteBootstrapMethodEntry e = map.getElementByToken(token); + if (e.bootstrapMethod() == mre && e.arguments().equals(arguments)) { + return e; + } + } + ConcreteBootstrapMethodEntry ne = new ConcreteBootstrapMethodEntry(this, bsmSize, hash, mre, arguments); + return internalAdd(ne, hash); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java new file mode 100755 index 0000000000000..abda13c2f6b38 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.lang.constant.ConstantDescs; +import java.util.List; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.attribute.StackMapTableAttribute.*; +import jdk.classfile.ClassReader; + +import static jdk.classfile.Classfile.*; +import jdk.classfile.MethodModel; +import static jdk.classfile.attribute.StackMapTableAttribute.VerificationType.*; + +public class StackMapDecoder { + static final VerificationTypeInfo soleTopVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_TOP); + static final VerificationTypeInfo soleIntegerVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_INTEGER); + static final VerificationTypeInfo soleFloatVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_FLOAT); + static final VerificationTypeInfo soleDoubleVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_DOUBLE); + static final VerificationTypeInfo soleLongVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_LONG); + static final VerificationTypeInfo soleNullVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_NULL); + static final VerificationTypeInfo soleUninitializedThisVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_UNINITIALIZED_THIS); + + private static final int + SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, + SAME_EXTENDED = 251, + FULL = 255; + + private final ClassReader classReader; + private final int pos; + private final StackMapFrame initFrame; + private int p; + + StackMapDecoder(ClassReader classReader, int pos, StackMapFrame initFrame) { + this.classReader = classReader; + this.pos = pos; + this.initFrame = initFrame; + } + + static StackMapFrame initFrame(MethodModel method) { + VerificationTypeInfo vtis[]; + var mdesc = method.descriptorSymbol(); + int i = 0; + if (!method.flags().has(AccessFlag.STATIC)) { + vtis = new VerificationTypeInfo[mdesc.parameterCount() + 1]; + var thisClass = method.parent().orElseThrow().thisClass(); + if ("".equals(method.methodName().stringValue()) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) { + vtis[i++] = StackMapDecoder.soleUninitializedThisVerificationTypeInfo; + } 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" -> StackMapDecoder.soleIntegerVerificationTypeInfo; + case "J" -> StackMapDecoder.soleLongVerificationTypeInfo; + case "F" -> StackMapDecoder.soleFloatVerificationTypeInfo; + case "D" -> StackMapDecoder.soleDoubleVerificationTypeInfo; + case "V" -> throw new IllegalArgumentException("Illegal method argument type: " + arg); + default -> new StackMapDecoder.ObjectVerificationTypeInfoImpl(TemporaryConstantPool.INSTANCE.classEntry(arg)); + }; + } + return new StackMapFrameImpl(FULL, FrameKind.FULL_FRAME, -1, -1, List.of(vtis), List.of(), List.of(vtis), List.of()); + } + + List entries() { + p = pos; + var frame = initFrame; + var entries = new StackMapFrame[u2()]; + for (int ei = 0; ei < entries.length; ei++) { + int frameType = classReader.readU1(p++); + if (frameType < 64) { + frame = new StackMapFrameImpl(frameType, FrameKind.SAME, + frameType, frame.absoluteOffset() + frameType + 1, + List.of(), List.of(), + frame.effectiveLocals(), List.of()); + } else if (frameType < 128) { + var stack = List.of(readVerificationTypeInfo()); + frame = new StackMapFrameImpl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM, + frameType - 64, frame.absoluteOffset() + frameType - 63, + List.of(), stack, + frame.effectiveLocals(), stack); + } else { + if (frameType < SAME_LOCALS_1_STACK_ITEM_EXTENDED) + throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType); + int offsetDelta = u2(); + if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + var stack = List.of(readVerificationTypeInfo()); + frame = new StackMapFrameImpl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM_EXTENDED, + offsetDelta, frame.absoluteOffset() + offsetDelta + 1, + List.of(), stack, + frame.effectiveLocals(), stack); + } else if (frameType < SAME_EXTENDED) { + frame = new StackMapFrameImpl(frameType, FrameKind.CHOP, + offsetDelta, frame.absoluteOffset() + offsetDelta + 1, + List.of(), List.of(), + frame.effectiveLocals().subList(0, frame.effectiveLocals().size() + frameType - SAME_EXTENDED), List.of()); + } else if (frameType == SAME_EXTENDED) { + frame = new StackMapFrameImpl(frameType, FrameKind.SAME_FRAME_EXTENDED, + offsetDelta, frame.absoluteOffset() + offsetDelta + 1, + List.of(), List.of(), + frame.effectiveLocals(), List.of()); + } else if (frameType < SAME_EXTENDED + 4) { + int actSize = frame.effectiveLocals().size(); + var locals = frame.effectiveLocals().toArray(new VerificationTypeInfo[actSize + frameType - SAME_EXTENDED]); + for (int i = actSize; i < locals.length; i++) + locals[i] = readVerificationTypeInfo(); + var locList = List.of(locals); + frame = new StackMapFrameImpl(frameType, FrameKind.APPEND, + offsetDelta, frame.absoluteOffset() + offsetDelta + 1, + locList.subList(actSize, locList.size()), List.of(), + locList, List.of()); + } else { + var locals = new VerificationTypeInfo[u2()]; + for (int i=0; i soleTopVerificationTypeInfo; + case VT_INTEGER -> soleIntegerVerificationTypeInfo; + case VT_FLOAT -> soleFloatVerificationTypeInfo; + case VT_DOUBLE -> soleDoubleVerificationTypeInfo; + case VT_LONG -> soleLongVerificationTypeInfo; + case VT_NULL -> soleNullVerificationTypeInfo; + case VT_UNINITIALIZED_THIS -> soleUninitializedThisVerificationTypeInfo; + case VT_OBJECT -> new ObjectVerificationTypeInfoImpl((ClassEntry)classReader.entryByIndex(u2())); + case VT_UNINITIALIZED -> new UninitializedVerificationTypeInfoImpl(u2()); + default -> throw new IllegalArgumentException("Invalid verification type tag: " + tag); + }; + } + + public static record SimpleVerificationTypeInfoImpl(VerificationType type) implements SimpleVerificationTypeInfo { + + @Override + public String toString() { + return switch (type) { + case ITEM_DOUBLE -> "D"; + case ITEM_FLOAT -> "F"; + case ITEM_INTEGER -> "I"; + case ITEM_LONG -> "J"; + case ITEM_NULL -> "null"; + case ITEM_TOP -> "?"; + case ITEM_UNINITIALIZED_THIS -> "THIS"; + default -> throw new AssertionError("should never happen"); + }; + } + } + + public static record ObjectVerificationTypeInfoImpl( + ClassEntry className) implements ObjectVerificationTypeInfo { + + @Override + public VerificationType type() { return VerificationType.ITEM_OBJECT; } + + @Override + public String toString() { + return className.asInternalName(); + } + } + + public static record UninitializedVerificationTypeInfoImpl(int offset) implements UninitializedVerificationTypeInfo { + + @Override + public VerificationType type() { return VerificationType.ITEM_UNINITIALIZED; } + + @Override + public String toString() { + return "UNINIT(" + offset +")"; + } + } + + private int u2() { + int v = classReader.readU2(p); + p += 2; + return v; + } + + public static record StackMapFrameImpl(int frameType, + FrameKind frameKind, + int offsetDelta, + int absoluteOffset, + List declaredLocals, + List declaredStack, + List effectiveLocals, + List effectiveStack) + implements StackMapFrame { + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java new file mode 100755 index 0000000000000..a2d1db6a14b73 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -0,0 +1,1632 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute 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.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import jdk.classfile.Attribute; + +import static jdk.classfile.Classfile.*; +import jdk.classfile.BufWriter; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.attribute.StackMapTableAttribute; +import jdk.classfile.Attributes; + +/** + * 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.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.classfile.impl.StackMapGenerator.TRACE=true + *
Activates full detailed tracing of the generator process for all invocations. + *
+ */ + +public final class StackMapGenerator { + + private static final boolean TRACE, DEBUG; + private static final Map OPCODE_NAMES; + static { + TRACE = Boolean.getBoolean(StackMapGenerator.class.getName() + ".TRACE"); + DEBUG = TRACE || Boolean.getBoolean(StackMapGenerator.class.getName() + ".DEBUG"); + if (DEBUG) { + OPCODE_NAMES = new HashMap<>(); + for (var o : Opcode.values()) + OPCODE_NAMES.put(o.bytecode(), o.name()); + } else + OPCODE_NAMES = null; + } + + 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 BITS_PER_BYTE = 8; + 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 ClassDesc[] ARRAY_FROM_BASIC_TYPE = new ClassDesc[]{null, null, null, null, + CD_boolean.arrayType(), CD_char.arrayType(), CD_float.arrayType(), CD_double.arrayType(), + CD_byte.arrayType(), CD_short.arrayType(), CD_int.arrayType(), CD_long.arrayType()}; + + private final Type thisType; + private final String methodName; + private final MethodTypeDesc methodDesc; + private final ByteBuffer bytecode; + private final ConstantPoolBuilder 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; + private boolean trace = TRACE; + + /** + * 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, + ConstantPoolBuilder 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.optionValue(Classfile.Option.Key.HIERARCHY_RESOLVER)); + this.patchDeadCode = cp.optionValue(Classfile.Option.Key.PATCH_DEAD_CODE); + this.currentFrame = new Frame(classHierarchy); + if (DEBUG) System.out.println("Generating stack maps for class: " + thisClass.displayName() + " method: " + methodName + " with signature: " + methodDesc); + try { + generate(); + } catch (Error | Exception e) { + if (DEBUG && !trace) { + e.printStackTrace(System.out); + trace = true; + generate(); + } + throw e; + } + } + + /** + * 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(); + if (trace) System.out.println(" Detected mandatory frame bytecode offsets: " + frameOffsets); + 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 { + if (trace) System.out.println(" Entering generator loop"); + processMethod(); + } while (isAnyFrameDirty()); + maxLocals = currentFrame.frameMaxLocals; + maxStack = currentFrame.frameMaxStack; + if (DEBUG) System.out.println(" Calculated maxLocals: " + maxLocals + " maxStack: " + maxStack); + + if (framesCount > 0) if (DEBUG) System.out.println(" Generated stack map frames:"); + //dead code patching + for (int i = 0; i < framesCount; i++) { + var frame = frames.get(i); + if (DEBUG) System.out.println(" " + frame); + if (frame.flags == -1) { + if (!patchDeadCode) generatorError("Unable to generate stack map frame for dead code"); + //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 + if (trace) System.out.println(" Patching dead code range <" + frame.offset + ", " + (frame.offset + blockSize) + ")"); + bytecode.position(frame.offset); + for (int n=1; n= handlerEnd || rangeEnd <= handlerStart) { + //out of range + return; + } + if (trace) System.out.println(" Removing dead code range from exception handler start: " + handlerStart + " end: " + handlerEnd); + 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 AbstractInstruction.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 AbstractInstruction.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 AbstractInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); + it.add(new AbstractInstruction.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) { + int start = b.size(); + 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 int classDescToType(ClassDesc desc, Type inference_types[], int inference_type_index) { + return classDescToType(desc, new BiConsumer<>() { + @Override + public void accept(Integer i, Type vt) { + inference_types[i] = vt; + } + }, inference_type_index); + } + + private static int classDescToType(ClassDesc desc, BiConsumer inference_types_consumer, int inference_type_index) { + if (desc.isClassOrInterface() || desc.isArray()) { + inference_types_consumer.accept(inference_type_index, Type.referenceType(desc)); + return 1; + } + switch (desc.descriptorString()) { + case "J" -> { + inference_types_consumer.accept(inference_type_index, Type.LONG_TYPE); + inference_types_consumer.accept(++inference_type_index, Type.LONG2_TYPE); + return 2; + } + case "D" -> { + inference_types_consumer.accept(inference_type_index, Type.DOUBLE_TYPE); + inference_types_consumer.accept(++inference_type_index, Type.DOUBLE2_TYPE); + return 2; + } + case "I", "Z", "B", "C", "S" -> { + inference_types_consumer.accept(inference_type_index, Type.INTEGER_TYPE); + return 1; + } + case "F" -> { + inference_types_consumer.accept(inference_type_index, Type.FLOAT_TYPE); + return 1; + } + default -> throw new AssertionError("Should not reach here"); + } + } + + 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; + if (trace) System.out.println(" " + currentFrame); + 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"); + } + processBlock(bcs); + ncf = noControlFlow(bcs.rawCode); + } + if (trace) System.out.println(" " + currentFrame); + } + + private void processBlock(RawBytecodeHelper bcs) { + int opcode = bcs.rawCode; + boolean this_uninit = false; + boolean verified_exc_handlers = false; + int bci = bcs.bci; + if (trace) System.out.println(" " +currentFrame +"\n @" + bci + " " + OPCODE_NAMES.get(opcode)); + 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, Classfile.RETURN -> {} + 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.pushStack2(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.pushStack2(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).pushStack2(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).pushStack2(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).pushStack2(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).pushStack2(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).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.FALOAD -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case Classfile.DALOAD -> + currentFrame.decStack(2).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.AALOAD -> + currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()).isNull() ? 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).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.LNEG -> + currentFrame.decStack(2).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.LSHL, Classfile.LSHR, Classfile.LUSHR -> + currentFrame.decStack(3).pushStack2(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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.DNEG -> + currentFrame.decStack(2).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.IINC -> + currentFrame.checkLocal(bcs.getIndex()); + case Classfile.I2L -> + currentFrame.decStack(1).pushStack2(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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.L2F -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case Classfile.L2D -> + currentFrame.decStack(2).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.F2I -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case Classfile.F2L -> + currentFrame.decStack(1).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + case Classfile.F2D -> + currentFrame.decStack(1).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case Classfile.D2L -> + currentFrame.decStack(2).pushStack2(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()); + case Classfile.GOTO_W -> + checkJumpTarget(currentFrame, bcs.destW()); + case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> + processSwitch(bcs); + case Classfile.LRETURN, Classfile.DRETURN -> + currentFrame.decStack(2); + case Classfile.IRETURN, Classfile.FRETURN, Classfile.ARETURN, Classfile.ATHROW -> + currentFrame.decStack(1); + 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); + } + } + + 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); + checkJumpTarget(newFrame, labelContext.labelToBci(exhandler.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.referenceType(CD_String)); + case TAG_CLASS -> + currentFrame.pushStack(Type.referenceType(CD_Class)); + case TAG_INTEGER -> + currentFrame.pushStack(Type.INTEGER_TYPE); + case TAG_FLOAT -> + currentFrame.pushStack(Type.FLOAT_TYPE); + case TAG_DOUBLE -> + currentFrame.pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case TAG_LONG -> + currentFrame.pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + case TAG_METHODHANDLE -> + currentFrame.pushStack(Type.referenceType(CD_MethodHandle)); + case TAG_METHODTYPE -> + currentFrame.pushStack(Type.referenceType(CD_MethodType)); + case TAG_CONSTANTDYNAMIC -> { + Type[] vConstantType = new Type[2]; + int n = classDescToType(((ConstantDynamicEntry)cp.entryByIndex(index)).asSymbol().constantType(), vConstantType, 0); + for (int i = 0; i < n; i++) { + currentFrame.pushStack(vConstantType[i]); + } + } + default -> + generatorError("Invalid index in ldc"); + } + } + + 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) { + int index = bcs.getIndexU2(); + Type[] fieldType = new Type[2]; + int n = classDescToType(ClassDesc.ofDescriptor(((MemberRefEntry)cp.entryByIndex(index)).nameAndType().type().stringValue()), fieldType, 0); + switch (bcs.rawCode) { + case Classfile.GETSTATIC -> { + for (int i = 0; i < n; i++) { + currentFrame.pushStack(fieldType[i]); + } + } + case Classfile.PUTSTATIC -> { + for (int i = n - 1; i >= 0; i--) { + currentFrame.popStack(); + } + } + case Classfile.GETFIELD -> { + currentFrame.popStack(); + for (int i = 0; i < n; i++) { + currentFrame.pushStack(fieldType[i]); + } + } + case Classfile.PUTFIELD -> { + for (int i = n - 1; i >= 0; i--) { + currentFrame.popStack(); + } + 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 methodDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); + Type[] sig_type = new Type[2]; + int nargs = 0; + ArrayList sigVerifTypes = new ArrayList<>(); + for (int i = 0; i < methodDesc.parameterCount(); i++) { + int n = classDescToType(methodDesc.parameterType(i), sig_type, 0); + for (int x = 0; x < n; x++) { + sigVerifTypes.add(sig_type[x]); + } + nargs += n; + } + if (!methodDesc.returnType().equals(CD_void)) { + int n = classDescToType(methodDesc.returnType(), sig_type, 0); + for (int y = 0; y < n; y++) { + sigVerifTypes.add(sig_type[y]); + } + } + int bci = bcs.bci; + for (int i = nargs - 1; i >= 0; i--) { + currentFrame.popStack(); + } + if (opcode != Classfile.INVOKESTATIC && opcode != Classfile.INVOKEDYNAMIC) { + if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { + Type type = currentFrame.popStack(); + if (type.isUninitializedThis()) { + if (inTryBlock) { + processExceptionHandlerTargets(bci, true); + } + currentFrame.initializeObject(type, thisType); + thisUninit = true; + } else if (type.isUninitialized()) { + 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(); + } + } + int sig_verif_types_len = sigVerifTypes.size(); + if (sig_verif_types_len > nargs) { + if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { + generatorError("Return type must be void in method"); + } + for (int i = nargs; i < sig_verif_types_len; i++) { + currentFrame.pushStack(sigVerifTypes.get(i)); + } + } + return thisUninit; + } + + private Type getNewarrayType(int index) { + if (index < T_BOOLEAN || index > T_LONG) generatorError("Illegal newarray instruction"); + return Type.referenceType(ARRAY_FROM_BASIC_TYPE[index]); + } + + private void processAnewarray(int index) { + currentFrame.popStack(); + currentFrame.pushStack(Type.referenceType(cpIndexToType(index, cp).sym.arrayType())); + } + + /** + * Throws java.lang.VerifyError with given error message + * @param msg error message + */ + private void generatorError(String msg) { + throw new VerifyError(String.format("%s at %s", msg, methodName)); + } + + /** + * Simple check if the given opcode falls into "no control flow" instructions group + * (GOTO, GOTO_W, TABLESWITCH, + * LOOKUPSWITCH, ATHROW, all xRETURN instructions), + * for which current stack map frame is not propagated to the next instruction + * and a new frame must be provided for the following instruction (if any) + * @param opcode bytecode instruction opcode + * @return boolean true if the opcode is in the "no control flow" group + */ + private static boolean noControlFlow(int opcode) { + return switch(opcode) { + case Classfile.GOTO, Classfile.GOTO_W, Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH, Classfile.IRETURN, Classfile.LRETURN, Classfile.FRETURN, Classfile.DRETURN, Classfile.ARETURN, Classfile.RETURN, Classfile.ATHROW -> true; + default -> false; + }; + } + + /** + * 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()) + generatorError("Branch offset out of bytecode range: " + i); + super.set(i); + } + }; + RawBytecodeHelper bcs = new RawBytecodeHelper(bytecode); + boolean no_control_flow = false; + int opcode, bci; + while (!bcs.isLastBytecode()) { + opcode = bcs.rawNext(); + bci = bcs.bci; + if (no_control_flow) { + offsets.set(bci); + } + no_control_flow = noControlFlow(opcode); + switch (opcode) { + 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, Classfile.GOTO -> + offsets.set(bcs.dest()); + case Classfile.GOTO_W -> + offsets.set(bcs.destW()); + 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)); + } + } + }; + } + for (var exhandler : exceptionTable) { + offsets.set(labelContext.labelToBci(exhandler.handler())); + } + return offsets; + } + + private static 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.offset = -1; + this.localsSize = 0; + this.stackSize = 0; + this.flags = 0; + this.classHierarchy = classHierarchy; + this.locals = null; + this.stack = null; + } + + Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl context) { + this.offset = offset; + this.localsSize = locals_size; + this.stackSize = stack_size; + this.flags = flags; + this.locals = locals; + this.stack = stack; + this.classHierarchy = context; + } + + public Frame(int offset, ClassHierarchyImpl context) { + this(offset, -1, 0, 0, null, null, context); + } + + @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(Type type) { + checkStack(stackSize); + stack[stackSize++] = type; + return this; + } + + void pushStack2(Type type1, Type type2) { + checkStack(stackSize + 1); + stack[stackSize++] = type1; + stack[stackSize++] = type2; + } + + Type popStack() { + if (stackSize < 1) throw new VerifyError("Operand stack underflow"); + return stack[--stackSize]; + } + + Frame decStack(int size) { + stackSize -= size; + if (stackSize < 0) throw new VerifyError("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.isUninitializedThis()) { + 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; + } + + Type setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { + localsSize = 0; + if (!isStatic) { + localsSize++; + if (OBJECT_INITIALIZER_NAME.equals(name) && !ConstantDescs.CD_Object.equals(thisKlass.sym)) { + setLocal(0, Type.uninitialized_this_type); + flags |= FLAG_THIS_UNINIT; + } else { + setLocalRawInternal(0, thisKlass); + } + } + for (int i = 0; i < methodDesc.parameterCount(); i++) { + localsSize += StackMapGenerator.classDescToType(methodDesc.parameterType(i), new BiConsumer<>() { + @Override + public void accept(Integer i, Type vt) { + setLocalRawInternal(i, vt); + } + }, localsSize); + } + var ret = methodDesc.returnType(); + if (ret.isClassOrInterface() || ret.isArray()) { + return Type.referenceType(ret); + } + return switch (ret.descriptorString()) { + case "I" -> Type.INTEGER_TYPE; + case "B" -> Type.BYTE_TYPE; + case "C" -> Type.CHAR_TYPE; + case "S" -> Type.SHORT_TYPE; + case "Z" -> Type.BOOLEAN_TYPE; + case "F"-> Type.FLOAT_TYPE; + case "D" -> Type.DOUBLE_TYPE; + case "J" -> Type.LONG_TYPE; + case "V" -> Type.TOP_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); + for (int i = 0; i < src.localsSize; i++) { + locals[i] = src.locals[i]; + } + if (stack != null) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE); + stackSize = src.stackSize; + checkStack(src.stackSize - 1); + for (int i = 0; i < src.stackSize; i++) { + stack[i] = src.stack[i]; + } + 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 getLocal2(int index) { + getLocalRawInternal(index); + getLocalRawInternal(index + 1); + } + + void setLocal(int index, Type type) { + Type old = getLocalRawInternal(index); + if (old.isDouble() || old.isLong()) { + setLocalRawInternal(index + 1, Type.TOP_TYPE); + } + if (old.isDouble2() || old.isLong2()) { + 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.isDouble() || old.isLong()) { + setLocalRawInternal(index + 2, Type.TOP_TYPE); + } + old = getLocalRawInternal(index); + if (old.isDouble2() || old.isLong2()) { + 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].isTop()) 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 CONSTANTS_MAP = new IdentityHashMap<>(18); + + @Override + public String toString() { + if (CONSTANTS_MAP.isEmpty()) { + for (Field f : Type.class.getDeclaredFields()) { + var name = f.getName(); + if (Modifier.isStatic(f.getModifiers()) && f.getType() == Type.class && name.endsWith("_TYPE")) try { + CONSTANTS_MAP.put((Type) f.get(null), name.substring(0, name.length() - 5)); + } catch (IllegalAccessException ignore) { + } + } + } + if (sym != null) { + return sym.displayName(); + } + if ((data & 0xff) == UNINITIALIZED) { + return "uninit@" + (data >> 8); + } + return CONSTANTS_MAP.getOrDefault(this, Integer.toHexString(data)); + } + + private static final int TYPE_MASK = 0x00000003, + REFERENCE = 0x0, + PRIMITIVE = 0x1, + UNINITIALIZED = 0x2, + TYPE_QUERY = 0x3, + CATEGORY1_FLAG = 0x01, + CATEGORY2_FLAG = 0x02, + CATEGORY2_2ND_FLAG = 0x04, + NULL = 0x00000000, + CATEGORY1 = (CATEGORY1_FLAG << 1 * BITS_PER_BYTE) | PRIMITIVE, + CATEGORY2 = (CATEGORY2_FLAG << 1 * BITS_PER_BYTE) | PRIMITIVE, + CATEGORY2_2ND = (CATEGORY2_2ND_FLAG << 1 * BITS_PER_BYTE) | PRIMITIVE, + TOP = PRIMITIVE, + BOOLEAN = (ITEM_BOOLEAN << 2 * BITS_PER_BYTE) | CATEGORY1, + BYTE = (ITEM_BYTE << 2 * BITS_PER_BYTE) | CATEGORY1, + SHORT = (ITEM_SHORT << 2 * BITS_PER_BYTE) | CATEGORY1, + CHAR = (ITEM_CHAR << 2 * BITS_PER_BYTE) | CATEGORY1, + INTEGER = (ITEM_INTEGER << 2 * BITS_PER_BYTE) | CATEGORY1, + FLOAT = (ITEM_FLOAT << 2 * BITS_PER_BYTE) | CATEGORY1, + LONG = (ITEM_LONG << 2 * BITS_PER_BYTE) | CATEGORY2, + DOUBLE = (ITEM_DOUBLE << 2 * BITS_PER_BYTE) | CATEGORY2, + LONG_2ND = (ITEM_LONG_2ND << 2 * BITS_PER_BYTE) | CATEGORY2_2ND, + DOUBLE_2ND = (ITEM_DOUBLE_2ND << 2 * BITS_PER_BYTE) | CATEGORY2_2ND, + BCI_MASK = 0xffff << 1 * BITS_PER_BYTE, + BCI_FOR_THIS = 0xffff; + + private Type(int raw_data) { + this(raw_data, null); + } + + private Type(int raw_data, ClassDesc sym) { + this.data = raw_data; + this.sym = sym; + } + + static final Type TOP_TYPE = new Type(TOP), + NULL_TYPE = new Type(NULL), + INTEGER_TYPE = new Type(INTEGER), + FLOAT_TYPE = new Type(FLOAT), + LONG_TYPE = new Type(LONG), + LONG2_TYPE = new Type(LONG_2ND), + DOUBLE_TYPE = new Type(DOUBLE), + BOOLEAN_TYPE = new Type(BOOLEAN), + BYTE_TYPE = new Type(BYTE), + CHAR_TYPE = new Type(CHAR), + SHORT_TYPE = new Type(SHORT), + DOUBLE2_TYPE = new Type(DOUBLE_2ND); + + static final Type OBJECT_TYPE = new Type(CD_Object), + THROWABLE_TYPE = new Type(CD_Throwable), + INT_ARRAY_TYPE = new Type(CD_int.arrayType()), + BOOLEAN_ARRAY_TYPE = new Type(CD_boolean.arrayType()), + BYTE_ARRAY_TYPE = new Type(CD_byte.arrayType()), + CHAR_ARRAY_TYPE = new Type(CD_char.arrayType()), + SHORT_ARRAY_TYPE = new Type(CD_short.arrayType()), + LONG_ARRAY_TYPE = new Type(CD_long.arrayType()), + DOUBLE_ARRAY_TYPE = new Type(CD_double.arrayType()), + FLOAT_ARRAY_TYPE = new Type(CD_float.arrayType()); + + static Type referenceType(ClassDesc sh) { + return new Type(sh); + } + + static Type uninitializedType(int bci) { + return new Type(bci << 1 * BITS_PER_BYTE | UNINITIALIZED); + } + static final Type uninitialized_this_type = uninitializedType(BCI_FOR_THIS); + + boolean isTop() { + return (data == TOP); + } + + boolean isNull() { + return (data == NULL); + } + + boolean isNullOrArray() { + return isNull() || isArray();// || (sub1 != null && sub1.isNullOrArray() && sub2.isNullOrArray()); + } + + boolean isBoolean() { + return (data == BOOLEAN); + } + + boolean isByte() { + return (data == BYTE); + } + + boolean isChar() { + return (data == CHAR); + } + + boolean isShort() { + return (data == SHORT); + } + + boolean isInteger() { + return (data == INTEGER); + } + + boolean isLong() { + return (data == LONG); + } + + boolean isFloat() { + return (data == FLOAT); + } + + boolean isDouble() { + return (data == DOUBLE); + } + + boolean isLong2() { + return (data == LONG_2ND); + } + + boolean isDouble2() { + return (data == DOUBLE_2ND); + } + + boolean isReference() { + return ((data & TYPE_MASK) == REFERENCE); + } + + boolean isCategory1() { + return ((data & CATEGORY1) != PRIMITIVE); + } + + boolean isCategory2() { + return ((data & CATEGORY2) == CATEGORY2); + } + + boolean isCategory2_2nd() { + return ((data & CATEGORY2_2ND) == CATEGORY2_2ND); + } + + boolean isCheck() { + return (data & TYPE_QUERY) == TYPE_QUERY; + } + + boolean isObject() { + return (isReference() && !isNull() && sym.isClassOrInterface()); + } + + boolean isArray() { + return (isReference() && !isNull() && sym.isArray()); + } + + boolean isUninitialized() { + return ((data & UNINITIALIZED) == UNINITIALIZED); + } + + boolean isUninitializedThis() { + return isUninitialized() && bci() == BCI_FOR_THIS; + } + + int bci() { + return ((data & BCI_MASK) >> 1 * BITS_PER_BYTE); + } + + Type mergeFrom(Type from, ClassHierarchyImpl context) { + if (isTop() || this == from || equals(from)) { + return this; + } else { + switch (data) { + case BOOLEAN: + case BYTE: + case CHAR: + case SHORT: + return from.isInteger() ? this : Type.TOP_TYPE; + default: + if (isReference() && from.isReference()) { + return mergeReferenceFrom(from, context); + } else { + return Type.TOP_TYPE; + } + } + } + } + + Type mergeComponentFrom(Type from, ClassHierarchyImpl context) { + if (isTop() || this == from || equals(from)) { + return this; + } else { + switch (data) { + case BOOLEAN: + case BYTE: + case CHAR: + case SHORT: + return Type.TOP_TYPE; + default: + if (isReference() && from.isReference()) { + return mergeReferenceFrom(from, context); + } else { + return Type.TOP_TYPE; + } + } + } + } + + int dimensions() { + int index = 0; + String desc = sym.descriptorString(); + while (desc.charAt(index) == '[') { + index++; + } + return index; + } + + 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.isNull()) { + return this; + } else if (isNull()) { + return from; + } else if (sym.equals(from.sym)) { + return this; + } else if (isObject()) { + if (ConstantDescs.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); + } + return Type.OBJECT_TYPE; + } else if (isArray() && from.isArray()) { + Type compThis = getComponent(); + Type compFrom = from.getComponent(); + if (!compThis.isTop() && !compFrom.isTop()) { + return compThis.mergeComponentFrom(compFrom, context).toArray(); + } + } + return OBJECT_TYPE; + } + + Type toArray() { + if (isBoolean()) { + return BOOLEAN_ARRAY_TYPE; + } else if (isByte()) { + return BYTE_ARRAY_TYPE; + } else if (isChar()) { + return CHAR_ARRAY_TYPE; + } else if (isShort()) { + return SHORT_ARRAY_TYPE; + } else if (isInteger()) { + return INT_ARRAY_TYPE; + } else if (isLong()) { + return LONG_ARRAY_TYPE; + } else if (isFloat()) { + return FLOAT_ARRAY_TYPE; + } else if (isDouble()) { + return DOUBLE_ARRAY_TYPE; + } else if (isArray() || isObject()) { + return Type.referenceType(sym.arrayType()); + } else { + return 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) { + if (isTop()) { + bw.writeU1(ITEM_TOP); + } else if (isInteger()) { + bw.writeU1(ITEM_INTEGER); + } else if (isFloat()) { + bw.writeU1(ITEM_FLOAT); + } else if (isLong()) { + bw.writeU1(ITEM_LONG); + } else if (isDouble()) { + bw.writeU1(ITEM_DOUBLE); + } else if (isNull()) { + bw.writeU1(ITEM_NULL); + } else if (isUninitializedThis()) { + bw.writeU1(ITEM_UNINITIALIZED_THIS); + } else if (isReference()) { + bw.writeU1(ITEM_OBJECT); + bw.writeU2(cp.classEntry(cp.utf8Entry(Util.toInternalName(sym))).index()); + } else if (isUninitialized()) { + bw.writeU1(ITEM_UNINITIALIZED); + bw.writeU2(bci()); + } + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java b/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java new file mode 100755 index 0000000000000..698cbb3301f76 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.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.classfile.impl; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Superclass; + +import static java.util.Objects.requireNonNull; + +/** + * SuperclassImpl + */ +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); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java b/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java new file mode 100644 index 0000000000000..70ba5fec68117 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.List; +import jdk.classfile.Label; +import jdk.classfile.TypeAnnotation.*; + +/** + * + */ +public final class TargetInfoImpl { + + private TargetInfoImpl() { + } + + public record TypeParameterTargetImpl(TargetType targetType, int typeParameterIndex) + implements TypeParameterTarget { + } + + 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 record EmptyTargetImpl(TargetType targetType) implements EmptyTarget { + } + + 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 { + @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 record TypeArgumentTargetImpl(TargetType targetType, Label target, int typeArgumentIndex) + implements TypeArgumentTarget { + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java b/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java new file mode 100644 index 0000000000000..ee331da70fc24 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import jdk.classfile.*; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.MethodRefEntry; +import jdk.classfile.constantpool.MethodTypeEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; + +import java.lang.constant.MethodTypeDesc; +import java.util.Collections; +import java.util.List; + +import static jdk.classfile.Classfile.TAG_METHODTYPE; + +public final class TemporaryConstantPool implements ConstantPoolBuilder { + private static final Options options = new Options(Collections.emptyList()); + + private TemporaryConstantPool() {}; + + public static final TemporaryConstantPool INSTANCE = new TemporaryConstantPool(); + + @Override + public Utf8Entry utf8Entry(String s) { + return new ConcreteEntry.ConcreteUtf8Entry(this, -1, s); + } + + @Override + public IntegerEntry intEntry(int value) { + return new ConcreteEntry.ConcreteIntegerEntry(this, -1, value); + } + + @Override + public FloatEntry floatEntry(float value) { + return new ConcreteEntry.ConcreteFloatEntry(this, -1, value); + } + + @Override + public LongEntry longEntry(long value) { + return new ConcreteEntry.ConcreteLongEntry(this, -1, value); + } + + @Override + public DoubleEntry doubleEntry(double value) { + return new ConcreteEntry.ConcreteDoubleEntry(this, -1, value); + } + + @Override + public ClassEntry classEntry(Utf8Entry name) { + return new ConcreteEntry.ConcreteClassEntry(this, -2, (ConcreteEntry.ConcreteUtf8Entry) name); + } + + @Override + public PackageEntry packageEntry(Utf8Entry name) { + return new ConcreteEntry.ConcretePackageEntry(this, -2, (ConcreteEntry.ConcreteUtf8Entry) name); + } + + @Override + public ModuleEntry moduleEntry(Utf8Entry name) { + return new ConcreteEntry.ConcreteModuleEntry(this, -2, (ConcreteEntry.ConcreteUtf8Entry) name); + } + + @Override + public NameAndTypeEntry natEntry(Utf8Entry nameEntry, Utf8Entry typeEntry) { + return new ConcreteEntry.ConcreteNameAndTypeEntry(this, -3, + (ConcreteEntry.ConcreteUtf8Entry) nameEntry, + (ConcreteEntry.ConcreteUtf8Entry) typeEntry); + } + + @Override + public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new ConcreteEntry.ConcreteFieldRefEntry(this, -3, + (ConcreteEntry.ConcreteClassEntry) owner, + (ConcreteEntry.ConcreteNameAndTypeEntry) nameAndType); + } + + @Override + public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new ConcreteEntry.ConcreteMethodRefEntry(this, -3, + (ConcreteEntry.ConcreteClassEntry) owner, + (ConcreteEntry.ConcreteNameAndTypeEntry) nameAndType); + } + + @Override + public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new ConcreteEntry.ConcreteInterfaceMethodRefEntry(this, -3, + (ConcreteEntry.ConcreteClassEntry) owner, + (ConcreteEntry.ConcreteNameAndTypeEntry) 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) { + throw new UnsupportedOperationException(); + } + + @Override + public T maybeClone(T entry) { + return entry; + } + + @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 T optionValue(Classfile.Option.Key option) { + return options.value(option); + } + + @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/classfile/impl/TerminalCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/TerminalCodeBuilder.java new file mode 100755 index 0000000000000..6d2113eb85450 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/TerminalCodeBuilder.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.classfile.impl; + +import jdk.classfile.CodeBuilder; + +/** + * TerminalCodeBuilder + */ +public sealed interface TerminalCodeBuilder extends CodeBuilder + permits DirectCodeBuilder, BufferedCodeBuilder { + +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/TerminalFieldBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/TerminalFieldBuilder.java new file mode 100755 index 0000000000000..d5eee7c62b0e9 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/TerminalFieldBuilder.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.classfile.impl; + +import jdk.classfile.FieldBuilder; + +/** + * TerminalFieldBuilder + */ +public sealed interface TerminalFieldBuilder + extends FieldBuilder + permits DirectFieldBuilder, BufferedFieldBuilder { +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/TerminalMethodBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/TerminalMethodBuilder.java new file mode 100755 index 0000000000000..1a26b6b435d2b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/TerminalMethodBuilder.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.classfile.impl; + +import jdk.classfile.CodeModel; +import jdk.classfile.MethodBuilder; + +/** + * TerminalMethodBuilder + */ +public sealed interface TerminalMethodBuilder + extends MethodBuilder + permits BufferedMethodBuilder, DirectMethodBuilder { + BufferedCodeBuilder bufferedCodeBuilder(CodeModel original); +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/TransformImpl.java b/src/java.base/share/classes/jdk/classfile/impl/TransformImpl.java new file mode 100755 index 0000000000000..60a3a7ee69fac --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/TransformImpl.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. + */ +package jdk.classfile.impl; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassTransform; +import jdk.classfile.ClassfileTransform; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.FieldBuilder; +import jdk.classfile.FieldElement; +import jdk.classfile.FieldModel; +import jdk.classfile.FieldTransform; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.MethodTransform; + +/** + * TransformImpl + */ +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 FakeClassTransform 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 ClassTransformImpl(Consumer consumer, + Runnable endHandler, + Runnable startHandler) + implements ClassfileTransform.ResolvedTransform { + + public ClassTransformImpl(Consumer consumer) { + this(consumer, NOTHING, NOTHING); + } + } + + public record ChainedClassTransform(ClassTransform t, + ClassTransform next) + implements FakeClassTransform { + @Override + public ClassTransformImpl resolve(ClassBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + ClassBuilder chainedBuilder = new ChainedClassBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ClassTransformImpl(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierClassTransform(Supplier supplier) + implements FakeClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return supplier.get().resolve(builder); + } + } + + public record ClassMethodTransform(MethodTransform transform, + Predicate filter) + implements FakeClassTransform { + @Override + public ClassTransformImpl resolve(ClassBuilder builder) { + return new ClassTransformImpl(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 FakeClassTransform.super.andThen(next); + } + } + + public record ClassFieldTransform(FieldTransform transform, + Predicate filter) + implements FakeClassTransform { + @Override + public ClassTransformImpl resolve(ClassBuilder builder) { + return new ClassTransformImpl(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 FakeClassTransform.super.andThen(next); + } + } + + // MethodTransform + + interface FakeMethodTransform 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 MethodTransformImpl(Consumer consumer, + Runnable endHandler, + Runnable startHandler) + implements ClassfileTransform.ResolvedTransform { + } + + public record ChainedMethodTransform(MethodTransform t, + MethodTransform next) + implements TransformImpl.FakeMethodTransform { + @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 MethodTransformImpl(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierMethodTransform(Supplier supplier) + implements TransformImpl.FakeMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + return supplier.get().resolve(builder); + } + } + + public record MethodCodeTransform(CodeTransform xform) + implements TransformImpl.FakeMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + return new MethodTransformImpl(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)) + : FakeMethodTransform.super.andThen(next); + + } + } + + // FieldTransform + + interface FakeFieldTransform 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 FieldTransformImpl(Consumer consumer, + Runnable endHandler, + Runnable startHandler) + implements ClassfileTransform.ResolvedTransform { + } + + public record ChainedFieldTransform(FieldTransform t, FieldTransform next) + implements FakeFieldTransform { + @Override + public FieldTransformImpl resolve(FieldBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + FieldBuilder chainedBuilder = new ChainedFieldBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new FieldTransformImpl(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierFieldTransform(Supplier supplier) + implements FakeFieldTransform { + @Override + public ResolvedTransform resolve(FieldBuilder builder) { + return supplier.get().resolve(builder); + } + } + + // CodeTransform + + interface FakeCodeTransform 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 CodeTransformImpl(Consumer consumer, + Runnable endHandler, + Runnable startHandler) + implements ClassfileTransform.ResolvedTransform { + } + + public record ChainedCodeTransform(CodeTransform t, CodeTransform next) + implements FakeCodeTransform { + @Override + public CodeTransformImpl resolve(CodeBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + CodeBuilder chainedBuilder = new ChainedCodeBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new CodeTransformImpl(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierCodeTransform(Supplier supplier) + implements FakeCodeTransform { + @Override + public ResolvedTransform resolve(CodeBuilder builder) { + return supplier.get().resolve(builder); + } + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java new file mode 100755 index 0000000000000..df6f969b34f85 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java @@ -0,0 +1,959 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import jdk.classfile.Annotation; +import jdk.classfile.AnnotationElement; +import jdk.classfile.AnnotationValue; +import jdk.classfile.Attribute; +import jdk.classfile.AttributeMapper; +import jdk.classfile.Attributes; +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.BufWriter; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.TypeAnnotation; +import jdk.classfile.attribute.AnnotationDefaultAttribute; +import jdk.classfile.attribute.BootstrapMethodsAttribute; +import jdk.classfile.attribute.CharacterRangeInfo; +import jdk.classfile.attribute.CharacterRangeTableAttribute; +import jdk.classfile.attribute.CompilationIDAttribute; +import jdk.classfile.attribute.ConstantValueAttribute; +import jdk.classfile.attribute.DeprecatedAttribute; +import jdk.classfile.attribute.EnclosingMethodAttribute; +import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.InnerClassInfo; +import jdk.classfile.attribute.InnerClassesAttribute; +import jdk.classfile.attribute.LineNumberInfo; +import jdk.classfile.attribute.LineNumberTableAttribute; +import jdk.classfile.attribute.LocalVariableInfo; +import jdk.classfile.attribute.LocalVariableTableAttribute; +import jdk.classfile.attribute.LocalVariableTypeInfo; +import jdk.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.classfile.attribute.MethodParameterInfo; +import jdk.classfile.attribute.MethodParametersAttribute; +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleExportInfo; +import jdk.classfile.attribute.ModuleHashInfo; +import jdk.classfile.attribute.ModuleHashesAttribute; +import jdk.classfile.attribute.ModuleMainClassAttribute; +import jdk.classfile.attribute.ModuleOpenInfo; +import jdk.classfile.attribute.ModulePackagesAttribute; +import jdk.classfile.attribute.ModuleProvideInfo; +import jdk.classfile.attribute.ModuleRequireInfo; +import jdk.classfile.attribute.ModuleResolutionAttribute; +import jdk.classfile.attribute.ModuleTargetAttribute; +import jdk.classfile.attribute.NestHostAttribute; +import jdk.classfile.attribute.NestMembersAttribute; +import jdk.classfile.attribute.PermittedSubclassesAttribute; +import jdk.classfile.attribute.RecordAttribute; +import jdk.classfile.attribute.RecordComponentInfo; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.attribute.SourceDebugExtensionAttribute; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.attribute.SourceIDAttribute; +import jdk.classfile.attribute.SyntheticAttribute; +import jdk.classfile.constantpool.ConstantValueEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.Utf8Entry; + +/** + * UnboundAttribute + */ +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); + } + + 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 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; + } + + @Override + public Kind codeKind() { + return Kind.TYPE_ANNOTATION; + } + + @Override + public Opcode opcode() { + return Opcode.TYPE_ANNO; + } + + @Override + public int sizeInBytes() { + return 0; + } + } + + 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; + } + + @Override + public Kind codeKind() { + return Kind.TYPE_ANNOTATION; + } + + @Override + public Opcode opcode() { + return Opcode.TYPE_ANNO; + } + + @Override + public int sizeInBytes() { + return 0; + } + } + + 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(LabelResolver 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) { + LabelResolver lr = ((BufWriterImpl) buf).labelResolver(); + // 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 non-sealed static abstract 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 sealed abstract class CustomAttribute> + extends UnboundAttribute + permits jdk.classfile.CustomAttribute { + + public CustomAttribute(AttributeMapper mapper) { + super(mapper); + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/Util.java b/src/java.base/share/classes/jdk/classfile/impl/Util.java new file mode 100755 index 0000000000000..f1662c92b35f5 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/Util.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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.classfile.CodeElement; +import jdk.classfile.Opcode; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.jdktypes.AccessFlag; + +import static jdk.classfile.Classfile.ACC_STATIC; + +/** + * 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(); + 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 ')': + i = type.length(); + break; + 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.replaceAll("/", "."); + } + + public static Iterator parameterTypes(String s) { + //TODO: gracefully non-method types + return new Iterator<>() { + int ch = 1; + + public boolean hasNext() { + return s.charAt(ch) != ')'; + } + + 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 descriptorToClass(String type) { + return type.startsWith("L") && type.charAt(type.length() - 1) == ';' + ? type.substring(1, type.length() - 1) + : type; + } + + public static String classToDescriptor(String c) { + return (c.startsWith("[") || (c.startsWith("L") && c.charAt(c.length() - 1) == ';')) + ? c + : "L" + c + ";"; + } + + public static String toInternalName(ClassDesc cd) { + return descriptorToClass(cd.descriptorString()); + } + + public static ClassDesc toClassDesc(String internalName) { + return ClassDesc.ofDescriptor((internalName.charAt(0) == '[') + ? internalName + : "L" + internalName + ";"); + } + + 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) { + // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy + ClassEntry[] result = new ClassEntry[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 List.of(result); + } + + public static void checkKind(Opcode op, CodeElement.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/classfile/impl/verifier/VerificationBytecodes.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationBytecodes.java new file mode 100644 index 0000000000000..9523bde48055e --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationBytecodes.java @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl.verifier; + +import java.nio.ByteBuffer; + +import jdk.classfile.Classfile; +import jdk.classfile.impl.verifier.VerificationSignature.BasicType; +import static jdk.classfile.impl.verifier.VerificationSignature.BasicType.*; + +/** + * @see
hotspot/share/interpreter/bytecodes.hpp + * @see hotspot/share/interpreter/bytecodes.cpp + */ +class VerificationBytecodes { + + static final int _breakpoint = 202, + number_of_java_codes = 203, + _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 length_for(int code) { + return is_valid(code) ? _lengths[code] & 0xf : -1; + } + + 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 int raw_special_length_at(byte bytecode[], int bci, int end) { + int code = code_or_bp_at(bytecode, bci); + if (code == _breakpoint) { + return 1; + } else { + return special_length_at(code, bytecode, bci, end); + } + } + + 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); + } + + final int code; + VerificationBytecodes(int code) { + this.code = code; + } +} diff --git a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationFrame.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationFrame.java new file mode 100644 index 0000000000000..f431a33ea677b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationFrame.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.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"); + } + VerificationType top = _stack[--_stack_size]; + return top; + } + + 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) { + VerificationType[] stack = new VerificationType[1]; + VerificationFrame frame = new VerificationFrame(_offset, flags, _locals_size, 0, _max_locals, _max_stack, _locals, stack, _verifier); + return frame; + } + + 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; + for (int i = 0; i < len; i++) { + _locals[i] = src.locals()[i]; + } + } + + void copy_stack(VerificationFrame src) { + int len = src.stack_size() < _stack_size ? src.stack_size() : _stack_size; + for (int i = 0; i < len; i++) { + _stack[i] = src.stack()[i]; + } + } + + private int is_assignable_to(VerificationType[] from, VerificationType[] to, int len) { + int i = 0; + for (i = 0; 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_SLASH = '/', + JVM_SIGNATURE_DOT = '.', + JVM_SIGNATURE_SPECIAL = '<', + JVM_SIGNATURE_ENDSPECIAL = '>', + JVM_SIGNATURE_ARRAY = '[', + JVM_SIGNATURE_BYTE = 'B', + JVM_SIGNATURE_CHAR = 'C', + JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_ENDCLASS = ';', + JVM_SIGNATURE_ENUM = 'E', + 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 isJavaPrimitive(BasicType t) { + return BasicType.T_BOOLEAN.type <= t.type && t.type <= BasicType.T_LONG.type; + } + + static boolean isReferenceType(BasicType t) { + return t == BasicType.T_OBJECT || t == BasicType.T_ARRAY; + } + + static char type2Char(BasicType t) { + return t.type < TYPE2CHAR_TAB.length ? TYPE2CHAR_TAB[t.type] : 0; + } + + 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; + } + + VerificationSignature(String signature, boolean is_method) { + this(signature, is_method, null); + } + + 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/classfile/impl/verifier/VerificationTable.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationTable.java new file mode 100644 index 0000000000000..2e9653e4614c0 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.impl.verifier; + +import static jdk.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 VerificationWrapper.ConstantPoolWrapper _cp; + private final StackMapStream _stream; + private byte[] _code_data; + private 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 << 1 * BitsPerByte) | Primitive, + Category2 = (Category2Flag << 1 * BitsPerByte) | Primitive, + Category2_2nd = (Category2_2ndFlag << 1 * 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 << 1 * BitsPerByte, + // A bci of -1 is an Unintialized-This + BciForThis = 0xffff, + // Query values + ReferenceQuery = (ReferenceFlag << 1 * BitsPerByte) | TypeQuery, + Category1Query = (Category1Flag << 1 * BitsPerByte) | TypeQuery, + Category2Query = (Category2Flag << 1 * BitsPerByte) | TypeQuery, + Category2_2ndQuery = (Category2_2ndFlag << 1 * BitsPerByte) | TypeQuery; + + VerificationType(int raw_data) { + this._data = raw_data; + this._sym = null; + } + + VerificationType() { + this(Bogus); + } + + static final VerificationType bogus_type = new VerificationType(Bogus), + top_type = bogus_type, + 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), + category2_2nd_check = new VerificationType(Category2_2ndQuery); + + 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_boolean() { + return (_data == Boolean); + } + + boolean is_byte() { + return (_data == Byte); + } + + boolean is_char() { + return (_data == Char); + } + + boolean is_short() { + return (_data == Short); + } + + boolean is_integer() { + return (_data == Integer); + } + + boolean is_long() { + return (_data == Long); + } + + boolean is_float() { + return (_data == Float); + } + + 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/classfile/impl/verifier/VerificationWrapper.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.java new file mode 100644 index 0000000000000..527afeab71f85 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl.verifier; + +import java.util.LinkedList; +import java.util.List; + +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.ClassModel; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.MethodModel; +import jdk.classfile.attribute.LocalVariableInfo; +import jdk.classfile.Attributes; +import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.CodeImpl; +import jdk.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); + } + + ConstantPoolWrapper constantPool() { + return cp; + } + + 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() : ((MemberRefEntry)e).nameAndType(); + } + + 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/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java new file mode 100644 index 0000000000000..da7687c6d464a --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java @@ -0,0 +1,1846 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl.verifier; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import jdk.classfile.ClassHierarchyResolver; +import jdk.classfile.ClassModel; +import jdk.classfile.util.ClassPrinter; +import jdk.classfile.Classfile; +import jdk.classfile.impl.ClassHierarchyImpl; +import jdk.classfile.impl.RawBytecodeHelper; +import static jdk.classfile.impl.RawBytecodeHelper.ILLEGAL; +import jdk.classfile.impl.verifier.VerificationWrapper.ConstantPoolWrapper; +import static jdk.classfile.impl.verifier.VerificationSignature.BasicType.*; +import jdk.classfile.impl.verifier.VerificationSignature.BasicType; +import static jdk.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_SLASH = '/', + JVM_SIGNATURE_DOT = '.', + JVM_SIGNATURE_SPECIAL = '<', + JVM_SIGNATURE_ENDSPECIAL = '>', + JVM_SIGNATURE_ARRAY = '[', + JVM_SIGNATURE_BYTE = 'B', + JVM_SIGNATURE_CHAR = 'C', + JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_ENDCLASS = ';', + JVM_SIGNATURE_ENUM = 'E', + 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 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; + } + + void set_sig_verif_types(ArrayList sig_verif_types) { + _sig_verif_types = 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.MethodWrapper method() { + return _method; + } + + 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) { + 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); + int bci = bcs.bci; + boolean is_assignable; + switch (bcs.rawCode) { + case Classfile.GETSTATIC: { + for (int i = 0; i < n; i++) { + current_frame.push_stack(field_type[i]); + } + break; + } + case Classfile.PUTSTATIC: { + for (int i = n - 1; i >= 0; i--) { + current_frame.pop_stack(field_type[i]); + } + break; + } + 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]); + } + break; + } + 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"); + } + break; + } + 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 subtype = false; + boolean have_imr_indirect = cp.tagAt(index) == JVM_CONSTANT_InterfaceMethodref; + 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 { + if (opcode == Classfile.INVOKESPECIAL) { + current_frame.pop_stack(current_type()); + } else if (opcode == Classfile.INVOKEVIRTUAL) { + VerificationType stack_object_type = + current_frame.pop_stack(ref_class_type); + if (current_type() != stack_object_type) { + String ref_class_name = + cp.classNameAt(cp.refClassIndexAt(index)); + } + } else { + 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.yamlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, _logger).printMethod(_method.m); + } + + 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/classfile/instruction/ArrayLoadInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ArrayLoadInstruction.java new file mode 100755 index 0000000000000..03b5da4aad7f0 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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 + * CodeElement.Kind#ARRAY_LOAD}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#ARRAY_LOAD} + */ + static ArrayLoadInstruction of(Opcode op) { + Util.checkKind(op, Kind.ARRAY_LOAD); + return new AbstractInstruction.UnboundArrayLoadInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java new file mode 100755 index 0000000000000..683d5fc5952cb --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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 + * CodeElement.Kind#ARRAY_STORE}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#ARRAY_STORE} + */ + static ArrayStoreInstruction of(Opcode op) { + Util.checkKind(op, Kind.ARRAY_STORE); + return new AbstractInstruction.UnboundArrayStoreInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java new file mode 100755 index 0000000000000..2deeb98f9c4c0 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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 CodeElement.Kind#BRANCH}. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#BRANCH} + */ + static BranchInstruction of(Opcode op, Label target) { + Util.checkKind(op, Kind.BRANCH); + return new AbstractInstruction.UnboundBranchInstruction(op, target); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java b/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java new file mode 100755 index 0000000000000..4b4578af39ade --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.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. + */ +package jdk.classfile.instruction; + +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Label; +import jdk.classfile.PseudoInstruction; +import jdk.classfile.attribute.CharacterRangeTableAttribute; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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.Key#PROCESS_DEBUG} option. + */ +sealed public interface CharacterRange extends PseudoInstruction + permits AbstractInstruction.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.classfile.Classfile#CRT_STATEMENT}, + * {@link jdk.classfile.Classfile#CRT_BLOCK}, + * {@link jdk.classfile.Classfile#CRT_ASSIGNMENT}, + * {@link jdk.classfile.Classfile#CRT_FLOW_CONTROLLER}, + * {@link jdk.classfile.Classfile#CRT_FLOW_TARGET}, + * {@link jdk.classfile.Classfile#CRT_INVOKE}, + * {@link jdk.classfile.Classfile#CRT_CREATE}, + * {@link jdk.classfile.Classfile#CRT_BRANCH_TRUE}, + * {@link jdk.classfile.Classfile#CRT_BRANCH_FALSE}. + * + * @@@ Need reference for interpretation of flags. + * + * @return the flags + */ + int flags(); +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java new file mode 100755 index 0000000000000..7810ba1189e44 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.instruction; + +import java.lang.constant.ConstantDesc; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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 + * aload_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 CodeElement.Kind#CONSTANT}. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +sealed public interface ConstantInstruction extends Instruction { + + /** + * {@return the constant value} + */ + ConstantDesc constantValue(); + + /** + * Models an "intrinsic constant" instruction (e.g., {@code + * aload_0}). + */ + sealed interface IntrinsicConstantInstruction extends ConstantInstruction + permits AbstractInstruction.UnboundIntrinsicConstantInstruction { + + } + + /** + * Models an "argument constant" instruction (e.g., {@code + * bipush}). + */ + sealed interface ArgumentConstantInstruction extends ConstantInstruction + permits AbstractInstruction.BoundArgumentConstantInstruction, + AbstractInstruction.UnboundArgumentConstantInstruction { + + @Override + Integer constantValue(); + } + + /** + * 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 an intrinsic constant instruction} + * + * @param op the opcode for the specific type of intrinsic constant instruction, + * which must be of kind {@link Kind#CONSTANT} + */ + static IntrinsicConstantInstruction ofIntrinsic(Opcode op) { + Util.checkKind(op, Kind.CONSTANT); + if (op.constantValue() == null && op != Opcode.ACONST_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 Kind#CONSTANT} + * @param value the constant value + */ + static ArgumentConstantInstruction ofArgument(Opcode op, int value) { + Util.checkKind(op, 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 Kind#CONSTANT} + * @param constant the constant value + */ + static LoadConstantInstruction ofLoad(Opcode op, LoadableConstantEntry constant) { + Util.checkKind(op, 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/classfile/instruction/ConvertInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ConvertInstruction.java new file mode 100755 index 0000000000000..05a37cb264518 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BytecodeHelpers; +import jdk.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 CodeElement.Kind#CONVERT}. Delivered as a {@link + * CodeElement} when traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#CONVERT} + */ + static ConvertInstruction of(Opcode op) { + Util.checkKind(op, Kind.CONVERT); + return new AbstractInstruction.UnboundConvertInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ExceptionCatch.java b/src/java.base/share/classes/jdk/classfile/instruction/ExceptionCatch.java new file mode 100755 index 0000000000000..c4e703c04579f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/ExceptionCatch.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.instruction; + +import java.util.Objects; +import java.util.Optional; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Label; +import jdk.classfile.PseudoInstruction; +import jdk.classfile.impl.AbstractInstruction; + +/** + * 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 + */ +sealed public interface ExceptionCatch extends PseudoInstruction + permits AbstractInstruction.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(); + + // @@@ Should we hide this? + /** + * {@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 null if this + * handler is unconditional + */ + static ExceptionCatch of(Label handler, Label tryStart, Label tryEnd, + ClassEntry catchTypeEntry) { + Objects.requireNonNull(catchTypeEntry); + return new AbstractInstruction.ExceptionCatchImpl(handler, tryStart, tryEnd, catchTypeEntry); + } + + /** + * {@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 AbstractInstruction.ExceptionCatchImpl(handler, tryStart, tryEnd, catchTypeEntry); + } + + /** + * {@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 AbstractInstruction.ExceptionCatchImpl(handler, tryStart, tryEnd, (ClassEntry) null); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java new file mode 100755 index 0000000000000..c9fef609fb878 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import java.lang.constant.ClassDesc; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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 + * CodeElement.Kind#FIELD_ACCESS}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#FIELD_ACCESS} + * @param field a constant pool entry describing the field + */ + static FieldInstruction of(Opcode op, FieldRefEntry field) { + Util.checkKind(op, 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 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.natEntry(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 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/classfile/instruction/IncrementInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.java new file mode 100755 index 0000000000000..f90c184e4ebd3 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.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 CodeElement.Kind#INCREMENT}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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/classfile/instruction/InvokeDynamicInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/InvokeDynamicInstruction.java new file mode 100755 index 0000000000000..fb6e7eb9235c7 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.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.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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}. + */ +sealed public 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/classfile/instruction/InvokeInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java new file mode 100755 index 0000000000000..26dbbc89ba120 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.instruction; + +import java.lang.constant.MethodTypeDesc; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodRefEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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 CodeElement.Kind#INVOKE}. Delivered as a + * {@link CodeElement} when traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#INVOKE} + * @param method a constant pool entry describing the method + */ + static InvokeInstruction of(Opcode op, MemberRefEntry method) { + Util.checkKind(op, 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 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.natEntry(name, type), isInterface); + } + + /** + * {@return an invocation instruction} + * + * @param op the opcode for the specific type of invocation instruction, + * which must be of kind {@link 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/classfile/instruction/LabelTarget.java b/src/java.base/share/classes/jdk/classfile/instruction/LabelTarget.java new file mode 100755 index 0000000000000..83e9f37c1ae86 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/LabelTarget.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.classfile.instruction; + +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Label; +import jdk.classfile.PseudoInstruction; +import jdk.classfile.attribute.CharacterRangeTableAttribute; +import jdk.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 + */ +sealed public interface LabelTarget extends PseudoInstruction + permits LabelImpl { + Label label(); +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LineNumber.java b/src/java.base/share/classes/jdk/classfile/instruction/LineNumber.java new file mode 100755 index 0000000000000..b78819d278d4b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/LineNumber.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.classfile.instruction; + +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.PseudoInstruction; +import jdk.classfile.attribute.CharacterRangeTableAttribute; +import jdk.classfile.attribute.LineNumberTableAttribute; +import jdk.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.Key#PROCESS_LINE_NUMBERS} option. + * + * @see PseudoInstruction + */ +sealed public interface LineNumber extends PseudoInstruction + permits LineNumberImpl { + int line(); +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java new file mode 100755 index 0000000000000..dee10d1c2bd96 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BytecodeHelpers; +import jdk.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 CodeElement.Kind#LOAD}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#LOAD} + * @param slot the local varaible slot to load from + */ + static LoadInstruction of(Opcode op, int slot) { + Util.checkKind(op, Kind.LOAD); + return new AbstractInstruction.UnboundLoadInstruction(op, slot); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java new file mode 100755 index 0000000000000..3943c5ff97572 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.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.classfile.instruction; + +import java.lang.constant.ClassDesc; + +import jdk.classfile.BufWriter; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Label; +import jdk.classfile.PseudoInstruction; +import jdk.classfile.attribute.LocalVariableTableAttribute; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BoundLocalVariable; + +/** + * 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.Key#PROCESS_DEBUG} option. + * + * @see PseudoInstruction + */ +sealed public interface LocalVariable extends PseudoInstruction + permits AbstractInstruction.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(); + + void writeTo(BufWriter buf, CodeBuilder labelContext); +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java new file mode 100755 index 0000000000000..edf6ce8025d82 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.instruction; + +import jdk.classfile.BufWriter; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Label; +import jdk.classfile.PseudoInstruction; +import jdk.classfile.Signature; +import jdk.classfile.attribute.LocalVariableTypeTableAttribute; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BoundLocalVariableType; + +/** + * 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.Key#PROCESS_DEBUG} option. + */ +sealed public interface LocalVariableType extends PseudoInstruction + permits AbstractInstruction.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(); + + void writeTo(BufWriter buf, CodeBuilder labelContext); +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LookupSwitchInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/LookupSwitchInstruction.java new file mode 100755 index 0000000000000..314c06cc276fd --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import java.util.List; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Label; +import jdk.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}. + */ +sealed public 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/classfile/instruction/MonitorInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/MonitorInstruction.java new file mode 100755 index 0000000000000..16cdb16bfbfaf --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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}. + */ +sealed public 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 Kind#MONITOR} + */ + static MonitorInstruction of(Opcode op) { + Util.checkKind(op, Kind.MONITOR); + return new AbstractInstruction.UnboundMonitorInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/NewMultiArrayInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/NewMultiArrayInstruction.java new file mode 100755 index 0000000000000..2ad31542d1b2d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Instruction; +import jdk.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}. + */ +sealed public 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/classfile/instruction/NewObjectInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/NewObjectInstruction.java new file mode 100755 index 0000000000000..9e1ffdb09a0c3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Instruction; +import jdk.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}. + */ +sealed public 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/classfile/instruction/NewPrimitiveArrayInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/NewPrimitiveArrayInstruction.java new file mode 100755 index 0000000000000..ef8447687addf --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.TypeKind; +import jdk.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}. + */ +sealed public 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/classfile/instruction/NewReferenceArrayInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/NewReferenceArrayInstruction.java new file mode 100755 index 0000000000000..b25a5354b411d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Instruction; +import jdk.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}. + */ +sealed public 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/classfile/instruction/NopInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/NopInstruction.java new file mode 100755 index 0000000000000..6f81998fb95f3 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.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}. + */ +sealed public 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/classfile/instruction/OperatorInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/OperatorInstruction.java new file mode 100755 index 0000000000000..3aef1e6157fb5 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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 CodeElement.Kind#OPERATOR}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#OPERATOR} + */ + static OperatorInstruction of(Opcode op) { + Util.checkKind(op, Kind.OPERATOR); + return new AbstractInstruction.UnboundOperatorInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java new file mode 100755 index 0000000000000..22f7b7c40dfac --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BytecodeHelpers; +import jdk.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 CodeElement.Kind#RETURN}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#RETURN} + */ + static ReturnInstruction of(Opcode op) { + Util.checkKind(op, Kind.RETURN); + return new AbstractInstruction.UnboundReturnInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java new file mode 100755 index 0000000000000..c76c910e94dcd --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.impl.AbstractInstruction; +import jdk.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 CodeElement.Kind#STACK}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#STACK} + */ + static StackInstruction of(Opcode op) { + Util.checkKind(op, Kind.STACK); + return new AbstractInstruction.UnboundStackInstruction(op); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java new file mode 100755 index 0000000000000..c27aec8d766f0 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.BytecodeHelpers; +import jdk.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 CodeElement.Kind#STORE}. Delivered as a {@link CodeElement} when + * traversing the elements of a {@link CodeModel}. + */ +sealed public 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 Kind#STORE} + * @param slot the local varaible slot to store to + */ + static StoreInstruction of(Opcode op, int slot) { + Util.checkKind(op, Kind.STORE); + return new AbstractInstruction.UnboundStoreInstruction(op, slot); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/instruction/SwitchCase.java b/src/java.base/share/classes/jdk/classfile/instruction/SwitchCase.java new file mode 100755 index 0000000000000..201a611e650d0 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.Label; +import jdk.classfile.impl.AbstractInstruction; + +/** + * Models a single case in a {@code lookupswitch} or {@code tableswitch} + * instruction. + * + * @see LookupSwitchInstruction + * @see TableSwitchInstruction + */ +sealed public 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/classfile/instruction/TableSwitchInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/TableSwitchInstruction.java new file mode 100755 index 0000000000000..853a1a628c7cf --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import java.util.List; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.Label; +import jdk.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}. + */ +sealed public 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/classfile/instruction/ThrowInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ThrowInstruction.java new file mode 100755 index 0000000000000..a45013cb0e49d --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.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}. + */ +sealed public 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/classfile/instruction/TypeCheckInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/TypeCheckInstruction.java new file mode 100755 index 0000000000000..83f7e677af5f0 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.classfile.instruction; + +import java.lang.constant.ClassDesc; + +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.Instruction; +import jdk.classfile.Opcode; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.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}. + */ +sealed public 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 Kind#TYPE_CHECK} + * @param type the type against which to check or cast + */ + static TypeCheckInstruction of(Opcode op, ClassEntry type) { + Util.checkKind(op, 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 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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(type)))); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java b/src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java new file mode 100644 index 0000000000000..ee1e5cffb749b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.jdktypes; + +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import static java.util.Map.entry; + +/** + * Represents a JVM access or module-related flag on a runtime member, + * such as a {@linkplain Class class}, {@linkplain Field field}, or + * {@linkplain Executable method}. + * + *

JVM access and module-related flags are related to, but distinct + * from Java language {@linkplain Modifier modifiers}. Some modifiers + * and access flags have a one-to-one correspondence, such as {@code + * public}. In other cases, some language-level modifiers do + * not have an access flag, such as {@code sealed} (JVMS + * {@jvms 4.7.31}) and some access flags have no corresponding + * modifier, such as {@linkplain #SYNTHETIC synthetic}. + * + *

The values for the constants representing the access and module + * flags are taken from sections of The Java Virtual Machine + * Specification including {@jvms 4.1} (class access and + * property modifiers), {@jvms 4.5} (field access and property flags), + * {@jvms 4.6} (method access and property flags), {@jvms 4.7.6} + * (nested class access and property flags), {@jvms 4.7.24} (method + * parameters), and {@jvms 4.7.25} (module flags and requires, + * exports, and opens flags). + * + *

The {@linkplain #mask() mask} values for the different access + * flags are not distinct. Flags are defined for different + * kinds of JVM structures and the same bit position has different + * meanings in different contexts. For example, {@code 0x0000_0040} + * indicates a {@link #VOLATILE volatile} field but a {@linkplain + * #BRIDGE bridge method}; {@code 0x0000_0080} indicates a {@link + * #TRANSIENT transient} field but a {@linkplain #VARARGS variable + * arity (varargs)} method. + * + *

The access flag constants are ordered by non-decreasing mask + * value; that is the mask value of a constant is greater than or + * equal to the mask value of an immediate neighbor to its (syntactic) + * left. If new constants are added, this property will be + * maintained. That implies new constants will not necessarily be + * added at the end of the existing list. + * + * @see java.lang.reflect.Modifier + * @see java.lang.module.ModuleDescriptor.Modifier + * @see java.lang.module.ModuleDescriptor.Requires.Modifier + * @see java.lang.module.ModuleDescriptor.Exports.Modifier + * @see java.lang.module.ModuleDescriptor.Opens.Modifier + * @see java.compiler/javax.lang.model.element.Modifier + * @since 19 + */ +@SuppressWarnings("doclint:reference") // cross-module link +public enum AccessFlag { + /** + * The access flag {@code ACC_PUBLIC}, corresponding to the source + * modifier {@link Modifier#PUBLIC public} with a mask value of + * {@code 0x0001}. + */ + PUBLIC(Modifier.PUBLIC, true, + Set.of(Location.CLASS, Location.FIELD, Location.METHOD, + Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_PRIVATE}, corresponding to the + * source modifier {@link Modifier#PRIVATE private} with a mask + * value of {@code 0x0002}. + */ + PRIVATE(Modifier.PRIVATE, true, + Set.of(Location.FIELD, Location.METHOD, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_PROTECTED}, corresponding to the + * source modifier {@link Modifier#PROTECTED protected} with a mask + * value of {@code 0x0004}. + */ + PROTECTED(Modifier.PROTECTED, true, + Set.of(Location.FIELD, Location.METHOD, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_STATIC}, corresponding to the source + * modifier {@link Modifier#STATIC static} with a mask value of + * {@code 0x0008}. + */ + STATIC(Modifier.STATIC, true, + Set.of(Location.FIELD, Location.METHOD, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_FINAL}, corresponding to the source + * modifier {@link Modifier#FINAL final} with a mask + * value of {@code 0x0010}. + */ + FINAL(Modifier.FINAL, true, + Set.of(Location.CLASS, Location.FIELD, Location.METHOD, + Location.INNER_CLASS, Location.METHOD_PARAMETER)), + + /** + * The access flag {@code ACC_SUPER} with a mask value of {@code + * 0x0020}. + */ + SUPER(0x0000_0020, false, Set.of(Location.CLASS)), + + /** + * The module flag {@code ACC_OPEN} with a mask value of {@code + * 0x0020}. + * @see java.lang.module.ModuleDescriptor#isOpen + */ + OPEN(0x0000_0020, false, Set.of(Location.MODULE)), + + /** + * The module requires flag {@code ACC_TRANSITIVE} with a mask + * value of {@code 0x0020}. + * @see java.lang.module.ModuleDescriptor.Requires.Modifier#TRANSITIVE + */ + TRANSITIVE(0x0000_0020, false, Set.of(Location.MODULE_REQUIRES)), + + /** + * The access flag {@code ACC_SYNCHRONIZED}, corresponding to the + * source modifier {@link Modifier#SYNCHRONIZED synchronized} with + * a mask value of {@code 0x0020}. + */ + SYNCHRONIZED(Modifier.SYNCHRONIZED, true, Set.of(Location.METHOD)), + + /** + * The module requires flag {@code ACC_STATIC_PHASE} with a mask + * value of {@code 0x0040}. + * @see java.lang.module.ModuleDescriptor.Requires.Modifier#STATIC + */ + STATIC_PHASE(0x0000_0040, false, Set.of(Location.MODULE_REQUIRES)), + + /** + * The access flag {@code ACC_VOLATILE}, corresponding to the + * source modifier {@link Modifier#VOLATILE volatile} with a mask + * value of {@code 0x0040}. + */ + VOLATILE(Modifier.VOLATILE, true, Set.of(Location.FIELD)), + + /** + * The access flag {@code ACC_BRIDGE} with a mask value of {@code + * 0x0040}. + * @see Method#isBridge() + */ + BRIDGE(0x0000_0040, false, Set.of(Location.METHOD)), + + /** + * The access flag {@code ACC_TRANSIENT}, corresponding to the + * source modifier {@link Modifier#TRANSIENT transient} with a + * mask value of {@code 0x0080}. + */ + TRANSIENT(Modifier.TRANSIENT, true, Set.of(Location.FIELD)), + + /** + * The access flag {@code ACC_VARARGS} with a mask value of {@code + * 0x0080}. + * @see Executable#isVarArgs() + */ + VARARGS(0x0000_0080, false, Set.of(Location.METHOD)), + + /** + * The access flag {@code ACC_NATIVE}, corresponding to the source + * modifier {@link Modifier#NATIVE native} with a mask value of {@code + * 0x0100}. + */ + NATIVE(Modifier.NATIVE, true, Set.of(Location.METHOD)), + + /** + * The access flag {@code ACC_INTERFACE} with a mask value of + * {@code 0x0200}. + * @see Class#isInterface() + */ + INTERFACE(Modifier.INTERFACE, false, + Set.of(Location.CLASS, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_ABSTRACT}, corresponding to the + * source modifier {@link Modifier#ABSTRACT abstract} with a mask + * value of {@code 0x0400}. + */ + ABSTRACT(Modifier.ABSTRACT, true, + Set.of(Location.CLASS, Location.METHOD, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_STRICT}, corresponding to the source + * modifier {@link Modifier#STRICT strictfp} with a mask value of + * {@code 0x0800}. + */ + STRICT(Modifier.STRICT, true, Set.of(Location.METHOD)), + + /** + * The access flag {@code ACC_SYNTHETIC} with a mask value of + * {@code 0x1000}. + * @see Class#isSynthetic() + * @see Executable#isSynthetic() + * @see java.lang.module.ModuleDescriptor.Modifier#SYNTHETIC + */ + SYNTHETIC(0x0000_1000, false, + Set.of(Location.CLASS, Location.FIELD, Location.METHOD, + Location.INNER_CLASS, Location.METHOD_PARAMETER, + Location.MODULE, Location.MODULE_REQUIRES, + Location.MODULE_EXPORTS, Location.MODULE_OPENS)), + + /** + * The access flag {@code ACC_ANNOTATION} with a mask value of + * {@code 0x2000}. + * @see Class#isAnnotation() + */ + ANNOTATION(0x0000_2000, false, + Set.of(Location.CLASS, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_ENUM} with a mask value of {@code + * 0x4000}. + * @see Class#isEnum() + */ + ENUM(0x0000_4000, false, + Set.of(Location.CLASS, Location.FIELD, Location.INNER_CLASS)), + + /** + * The access flag {@code ACC_MANDATED} with a mask value of + * {@code 0x8000}. + */ + MANDATED(0x0000_8000, false, + Set.of(Location.METHOD_PARAMETER, + Location.MODULE, Location.MODULE_REQUIRES, + Location.MODULE_EXPORTS, Location.MODULE_OPENS)), + + /** + * The access flag {@code ACC_MODULE} with a mask value of {@code + * 0x8000}. + */ + MODULE(0x0000_8000, false, Set.of(Location.CLASS)) + ; + + // May want to override toString for a different enum constant -> + // name mapping. + + private int mask; + private boolean sourceModifier; + + // Intentionally using Set rather than EnumSet since EnumSet is + // mutable. + private Set locations; + + private AccessFlag(int mask, boolean sourceModifier, Set locations) { + this.mask = mask; + this.sourceModifier = sourceModifier; + this.locations = locations; + } + + /** + * {@return the corresponding integer mask for the access flag} + */ + public int mask() { + return mask; + } + + /** + * {@return whether or not the flag has a directly corresponding + * modifier in the Java programming language} + */ + public boolean sourceModifier() { + return sourceModifier; + } + + /** + * {@return kinds of constructs the flag can be applied to} + */ + public Set locations() { + return locations; + } + + /** + * {@return a set of access flags for the given mask value + * appropriate for the location in question} + * + * @param mask bit mask of access flags + * @param location context to interpret mask value + * @throws IllegalArgumentException if the mask contains bit + * positions not support for the location in question + */ + public static Set maskToAccessFlags(int mask, Location location) { + Set result = java.util.EnumSet.noneOf(AccessFlag.class); + for (var accessFlag : LocationToFlags.locationToFlags.get(location)) { + int accessMask = accessFlag.mask(); + if ((mask & accessMask) != 0) { + result.add(accessFlag); + mask = mask & ~accessMask; + } + } + if (mask != 0) { + throw new IllegalArgumentException("Unmatched bit position 0x" + + Integer.toHexString(mask) + + " for location " + location); + } + return Collections.unmodifiableSet(result); + } + + /** + * A location within a class file where flags can be applied. + * + * Note that since these locations represent class file structures + * rather than language structures many language structures, such + * as constructors and interfaces, are not present. + * @since 19 + */ + public enum Location { + /** + * Class location. + * @jvms 4.1 The ClassFile Structure + */ + CLASS, + + /** + * Field location. + * @jvms 4.5 Fields + */ + FIELD, + + /** + * Method location. + * @jvms 4.6 Method + */ + METHOD, + + /** + * Inner class location. + * @jvms 4.7.6 The InnerClasses Attribute + */ + INNER_CLASS, + + /** + * Method parameter loccation. + * @jvms 4.7.24. The MethodParameters Attribute + */ + METHOD_PARAMETER, + + /** + * Module location + * @jvms 4.7.25. The Module Attribute + */ + MODULE, + + /** + * Module requires location + * @jvms 4.7.25. The Module Attribute + */ + MODULE_REQUIRES, + + /** + * Module exports location + * @jvms 4.7.25. The Module Attribute + */ + MODULE_EXPORTS, + + /** + * Module opens location + * @jvms 4.7.25. The Module Attribute + */ + MODULE_OPENS; + + } + + private static class LocationToFlags { + private static Map> locationToFlags = + Map.ofEntries(entry(Location.CLASS, + Set.of(PUBLIC, FINAL, SUPER, + INTERFACE, ABSTRACT, + SYNTHETIC, ANNOTATION, + ENUM, AccessFlag.MODULE)), + entry(Location.FIELD, + Set.of(PUBLIC, PRIVATE, PROTECTED, + STATIC, FINAL, VOLATILE, + TRANSIENT, SYNTHETIC, ENUM)), + entry(Location.METHOD, + Set.of(PUBLIC, PRIVATE, PROTECTED, + STATIC, FINAL, SYNCHRONIZED, + BRIDGE, VARARGS, NATIVE, + ABSTRACT, STRICT, SYNTHETIC)), + entry(Location.INNER_CLASS, + Set.of(PUBLIC, PRIVATE, PROTECTED, + STATIC, FINAL, INTERFACE, ABSTRACT, + SYNTHETIC, ANNOTATION, ENUM)), + entry(Location.METHOD_PARAMETER, + Set.of(FINAL, SYNTHETIC, MANDATED)), + entry(Location.MODULE, + Set.of(OPEN, SYNTHETIC, MANDATED)), + entry(Location.MODULE_REQUIRES, + Set.of(TRANSITIVE, STATIC_PHASE, SYNTHETIC, MANDATED)), + entry(Location.MODULE_EXPORTS, + Set.of(SYNTHETIC, MANDATED)), + entry(Location.MODULE_OPENS, + Set.of(SYNTHETIC, MANDATED))); + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/jdktypes/ModuleDesc.java b/src/java.base/share/classes/jdk/classfile/jdktypes/ModuleDesc.java new file mode 100644 index 0000000000000..c5315c186f2bf --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/jdktypes/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.classfile.jdktypes; + +import static java.util.Objects.requireNonNull; +import jdk.classfile.impl.ModuleDescImpl; +import static jdk.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/classfile/jdktypes/PackageDesc.java b/src/java.base/share/classes/jdk/classfile/jdktypes/PackageDesc.java new file mode 100644 index 0000000000000..d7dcad520f180 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/jdktypes/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.classfile.jdktypes; + +import static java.util.Objects.requireNonNull; +import jdk.classfile.impl.PackageDescImpl; +import static jdk.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/classfile/package-info.java b/src/java.base/share/classes/jdk/classfile/package-info.java new file mode 100755 index 0000000000000..3cce608a108ff --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/package-info.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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.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.classfile.ClassModel}; we + * convert bytes into a {@link jdk.classfile.ClassModel} with {@link + * jdk.classfile.Classfile#parse(byte[], jdk.classfile.Classfile.Option[])}: + *

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

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

+ * A {@link jdk.classfile.ClassModel} is an immutable description of a class + * file. It provides accessor methods to get at class metadata (e.g., {@link + * jdk.classfile.ClassModel#thisClass()}, {@link jdk.classfile.ClassModel#flags()}), + * as well as subordinate classfile entities ({@link jdk.classfile.ClassModel#fields()}, + * {@link jdk.classfile.ClassModel#attributes()}). A {@link + * jdk.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 class="jdk.classfile.snippets.PackageSnippets" region="enumerateFieldsMethods1"} + *

+ * When we enumerate the methods, we get a {@link jdk.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.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.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 class="jdk.classfile.snippets.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 class="jdk.classfile.snippets.PackageSnippets" region="gatherDependencies1"} + *

+ * This same query could alternately be processed as a stream pipeline over + * class elements: + *

+ * {@snippet class="jdk.classfile.snippets.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.classfile.ClassModel#methods()} accessor) or as a linear + * sequence of elements. (Elements can in turn also be models; a {@link + * jdk.classfile.FieldModel} is also an element of a class.) For each model type + * (e.g., {@link jdk.classfile.MethodModel}), there is a corresponding element + * type ({@link jdk.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.classfile.ClassModel} provides a lazily-inflated, + * read-only view of the constant pool via {@link jdk.classfile.ClassModel#constantPool()}. + * Descriptions of classfile content is often exposed in the form of various + * subtypes of {@link jdk.classfile.constantpool.PoolEntry}, such as {@link + * jdk.classfile.constantpool.ClassEntry} or {@link jdk.classfile.constantpool.Utf8Entry}. + *

+ * Constant pool entries are also exposed through models and elements; in the + * above traversal example, the {@link jdk.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.classfile.attribute.SignatureAttribute} is a {@link + * jdk.classfile.ClassElement}, {@link jdk.classfile.MethodElement}, and {@link + * jdk.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.classfile.CodeModel}.) + *

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

+ * Each standard attribute has an interface (in {@code jdk.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.classfile.attribute.SignatureAttribute} class, and + * provides accessors for {@link jdk.classfile.attribute.SignatureAttribute#signature()} + * as well as factories taking {@link jdk.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.classfile.AttributeMapper}. An {@code + * AttributeMapper} provides the {@link jdk.classfile.AttributeMapper#readAttribute(AttributedElement, ClassReader, int)} method for mapping from the classfile format + * to an attribute instance, and the {@link jdk.classfile.AttributeMapper#writeAttribute(jdk.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.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.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.classfile.Classfile.Option#attributeMapper(java.util.function.Function)}} + * classfile option. Implementations of custom attributes should extend {@link + * jdk.classfile.CustomAttribute}. Custom attributes will be delivered as + * elements in all of the contexts specified by {@link jdk.classfile.AttributeMapper#whereApplicable()}. + *

+ *

Options

+ *

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

+ *

    + *
  • {@link jdk.classfile.Classfile.Option#generateStackmap(boolean)} -- generate stackmaps (default is true)
  • + *
  • {@link jdk.classfile.Classfile.Option#processDebug(boolean)} -- processing of debug information, such as local variable metadata (default is true)
  • + *
  • {@link jdk.classfile.Classfile.Option#processLineNumbers(boolean)} -- processing of line numbers (default is true)
  • + *
  • {@link jdk.classfile.Classfile.Option#processUnknownAttributes(boolean)} -- processing of unrecognized attributes (default is true)
  • + *
  • {@link jdk.classfile.Classfile.Option#constantPoolSharing(boolean)}} -- share constant pool when transforming (default is true)
  • + *
  • {@link jdk.classfile.Classfile.Option#classHierarchyResolver(jdk.classfile.ClassHierarchyResolver)} -- specify a custom class hierarchy + * resolver used by stack map generation
  • + *
  • {@link jdk.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.classfile.ClassBuilder}, methods through + * {@link jdk.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 class="jdk.classfile.snippets.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.classfile.CodeBuilder#invokevirtual(java.lang.constant.ClassDesc, + * java.lang.String, java.lang.constant.MethodTypeDesc, boolean) CodeBuilder.invokevirtual}, {@link + * jdk.classfile.CodeBuilder#invokeInstruction(jdk.classfile.Opcode, + * java.lang.constant.ClassDesc, java.lang.String, java.lang.constant.MethodTypeDesc, + * boolean) CodeBuilder.invokeInstruction}, or + * {@link jdk.classfile.CodeBuilder#with(jdk.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.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.classfile.ClassModel}, build a new classfile that + * provides a {@link jdk.classfile.ClassBuilder}, iterate the elements of the + * original {@link jdk.classfile.ClassModel}, and pass through all of them to + * the builder except the methods we want to drop: + *

+ * {@snippet class="jdk.classfile.snippets.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 class="jdk.classfile.snippets.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.classfile.CodeElement}: + *

+ * {@snippet class="jdk.classfile.snippets.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 = ClassModel.of(bytes).transform(ct); + * } + *

+ * This is much more concise (and less error-prone) than the equivalent + * expressed by traversing the classfile structure directly: + *

+ * {@snippet class="jdk.classfile.snippets.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 class="jdk.classfile.snippets.PackageSnippets" region="instrumentCallsTransform"} + *

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

+ * {@snippet lang=java : + * byte[] newBytes = ClassModel.of(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.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.classfile.CodeBuilder#invokevirtual(java.lang.constant.ClassDesc, + * java.lang.String, java.lang.constant.MethodTypeDesc, boolean)}). + *

+ * Most symbolic information in elements is represented by constant pool entries + * (for example, the owner of a field is represented by a {@link + * jdk.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.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.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.classfile; \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java b/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java new file mode 100755 index 0000000000000..71856aaac48f7 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.snippets; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.HashSet; +import java.util.Set; + +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.FieldModel; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodModel; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.InvokeInstruction; + +import static java.util.stream.Collectors.toSet; + +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() + .filter(ce -> ce instanceof MethodModel) + .flatMap(ce -> ((MethodModel) ce).elementStream()) + .filter(me -> me instanceof CodeModel) + .flatMap(me -> ((CodeModel) me).elementStream()) + .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.descriptorSymbol(), + 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 + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java b/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java new file mode 100644 index 0000000000000..24915be77259d --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.transforms; + +import java.lang.constant.ClassDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.Map; +import java.util.function.Function; +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassSignature; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.FieldBuilder; +import jdk.classfile.FieldElement; +import jdk.classfile.FieldModel; +import jdk.classfile.FieldTransform; +import jdk.classfile.Interfaces; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodElement; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.InvokeDynamicInstruction; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewObjectInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import jdk.classfile.instruction.TypeCheckInstruction; +import jdk.classfile.MethodModel; +import jdk.classfile.MethodSignature; +import jdk.classfile.MethodTransform; +import jdk.classfile.Signature; +import jdk.classfile.Superclass; +import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; +import jdk.classfile.impl.Util; + +/** + * + */ +public sealed interface ClassRemapper { + + static ClassRemapper of(Map classMap) { + return of(desc -> classMap.getOrDefault(desc, desc)); + } + + static ClassRemapper of(Function mapFunction) { + return new ClassRemapperImpl(mapFunction); + } + + ClassDesc map(ClassDesc desc); + + ClassTransform classTransform(); + + FieldTransform fieldTransform(); + + MethodTransform methodTransform(); + + CodeTransform codeTransform(); + + default byte[] remapClass(ClassModel clm) { + return Classfile.build(map(clm.thisClass().asSymbol()), + clb -> clm.forEachElement(classTransform().resolve(clb).consumer())); + } + + final static class ClassRemapperImpl implements ClassRemapper { + + private final Function mapFunction; + + ClassRemapperImpl(Function mapFunction) { + this.mapFunction = mapFunction; + } + + @Override + public ClassTransform classTransform() { + return (ClassBuilder clb, ClassElement cle) -> { + switch (cle) { + case FieldModel fm -> + clb.withField(fm.fieldName().stringValue(), map(fm.descriptorSymbol()), fb -> fm.forEachElement(fieldTransform().resolve(fb).consumer())); + case MethodModel mm -> + clb.withMethod(mm.methodName().stringValue(), mapMethodDesc(mm.descriptorSymbol()), mm.flags().flagsMask(), mb -> mm.forEachElement(methodTransform().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()))); + default -> + clb.with(cle); + } + }; + } + + @Override + public FieldTransform fieldTransform() { + return (FieldBuilder fb, FieldElement fe) -> { + switch (fe) { + case SignatureAttribute sa -> + fb.with(SignatureAttribute.of(mapSignature(sa.asTypeSignature()))); + default -> + fb.with(fe); + } + }; + } + + @Override + public MethodTransform methodTransform() { + return (MethodBuilder mb, MethodElement me) -> { + switch (me) { + case CodeModel com -> + mb.transformCode(com, codeTransform()); + 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()))); + default -> + mb.with(me); + } + }; + } + + @Override + public CodeTransform codeTransform() { + 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 NewPrimitiveArrayInstruction c -> + cob.newPrimitiveArrayInstruction(c.typeKind()); + case NewReferenceArrayInstruction c -> + cob.anewarray(c.componentType().asSymbol()); + case NewMultiArrayInstruction c -> + cob.multianewarray(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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(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()); + 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(signature.typeParameters(), + mapSignature(signature.superclassSignature()), + signature.superinterfaceSignatures().stream().map(this::mapSignature).toArray(Signature.RefTypeSig[]::new)); + } + + MethodSignature mapMethodSignature(MethodSignature signature) { + return MethodSignature.of(signature.typeParameters(), + signature.arguments().stream().map(this::mapSignature).toList(), + mapSignature(signature.result()), + signature.throwableSignatures().stream().map(this::mapSignature).toList()); + } + + @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(this::mapSignature).toArray(Signature[]::new)); + default -> signature; + }; + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java b/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java new file mode 100644 index 0000000000000..b44641d54d87f --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.transforms; + +import java.lang.constant.MethodTypeDesc; +import java.util.Arrays; + +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.AccessFlags; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.Signature; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.LocalVariableType; + +/** + * + */ +public final class CodeLocalsShifter implements CodeTransform { + + private int[] locals = new int[0]; + private final int fixed; + private int next; + + public CodeLocalsShifter(AccessFlags methodFlags, MethodTypeDesc methodDescriptor) { + next = methodFlags.has(AccessFlag.STATIC) ? 0 : 1; + for (var param : methodDescriptor.parameterList()) + next += TypeKind.fromDescriptor(param.descriptorString()).slotSize(); + fixed = next; + } + + private CodeLocalsShifter(int fixed, int next) { + this.fixed = fixed; + this.next = next; + } + + public CodeLocalsShifter fork() { + return new CodeLocalsShifter(fixed, next); + } + + public int addLocal(TypeKind tk) { + int local = next; + next += tk.slotSize(); + return local; + } + + @Override + public void accept(CodeBuilder cob, CodeElement coe) { + switch (coe) { + case LoadInstruction li -> + cob.loadInstruction( + li.typeKind(), + shift(li.slot(), li.typeKind())); + case StoreInstruction si -> + cob.storeInstruction( + si.typeKind(), + shift(si.slot(), si.typeKind())); + case IncrementInstruction ii -> + cob.incrementInstruction( + shift(ii.slot(), TypeKind.IntType), + ii.constant()); + case LocalVariable lv -> + cob.localVariable( + shift(lv.slot(), TypeKind.fromDescriptor(lv.type().stringValue())), + lv.name(), + lv.type(), + lv.startScope(), + lv.endScope()); + case LocalVariableType lvt -> + cob.localVariableType( + shift(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(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 = addLocal(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/classfile/transforms/LabelsRemapper.java b/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java new file mode 100644 index 0000000000000..f09b20a51dc9b --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute 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.classfile.transforms; + +import java.util.IdentityHashMap; +import java.util.Map; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.SwitchCase; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.Label; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.LabelTarget; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; + +/** + * + */ +public final class LabelsRemapper { + + private LabelsRemapper() { + } + + public static CodeTransform remapLabels() { + var map = new IdentityHashMap(); + return (CodeBuilder cob, CodeElement coe) -> { + switch (coe) { + case BranchInstruction bi -> + cob.branchInstruction( + bi.opcode(), + remap(map, bi.target(), cob)); + case LookupSwitchInstruction lsi -> + cob.lookupSwitchInstruction( + remap(map, lsi.defaultTarget(), cob), + lsi.cases().stream().map(c -> + SwitchCase.of( + c.caseValue(), + remap(map, c.target(), cob))).toList()); + case TableSwitchInstruction tsi -> + cob.tableSwitchInstruction( + tsi.lowValue(), + tsi.highValue(), + remap(map, tsi.defaultTarget(), cob), + tsi.cases().stream().map(c -> + SwitchCase.of( + c.caseValue(), + remap(map, c.target(), cob))).toList()); + case LabelTarget lt -> + cob.labelBinding( + remap(map, lt.label(), cob)); + case ExceptionCatch ec -> + cob.exceptionCatch( + remap(map, ec.tryEnd(), cob), + remap(map, ec.tryEnd(), cob), + remap(map, ec.handler(), cob), + ec.catchType()); + case LocalVariable lv -> + cob.localVariable( + lv.slot(), + lv.name().stringValue(), + lv.typeSymbol(), + remap(map, lv.startScope(), cob), + remap(map, lv.endScope(), cob)); + case LocalVariableType lvt -> + cob.localVariableType( + lvt.slot(), + lvt.name().stringValue(), + lvt.signatureSymbol(), + remap(map, lvt.startScope(), cob), + remap(map, lvt.endScope(), cob)); + default -> + cob.with(coe); + } + }; + } + + private static Label remap(Map map, Label l, CodeBuilder cob) { + return map.computeIfAbsent(l, ll -> cob.newLabel()); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java b/src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java new file mode 100644 index 0000000000000..6f1b2fe8310d2 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/util/ClassPrinter.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.classfile.util; + +import java.util.function.Consumer; + +import jdk.classfile.ClassModel; +import jdk.classfile.MethodModel; +import jdk.classfile.impl.ClassPrinterImpl; + +/** + * + */ +public sealed interface ClassPrinter permits ClassPrinterImpl { + + public enum VerbosityLevel {MEMBERS_ONLY, CRITICAL_ATTRIBUTES, TRACE_ALL} + + void printClass(ClassModel classModel); + + void printMethod(MethodModel methodModel); + + static ClassPrinter jsonPrinter(VerbosityLevel verbosity, Consumer output) { + return new ClassPrinterImpl(ClassPrinterImpl.JSON, verbosity, output); + } + + static ClassPrinter xmlPrinter(VerbosityLevel verbosity, Consumer output) { + return new ClassPrinterImpl(ClassPrinterImpl.XML, verbosity, output); + } + + static ClassPrinter yamlPrinter(VerbosityLevel verbosity, Consumer output) { + return new ClassPrinterImpl(ClassPrinterImpl.YAML, verbosity, output); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java index 3437f32ceea5d..9749a6c536108 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java @@ -73,6 +73,7 @@ public class Preview { /** test flag: should all features be considered as preview features? */ private final boolean forcePreview; + private final boolean ignorePrevew; /** a mapping from classfile numbers to Java SE versions */ private final Map majorVersionToSource; @@ -105,6 +106,7 @@ public static Preview instance(Context context) { this.previewHandler = new MandatoryWarningHandler(log, source, lint.isEnabled(LintCategory.PREVIEW), true, "preview", LintCategory.PREVIEW); forcePreview = options.isSet("forcePreview"); + ignorePrevew = forcePreview || !options.isSet("ignorePreview"); majorVersionToSource = initMajorVersionToSourceMap(); } @@ -202,10 +204,10 @@ public boolean isEnabled() { */ public boolean isPreview(Feature feature) { return switch (feature) { - case CASE_NULL -> true; - case PATTERN_SWITCH -> true; - case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> true; - case RECORD_PATTERNS -> true; + case CASE_NULL -> ignorePrevew; + case PATTERN_SWITCH -> ignorePrevew; + case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> ignorePrevew; + case RECORD_PATTERNS -> ignorePrevew; //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing). //When real preview features will be added, this method can be implemented to return 'true' diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index e6c76b3fd2ac3..79500d79efb29 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -406,8 +406,8 @@ private void handleSwitch(JCTree tree, Type seltype = selector.type.hasTag(BOT) ? syms.objectType : selector.type; - Assert.check(preview.isEnabled()); - Assert.check(preview.usesPreview(env.toplevel.sourcefile)); +// Assert.check(preview.isEnabled()); +// Assert.check(preview.usesPreview(env.toplevel.sourcefile)); //rewrite pattern matching switches: //switch ($obj) { diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 4c921a34ce918..b6333dfbef590 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -43,6 +43,7 @@ tier1_part3 = \ :jdk_foreign \ 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..584f60b35bdec --- /dev/null +++ b/test/jdk/jdk/classfile/AccessFlagsTest.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. + */ + +/* + * @test + * @summary Testing Classfile AccessFlags. + * @run testng AccessFlagsTest + */ +import java.util.EnumSet; +import java.util.Random; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.AccessFlags; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import org.testng.annotations.DataProvider; + +public class AccessFlagsTest { + + @DataProvider(name = "accessFlagsContexts") + public static AccessFlag.Location[] accessFlagsContexts() { + return new AccessFlag.Location[] {AccessFlag.Location.CLASS, AccessFlag.Location.METHOD, AccessFlag.Location.FIELD}; + } + + @Test(dataProvider = "accessFlagsContexts") + public 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(dataProvider = "accessFlagsContexts", expectedExceptions = IllegalArgumentException.class) + public void testInvalidFlagsUse(AccessFlag.Location ctx) { + switch (ctx) { + case CLASS -> AccessFlags.ofClass(AccessFlag.values()); + case FIELD -> AccessFlags.ofField(AccessFlag.values()); + case METHOD -> AccessFlags.ofMethod(AccessFlag.values()); + } + } +} diff --git a/test/jdk/jdk/classfile/AdaptCodeTest.java b/test/jdk/jdk/classfile/AdaptCodeTest.java new file mode 100644 index 0000000000000..28be113053598 --- /dev/null +++ b/test/jdk/jdk/classfile/AdaptCodeTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng 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.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import helpers.ByteArrayClassLoader; +import helpers.TestUtil; +import helpers.Transforms; +import jdk.classfile.instruction.ConstantInstruction; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +@Test() +public 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"; + + public 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); + } + } + + @Test(dataProvider = "noExceptionClassfiles") + public 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); + } + } + + public void testSevenOfThirteenIterator() throws Exception { + ClassModel cm = Classfile.parse(testClassPath); + + var transform = ClassTransform.transformingMethodBodies((codeB, codeE) -> { + switch (codeE.codeKind()) { + case CONSTANT -> { + ConstantInstruction i = (ConstantInstruction) codeE; + 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); + } + + @DataProvider(name = "noExceptionClassfiles") + public Object[][] provide() { + return new Object[][] { { "modules/java.base/java/util/AbstractCollection.class" }, + { "modules/java.base/java/util/PriorityQueue.class" }, + { "modules/java.base/java/util/ArraysParallelSortHelpers.class" } + }; + } + + + + public 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..1060c96a6150d --- /dev/null +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng AdvancedTransformationsTest + */ +import helpers.ByteArrayClassLoader; +import java.util.Map; +import java.util.Set; +import jdk.classfile.ClassHierarchyResolver; +import jdk.classfile.Classfile; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.MethodModel; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.StackMapGenerator; +import jdk.classfile.transforms.ClassRemapper; +import jdk.classfile.transforms.CodeLocalsShifter; +import org.testng.annotations.Test; +import static org.testng.Assert.*; +import static helpers.TestUtil.assertEmpty; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.util.LinkedList; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import jdk.classfile.Attributes; +import jdk.classfile.ClassModel; +import jdk.classfile.CodeElement; +import jdk.classfile.FieldModel; +import jdk.classfile.Signature; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.RawBytecodeHelper; +import jdk.classfile.impl.Util; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.transforms.LabelsRemapper; + +public class AdvancedTransformationsTest { + + @Test + public 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 = new CodeLocalsShifter(mm.flags(), mm.descriptorSymbol()); + shifter.addLocal(TypeKind.ReferenceType); + shifter.addLocal(TypeKind.LongType); + shifter.addLocal(TypeKind.IntType); + shifter.addLocal(TypeKind.DoubleType); + mb.transformCode(com, shifter); + } + mb.with(me); + }); + } + else + clb.with(cle); + })); + remapped.verify(null); //System.out::print); + } + } + + @Test + public void testRemapClass() throws Exception { + var map = Map.of( + ConstantDescs.CD_List, ClassDesc.of("remapped.List"), + ClassDesc.ofDescriptor(AbstractInstruction.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.descriptorSymbol(), sa.asTypeSignature()))); + remapped.methods().forEach(m -> m.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> { + var md = m.descriptorSymbol(); + var ms = sa.asMethodSignature(); + verifySignature(md.returnType(), ms.result()); + var args = ms.arguments(); + assertEquals(md.parameterCount(), args.size()); + for (int i=0; i + 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 + public 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().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 Classfile.build(target.thisClass().asSymbol(), clb -> { + target.forEachElement(cle -> { + CodeModel instrumentorCodeModel; + if (cle instanceof MethodModel mm && ((instrumentorCodeModel = instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue())) != null)) { + clb.withMethod(mm.methodName().stringValue(), mm.descriptorSymbol(), mm.flags().flagsMask(), + mb -> mm.forEachElement(me -> { + if (me instanceof CodeModel targetCodeModel) { + //instrumented methods are merged + var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.descriptorSymbol()); + var instrumentorCodeRemapperAndShifter = + instrumentorClassRemapper.codeTransform() + .andThen(instrumentorLocalsShifter); + CodeTransform invokeInterceptor + = (codeBuilder, instrumentorCodeElement) -> { + if (instrumentorCodeElement instanceof InvokeInstruction inv + && instrumentor.thisClass().asInternalName().equals(inv.owner().asInternalName()) + && mm.methodName().stringValue().equals(inv.name().stringValue()) + && mm.methodType().stringValue().equals(inv.type().stringValue())) { + //store stacked arguments (in reverse order) + record Arg(TypeKind tk, int slot) {} + var storeStack = new LinkedList(); + int slot = 0; + if (!mm.flags().has(AccessFlag.STATIC)) { + storeStack.add(new Arg(TypeKind.ReferenceType, slot++)); + } + var it = Util.parameterTypes(mm.methodType().stringValue()); + while (it.hasNext()) { + var tk = TypeKind.fromDescriptor(it.next()); + storeStack.add(new Arg(tk, slot)); + slot += tk.slotSize(); + } + while (!storeStack.isEmpty()) { + var arg = storeStack.removeLast(); + codeBuilder.storeInstruction(arg.tk, arg.slot); + } + var endLabel = codeBuilder.newLabel(); + //inlined target locals must be shifted based on the actual instrumentor locals shifter next free slot, relabeled and returns must be replaced with goto + var sequenceTransform = + instrumentorLocalsShifter.fork() + .andThen(LabelsRemapper.remapLabels()) + .andThen((innerBuilder, shiftedRelabeledTargetCode) -> { + if (shiftedRelabeledTargetCode.codeKind() == CodeElement.Kind.RETURN) { + innerBuilder.goto_w(endLabel); + } + else + innerBuilder.with(shiftedRelabeledTargetCode); + }) + .andThen(CodeTransform.endHandler(b -> codeBuilder.labelBinding(endLabel))); + codeBuilder.transform(targetCodeModel, sequenceTransform); + } + else + codeBuilder.with(instrumentorCodeElement); + }; + mb.transformCode(instrumentorCodeModel, + invokeInterceptor.andThen(instrumentorCodeRemapperAndShifter)); + } + else { + mb.with(me); + } + })); + } + else { + clb.with(cle); + } + }); + var remapperConsumer = instrumentorClassRemapper.classTransform().resolve(clb).consumer(); + instrumentor.forEachElement(cle -> { + //remaining instrumentor fields and methods are remapped and moved + if (cle instanceof FieldModel fm && !targetFieldNames.contains(fm.fieldName().stringValue())) { + remapperConsumer.accept(cle); + } + else if (cle instanceof MethodModel mm && !"".equals(mm.methodName().stringValue()) && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue())) { + remapperConsumer.accept(cle); + } + }); + }); + } +} diff --git a/test/jdk/jdk/classfile/AnnotationModelTest.java b/test/jdk/jdk/classfile/AnnotationModelTest.java new file mode 100644 index 0000000000000..de9f95cd9ca0b --- /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 testng AnnotationModelTest + */ +import jdk.classfile.Classfile; +import jdk.classfile.Attributes; +import org.testng.annotations.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.testng.Assert.*; + +public 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 + public 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..e75e822548c86 --- /dev/null +++ b/test/jdk/jdk/classfile/AnnotationTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng 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.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.*; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import org.testng.annotations.Test; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import jdk.classfile.impl.DirectClassBuilder; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.fail; + +/** + * AnnotationTest + */ +@Test +public 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())); + } + + public 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())); + } + + public 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..b76adeb6c6962 --- /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 testng ArrayTest + */ +import jdk.classfile.Classfile; +import jdk.classfile.ClassModel; +import jdk.classfile.CodeElement; +import jdk.classfile.MethodModel; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; + +public class ArrayTest { + static final String testClassName = "ArrayTest$TestClass"; + static final Path testClassPath = Paths.get(URI.create(ArrayTest.class.getResource(testClassName + ".class").toString())); + + + @Test + public 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.codeKind() == CodeElement.Kind.NEW_REF_ARRAY + || im.codeKind() == CodeElement.Kind.NEW_PRIMITIVE_ARRAY + || im.codeKind() == CodeElement.Kind.NEW_MULTI_ARRAY) { + 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..82fb6b785084b --- /dev/null +++ b/test/jdk/jdk/classfile/BSMTest.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. + */ + +/* + * @test + * @summary Testing Classfile bootstrap methods. + * @run testng 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.classfile.*; +import helpers.ByteArrayClassLoader; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.LoadableConstantEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import org.testng.annotations.Test; + +import static java.lang.constant.ConstantDescs.CD_String; +import static org.testng.Assert.assertEquals; + +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() + public 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.codeKind()) { + case CONSTANT -> { + 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.natEntry("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..6a2beb75f3169 --- /dev/null +++ b/test/jdk/jdk/classfile/BasicBlockTest.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. + */ + +/* + * @test + * @summary Testing Classfile complex basic blocks affecting SM generator. + * @run testng BasicBlockTest + */ +import java.io.InputStream; +import java.io.IOException; + +import jdk.classfile.Classfile; +import org.testng.annotations.Test; + +/** + * + */ +public 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 + public 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..0f952df0dbfc7 --- /dev/null +++ b/test/jdk/jdk/classfile/BuilderBlockTest.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. + */ + +/* + * @test + * @summary Testing Classfile builder blocks. + * @run testng 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.classfile.AccessFlags; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.Classfile; +import jdk.classfile.Label; +import jdk.classfile.TypeKind; +import jdk.classfile.impl.LabelImpl; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +/** + * BuilderBlockTest + */ +@Test +public class BuilderBlockTest { + + static final String testClassName = "AdaptCodeTest$TestClass"; + static final Path testClassPath = Paths.get("target/test-classes/" + testClassName + ".class"); + + public 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]).getContextInfo(), 0); + assertEquals(((LabelImpl) startEnd[1]).getContextInfo(), -1); + })); + }); + + assertEquals(((LabelImpl) startEnd[0]).getContextInfo(), 0); + assertEquals(((LabelImpl) startEnd[1]).getContextInfo(), 1); + } + + public 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]).getContextInfo(), 0); + assertEquals(((LabelImpl) startEnd[1]).getContextInfo(), 3); + assertEquals(((LabelImpl) startEnd[2]).getContextInfo(), 1); + assertEquals(((LabelImpl) startEnd[3]).getContextInfo(), 2); + } + + public void testIfThen() 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((Integer) fooMethod.invoke(null, 3), (Integer) 1); + assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); + + } + + public void testIfThenElse() 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((Integer) fooMethod.invoke(null, 3), (Integer) 1); + assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); + + } + + public void testIfThenElse2() 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((Integer) fooMethod.invoke(null, 3), (Integer) 1); + assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); + + } +} diff --git a/test/jdk/jdk/classfile/BuilderParamTest.java b/test/jdk/jdk/classfile/BuilderParamTest.java new file mode 100644 index 0000000000000..484f5110ddb0c --- /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 testng BuilderParamTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.classfile.Classfile; +import org.testng.annotations.Test; + +import static java.lang.constant.ConstantDescs.CD_void; +import static jdk.classfile.Classfile.ACC_STATIC; +import static org.testng.Assert.assertEquals; + +/** + * BuilderParamTest + */ +@Test +public class BuilderParamTest { + public 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/ClassHierarchyInfoTest.java b/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java new file mode 100644 index 0000000000000..ca855c10670e7 --- /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 testng 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.classfile.ClassHierarchyResolver; + +import jdk.classfile.Classfile; +import jdk.classfile.CodeModel; +import jdk.classfile.MethodModel; +import jdk.classfile.impl.Util; +import org.testng.annotations.Test; + +/** + * + */ +public class ClassHierarchyInfoTest { + + @Test(expectedExceptions = VerifyError.class) + public void testProduceInvalidStackMaps() throws Exception { + transformAndVerify(className -> null); + } + + @Test + public 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(expectedExceptions = VerifyError.class) + public void testBreakDefaulClassHierarchy() throws Exception { + 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 + public 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); + } + })); + } + + public 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..b1ca7d572a160 --- /dev/null +++ b/test/jdk/jdk/classfile/ClassPrinterTest.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng ClassPrinterTest + */ +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.util.ClassPrinter; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + +public 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")) + .withField("f", ConstantDescs.CD_String, Classfile.ACC_PRIVATE) + .withMethod("m", MethodTypeDesc.of(ConstantDescs.CD_Void, ConstantDescs.CD_boolean, ConstantDescs.CD_Throwable), Classfile.ACC_PROTECTED, mb -> mb.withCode(cob -> { + cob.iload(1); + cob.ifThen(thb -> thb.aload(2).athrow()); + cob.return_(); + })) + )); + } + + @Test + public void testPrintYamlTraceAll() throws IOException { + var out = new StringBuilder(); + ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); + assertEquals(out.toString(), +""" + - class name: 'Foo' + version: '61.0' + flags: [PUBLIC] + superclass: 'Boo' + interfaces: ['Phee', 'Phoo'] + attributes: [SourceFile] + constant pool: + 1: [CONSTANT_Utf8, 'Foo'] + 2: [CONSTANT_Class, {name index: 1, name: 'Foo'}] + 3: [CONSTANT_Utf8, 'Boo'] + 4: [CONSTANT_Class, {name index: 3, name: 'Boo'}] + 5: [CONSTANT_Utf8, 'f'] + 6: [CONSTANT_Utf8, 'Ljava/lang/String;'] + 7: [CONSTANT_Utf8, 'm'] + 8: [CONSTANT_Utf8, '(ZLjava/lang/Throwable;)Ljava/lang/Void;'] + 9: [CONSTANT_Utf8, 'Phee'] + 10: [CONSTANT_Class, {name index: 9, name: 'Phee'}] + 11: [CONSTANT_Utf8, 'Phoo'] + 12: [CONSTANT_Class, {name index: 11, name: 'Phoo'}] + 13: [CONSTANT_Utf8, 'Code'] + 14: [CONSTANT_Utf8, 'StackMapTable'] + 15: [CONSTANT_Utf8, 'SourceFile'] + 16: [CONSTANT_Utf8, 'Foo.java'] + source: 'Foo.java' + fields: + - field name: 'f' + flags: [PRIVATE] + descriptor: 'Ljava/lang/String;' + attributes: [] + methods: + - method name: 'm' + flags: [PROTECTED] + descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' + attributes: [Code] + code: + max stack: 1 + max locals: 3 + attributes: [StackMapTable] + stack map frames: + 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 0: [ILOAD_1, {slot: 1}] + 1: [IFEQ, {target: 6}] + 4: [ALOAD_2, {slot: 2}] + 5: [ATHROW] + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 6: [RETURN] +"""); + } + + @Test + public void testPrintYamlCriticalAttributes() throws IOException { + var out = new StringBuilder(); + ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); + assertEquals(out.toString(), +""" + - class name: 'Foo' + version: '61.0' + flags: [PUBLIC] + superclass: 'Boo' + interfaces: ['Phee', 'Phoo'] + attributes: [SourceFile] + fields: + - field name: 'f' + flags: [PRIVATE] + descriptor: 'Ljava/lang/String;' + attributes: [] + methods: + - method name: 'm' + flags: [PROTECTED] + descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' + attributes: [Code] + code: + max stack: 1 + max locals: 3 + attributes: [StackMapTable] + stack map frames: + 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 0: [ILOAD_1, {slot: 1}] + 1: [IFEQ, {target: 6}] + 4: [ALOAD_2, {slot: 2}] + 5: [ATHROW] + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 6: [RETURN] +"""); + } + + @Test + public void testPrintYamlMembersOnly() throws IOException { + var out = new StringBuilder(); + ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); + assertEquals(out.toString(), +""" + - class name: 'Foo' + version: '61.0' + flags: [PUBLIC] + superclass: 'Boo' + interfaces: ['Phee', 'Phoo'] + attributes: [SourceFile] + fields: + - field name: 'f' + flags: [PRIVATE] + descriptor: 'Ljava/lang/String;' + attributes: [] + methods: + - method name: 'm' + flags: [PROTECTED] + descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' + attributes: [Code] +"""); + } + + @Test + public void testPrintJsonTraceAll() throws IOException { + var out = new StringBuilder(); + ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); + assertEquals(out.toString().trim(), +""" +{ "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile"], + "constant pool": { + "1": ["CONSTANT_Utf8", "Foo"], + "2": ["CONSTANT_Class", { "name index:": 1, "name:": "Foo" }], + "3": ["CONSTANT_Utf8", "Boo"], + "4": ["CONSTANT_Class", { "name index:": 3, "name:": "Boo" }], + "5": ["CONSTANT_Utf8", "f"], + "6": ["CONSTANT_Utf8", "Ljava/lang/String;"], + "7": ["CONSTANT_Utf8", "m"], + "8": ["CONSTANT_Utf8", "(ZLjava/lang/Throwable;)Ljava/lang/Void;"], + "9": ["CONSTANT_Utf8", "Phee"], + "10": ["CONSTANT_Class", { "name index:": 9, "name:": "Phee" }], + "11": ["CONSTANT_Utf8", "Phoo"], + "12": ["CONSTANT_Class", { "name index:": 11, "name:": "Phoo" }], + "13": ["CONSTANT_Utf8", "Code"], + "14": ["CONSTANT_Utf8", "StackMapTable"], + "15": ["CONSTANT_Utf8", "SourceFile"], + "16": ["CONSTANT_Utf8", "Foo.java"] }, + "source": "Foo.java", + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "descriptor": "Ljava/lang/String;", + "attributes": [] }], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["Code"], + "code": { + "max stack": 1, + "max locals": 3, + "attributes": ["StackMapTable"], + "stack map frames": { + "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, + "0": ["ILOAD_1", { "slot": 1 }], + "1": ["IFEQ", { "target": 6 }], + "4": ["ALOAD_2", { "slot": 2 }], + "5": ["ATHROW"], + "6": ["RETURN"] } }] + } +""".trim()); + } + + @Test + public void testPrintJsonCriticalAttributes() throws IOException { + var out = new StringBuilder(); + ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); + assertEquals(out.toString().trim(), +""" + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "descriptor": "Ljava/lang/String;", + "attributes": [] }], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["Code"], + "code": { + "max stack": 1, + "max locals": 3, + "attributes": ["StackMapTable"], + "stack map frames": { + "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, + "0": ["ILOAD_1", { "slot": 1 }], + "1": ["IFEQ", { "target": 6 }], + "4": ["ALOAD_2", { "slot": 2 }], + "5": ["ATHROW"], + "6": ["RETURN"] } }] + } +""".trim()); + } + + @Test + public void testPrintJsonMembersOnly() throws IOException { + var out = new StringBuilder(); + ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); + assertEquals(out.toString().trim(), +""" + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "descriptor": "Ljava/lang/String;", + "attributes": [] }], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["Code"] }] + } +""".trim()); + } + + @Test + public void testPrintXmlTraceAll() throws IOException { + var out = new StringBuilder(); + ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); + assertEquals(out.toString(), +""" + + + + <:>1Foo + <:>2 + <:>3Boo + <:>4 + <:>5f + <:>6Ljava/lang/String; + <:>7m + <:>8(ZLjava/lang/Throwable;)Ljava/lang/Void; + <:>9Phee + <:>10 + <:>11Phoo + <:>12 + <:>13Code + <:>14StackMapTable + <:>15SourceFile + <:>16Foo.java + Foo.java + + + + + + + <:>6 + + <:>0 + <:>1 + <:>4 + <:>5 + + <:>6 +"""); + } + + @Test + public void testPrintXmlCriticalAttributes() throws IOException { + var out = new StringBuilder(); + ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); + assertEquals(out.toString(), +""" + + + + + + + + + <:>6 + + <:>0 + <:>1 + <:>4 + <:>5 + + <:>6 +"""); + } + + @Test + public void testPrintXmlMembersOnly() throws IOException { + var out = new StringBuilder(); + ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); + assertEquals(out.toString(), +""" + + + + + + +"""); + } +} diff --git a/test/jdk/jdk/classfile/ConstantPoolCopyTest.java b/test/jdk/jdk/classfile/ConstantPoolCopyTest.java new file mode 100644 index 0000000000000..f84093b8a6a9f --- /dev/null +++ b/test/jdk/jdk/classfile/ConstantPoolCopyTest.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng ConstantPoolCopyTest + */ +import jdk.classfile.ClassModel; +import jdk.classfile.ClassReader; +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.DynamicConstantPoolEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.MethodRefEntry; +import jdk.classfile.constantpool.MethodTypeEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.SplitConstantPool; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.BootstrapMethodEntry; +import jdk.classfile.constantpool.ConstantPool; +import org.testng.annotations.DataProvider; +import org.testng.annotations.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 java.nio.file.Path; +import java.util.stream.Stream; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class ConstantPoolCopyTest { + private ClassModel[] classes; + + ConstantPoolCopyTest() throws Exception { + classes = rtJarToClassLow(FileSystems.getFileSystem(URI.create("jrt:/"))); + } + + private 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); + } + } + + @DataProvider(name = "classes") + public Object[] dataProvider() { + return classes; + } + + @Test(dataProvider = "classes") + public 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.poolEntries(); + } + + // Bootstrap methods + assertEquals(cp.bootstrapMethodCount(), cp2.bootstrapMethodCount()); + for (int i = 0; i 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()) { + ClassFile cf = ClassFile.read(new ByteArrayInputStream(transformed)); + com.sun.tools.classfile.ConstantPool pool = cf.constant_pool; + System.out.println(String.format("Incremental dups in file %s (%s): %s / %s", path, m, baseDups, newDups)); + for (Map.Entry entry : newDups.entrySet()) { + System.out.println(String.format(" %d: %s", entry.getKey(), pool.get(entry.getKey()))); + System.out.println(String.format(" %d: %s", entry.getValue(), pool.get(entry.getValue()))); + } + } + compareCp(bytes, transformed); + break; + case UNSHARED_1, UNSHARED_2, UNSHARED_3: + if (!newDups.isEmpty()) { + ClassFile cf = ClassFile.read(new ByteArrayInputStream(transformed)); + com.sun.tools.classfile.ConstantPool pool = cf.constant_pool; + System.out.println(String.format("Dups in file %s (%s): %s", path, m, newDups)); + + for (Map.Entry entry : newDups.entrySet()) { + System.out.println(String.format(" %d: %s", entry.getKey(), pool.get(entry.getKey()))); + System.out.println(String.format(" %d: %s", entry.getValue(), pool.get(entry.getValue()))); + } + } + 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)); + } + } + + @Test + public void testReadAndTransform() throws IOException, ConstantPoolException { + var classModel = Classfile.parse(bytes); + var classFile = ClassFile.read(new ByteArrayInputStream(bytes)); + assertEqualsDeep(ClassRecord.ofClassModel(classModel), ClassRecord.ofClassFile(classFile), + "ClassModel (actual) vs ClassFile (expected)"); + + assertEqualsDeep(ClassRecord.ofStreamingElements(classModel), ClassRecord.ofClassFile(classFile), + "StreamingElements (actual) vs ClassFile (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) { + try { + ClassFile c1 = ClassFile.read(new ByteArrayInputStream(orig)); + ClassFile c2 = ClassFile.read(new ByteArrayInputStream(transformed)); + + List entryList1 = cpiEntries(c1.constant_pool); + List entryList2 = cpiEntries(c2.constant_pool); + + for (int i=0; i cpiEntries(ConstantPool pool) { + List entryList = new ArrayList<>(); + entryList.add(null); + for (com.sun.tools.classfile.ConstantPool.CPInfo e : pool.entries()) { + entryList.add(e); + if (e.getTag() == CONSTANT_Double || e.getTag() == CONSTANT_Long) + entryList.add(null); + } + return entryList; + } + + private static Map findDups(byte[] bytes) { + try { + Map dups = new HashMap<>(); + ClassFile cf = ClassFile.read(new ByteArrayInputStream(bytes)); + com.sun.tools.classfile.ConstantPool pool = cf.constant_pool; + + List entryList = cpiEntries(pool); + assertEquals(entryList.size(), pool.size()); + + Set entryStrings = new HashSet<>(); + for (int i=1; i { + 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") + .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) + .toList(); + + Assert.assertEquals(opcodes.size(), 4); + Assert.assertEquals(opcodes.get(0).opcode(), LDC); + Assert.assertEquals(opcodes.get(1).opcode(), LDC_W); + Assert.assertEquals(opcodes.get(2).opcode(), LDC); + Assert.assertEquals(opcodes.get(3).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..19b4a9356fdd6 --- /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 testng LimitsTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import jdk.classfile.Classfile; +import org.testng.annotations.Test; + +public class LimitsTest { + + @Test + public 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..19144709b727a --- /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 testng 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.classfile.AccessFlags; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import helpers.ByteArrayClassLoader; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.impl.DirectClassBuilder; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class LowAdaptTest { + + static final String test = "LowAdaptTest$TestClass"; + + @Test + public 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..cac7fae12300e --- /dev/null +++ b/test/jdk/jdk/classfile/LowJCovAttributeTest.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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng LowJCovAttributeTest + */ +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.CodeModel; +import jdk.classfile.MethodModel; +import jdk.classfile.Attributes; +import jdk.classfile.attribute.*; +import jdk.classfile.constantpool.Utf8Entry; +import org.testng.annotations.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +/** + * LowJCovAttributeTest + */ +@Test +public 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; + + public LowJCovAttributeTest() throws IOException { + this.path = Paths.get(URI.create(LowJCovAttributeTest.class.getResource(TEST_FILE).toString())); + this.classLow = Classfile.parse(path); + } + + @Test + public 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 +// public 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..a84f34abd91f0 --- /dev/null +++ b/test/jdk/jdk/classfile/LowModuleTest.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng LowModuleTest + */ +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystems; +import java.nio.file.Files; + +import java.lang.reflect.Method; +import java.nio.file.Path; + +import jdk.classfile.Attribute; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.Attributes; +import jdk.classfile.attribute.*; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.Utf8Entry; +import org.testng.ITest; +import org.testng.annotations.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +/** + * LowModuleTest + */ +@Test +public class LowModuleTest implements ITest { + + private static final boolean VERBOSE = false; + + @DataProvider(name = "corpus") + public static Object[] provide() 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(); + } + + private String testMethod = ""; + private final Path path; + private final ClassModel classLow; + + @Factory(dataProvider = "corpus") + public LowModuleTest(Path path) throws IOException { + this.path = path; + this.classLow = Classfile.parse(path); + } + + @BeforeMethod + public void handleTestMethodName(Method method) { + testMethod = method.getName(); + } + + @Override + public String getTestName() { + return testMethod + "[" + path.toString() + "]"; + } + + @Test + public void testRead() { + try { + testRead0(); + } catch(Exception ex) { + System.err.printf("%nFAIL %s - %s%n", path, ex); + ex.printStackTrace(System.err); + throw ex; + } + } + + private void testRead0() { + for (Attribute attr : classLow.attributes()) { + printf("%nCHECK %s%n", getTestName()); + 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..1ce7b50ac9b8e --- /dev/null +++ b/test/jdk/jdk/classfile/LvtTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng LvtTest + */ +import com.sun.tools.classfile.*; +import helpers.ClassRecord; +import helpers.Transforms; +import jdk.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.classfile.AccessFlags; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; +import jdk.classfile.jdktypes.AccessFlag; +import org.testng.Assert; +import org.testng.annotations.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.classfile.Opcode.*; +import static jdk.classfile.Opcode.INVOKEVIRTUAL; +import static jdk.classfile.TypeKind.VoidType; +import static org.testng.Assert.*; + +public 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() + public 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() + public void buildLVTEntries() throws Exception { + ClassModel c = Classfile.parse(fileBytes); + ClassFile cf = ClassFile.read(new ByteArrayInputStream(fileBytes)); + + // Compare untransformed model and view from ClassFile + ClassRecord orig = ClassRecord.ofClassModel(c); + ClassRecord origClassFile = ClassRecord.ofClassFile(cf); + ClassRecord.assertEqualsDeep(orig, origClassFile); + + // Compare transformed model and original with CodeBuilder filter + byte[] newClass = c.transform(Transforms.threeLevelNoop); + 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); + + // ClassFile vs transformed + origClassFile = ClassRecord.ofClassFile(cf, ClassRecord.CompatibilityFilter.By_ClassBuilder); + ClassRecord.assertEqualsDeep(transformed, origClassFile); + } + + @Test() + public 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 + })); + }); + + ClassFile cf = ClassFile.read(new ByteArrayInputStream(bytes)); + Method main = cf.methods[1]; + LocalVariableTable_attribute lvt = (LocalVariableTable_attribute) ((Code_attribute) main.attributes.get("Code")).attributes.get("LocalVariableTable"); + LocalVariableTable_attribute.Entry[] entries = lvt.local_variable_table; + + Assert.assertEquals(entries.length, 3); + + List lvs = Arrays.asList(entries); + List expected = List.of( + ExpectedLvRecord.of(1, "res", "I", 2, 25, cf.constant_pool), + ExpectedLvRecord.of(2, "i", "I", 4, 23, cf.constant_pool), + ExpectedLvRecord.of(0, "this", "LMyClass;", 0, 28, cf.constant_pool)); + + // Exploits non-symmetric "equals" in ExpectedLvRecord + assertTrue(expected.equals(lvs)); + } + + @Test() + public 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() + public 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); + })); + }); + + ClassFile cf = ClassFile.read(new ByteArrayInputStream(bytes)); + Method m = cf.methods[1]; + + LocalVariableTypeTable_attribute lvtt = (LocalVariableTypeTable_attribute) ((Code_attribute) m.attributes.get("Code")).attributes.get("LocalVariableTypeTable"); + LocalVariableTypeTable_attribute.Entry[] entries = lvtt.local_variable_table; + List lvts = Arrays.asList(entries); + + /* 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, cf.constant_pool), + ExpectedLvtRecord.of(2, "l", "Ljava/util/List<+Ljava/lang/Object;>;", 8, 2, cf.constant_pool) + ); + + // Exploits non-symmetric "equals" in ExpectedLvRecord + for (int i = 0; i < lvts.size(); i++) { + assertTrue(expected.get(i).equals(lvts.get(i))); + } + } + + @Test() + public 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); + }); + } + }); + } + }); + } +} \ No newline at end of file diff --git a/test/jdk/jdk/classfile/MassAdaptCopyCodeTest.java b/test/jdk/jdk/classfile/MassAdaptCopyCodeTest.java new file mode 100644 index 0000000000000..3f1c92d86d118 --- /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 testng MassAdaptCopyCodeTest + */ +import helpers.ByteArrayClassLoader; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.MethodModel; +import org.testng.annotations.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.testng.Assert.assertEquals; + +public 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() + public 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..bd101ab590f5b --- /dev/null +++ b/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng MassAdaptCopyPrimitiveMatchCodeTest + */ +import helpers.InstructionModelToCodeBuilder; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.Classfile; +import jdk.classfile.attribute.CodeAttribute; +import jdk.classfile.Attributes; +import jdk.classfile.ClassModel; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.Instruction; +import jdk.classfile.MethodModel; +import jdk.classfile.instruction.InvokeInstruction; +import org.testng.annotations.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.testng.Assert.*; + +/** + * MassAdaptCopyPrimitiveMatchCodeTest. + */ +public 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(enabled = false) + 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)) + continue; + rima[bci] = im; + bci += im.sizeInBytes(); + } + bci = 0; + for (CodeElement im : ((CodeModel) ocal)) { + if (!(im instanceof Instruction)) + continue; + oima[bci] = im; + bci += im.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..4dbfe7209ba45 --- /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 testng ModuleBuilderTest + */ +import jdk.classfile.*; + +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleExportInfo; +import jdk.classfile.attribute.ModuleMainClassAttribute; +import jdk.classfile.attribute.ModuleOpenInfo; +import jdk.classfile.attribute.ModulePackagesAttribute; +import jdk.classfile.attribute.ModuleProvideInfo; +import jdk.classfile.attribute.ModuleRequireInfo; +import jdk.classfile.Attributes; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.jdktypes.PackageDesc; +import org.testng.annotations.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.testng.Assert.*; + +public 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 + public 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 + public void testAllAttributes() { + assertEquals(moduleModel.attributes().size(), 3); + } + + @Test + public 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 + public 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 + public 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 + public void testVerifyUses() { + var uses = attr.uses(); + assertEquals(uses.size(), 2); + assertEquals(uses.get(1).asInternalName(), "another/Service"); + } + + @Test + public 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 + public 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 + public void verifyMainclass() { + ModuleMainClassAttribute a = moduleModel.findAttribute(Attributes.MODULE_MAIN_CLASS).orElseThrow(); + assertEquals(a.mainClass().asInternalName(), "overwritten/main/Class"); + } + + @Test + public 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..b8c65f544fda3 --- /dev/null +++ b/test/jdk/jdk/classfile/ModuleDescTest.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. + */ + +/* + * @test + * @summary Testing ModuleDesc. + * @run testng ModuleDescTest + */ +import jdk.classfile.jdktypes.ModuleDesc; +import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; +import static org.testng.Assert.assertEquals; + +/** + * + */ +public class ModuleDescTest { + + @DataProvider(name = "invalidModuleNames") + public static String[] invalidModuleNames() { + return new String[] {"abc\\", "ab\\c", "\u0000", "\u0001", "\u001e", "\u001f"}; + } + + @Test(dataProvider = "invalidModuleNames", expectedExceptions = IllegalArgumentException.class) + public void testInvalidModuleNames(String mdl) { + ModuleDesc.of(mdl); + } + + @DataProvider(name = "validModuleNames") + public static String[] validModuleNames() { + return new String[] {"a\\\\b", "a.b/c", "a\\@b\\: c"}; + } + + @Test(dataProvider = "validModuleNames") + 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..1dd8c523d75e2 --- /dev/null +++ b/test/jdk/jdk/classfile/OneToOneTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng OneToOneTest + */ +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import java.util.List; + +import jdk.classfile.AccessFlags; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.Instruction; +import jdk.classfile.Label; +import jdk.classfile.MethodModel; +import jdk.classfile.TypeKind; +import jdk.classfile.attribute.SourceFileAttribute; +import org.testng.Assert; +import org.testng.annotations.Test; + +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.OperatorInstruction; +import jdk.classfile.instruction.FieldInstruction; +import jdk.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.classfile.Opcode.*; + +public class OneToOneTest { + + @Test + public 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(); + Assert.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) + .toList(); + Assert.assertEquals(instructions.size(), 17); + + Assert.assertEquals(instructions.get(0).opcode(), ICONST_1); + + var i1 = (StoreInstruction) instructions.get(1); + Assert.assertEquals(i1.opcode(), ISTORE_1); + int lv1 = i1.slot(); + Assert.assertEquals(lv1, 1); + + ConstantInstruction i5 = (ConstantInstruction) instructions.get(5); + Assert.assertEquals(i5.opcode(), BIPUSH); + Assert.assertEquals(i5.constantValue(), 10); + + BranchInstruction i6 = (BranchInstruction) instructions.get(6); + Assert.assertEquals(i6.opcode(), IF_ICMPGE); + // Assert.assertEquals(code.instructionOffset(i6.target()), 14); //FIXME: CodeModel gives BCI, should give instruction offset + + LoadInstruction i7 = (LoadInstruction) instructions.get(7); + Assert.assertEquals(i7.opcode(), ILOAD_1); + + OperatorInstruction i9 = (OperatorInstruction) instructions.get(9); + Assert.assertEquals(i9.opcode(), IMUL); + + FieldInstruction i13 = (FieldInstruction) instructions.get(13); + Assert.assertEquals(i13.opcode(), GETSTATIC); + Assert.assertEquals(i13.owner().asInternalName(), "java/lang/System"); + Assert.assertEquals(i13.name().stringValue(), "out"); + Assert.assertEquals(i13.type().stringValue(), "Ljava/io/PrintStream;"); + + InvokeInstruction i15 = (InvokeInstruction) instructions.get(15); + Assert.assertEquals(i15.opcode(), INVOKEVIRTUAL); + Assert.assertEquals(i15.owner().asInternalName(), "java/io/PrintStream"); + Assert.assertEquals(i15.name().stringValue(), "println"); + Assert.assertEquals(i15.type().stringValue(), "(I)V"); + } + } + Assert.assertTrue(found); + } +} diff --git a/test/jdk/jdk/classfile/OpcodesValidationTest.java b/test/jdk/jdk/classfile/OpcodesValidationTest.java new file mode 100644 index 0000000000000..7a6567105a082 --- /dev/null +++ b/test/jdk/jdk/classfile/OpcodesValidationTest.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. + */ + +/* + * @test + * @summary Testing Classfile constant instruction opcodes. + * @run testng 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 jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.Classfile; +import jdk.classfile.Opcode; +import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; +import static jdk.classfile.Opcode.*; + +/** + * + */ +public class OpcodesValidationTest { + + @DataProvider(name = "positiveCases") + public static Object[][] positiveCases() { + return new Object[][] { + {ACONST_NULL, null}, + {SIPUSH, (int)Short.MIN_VALUE}, + {SIPUSH, (int)Short.MAX_VALUE}, + {BIPUSH, (int)Byte.MIN_VALUE}, + {BIPUSH, (int)Byte.MAX_VALUE}, + {ICONST_M1, -1}, + {ICONST_0, 0}, + {ICONST_1, 1}, + {ICONST_2, 2}, + {ICONST_3, 3}, + {ICONST_4, 4}, + {ICONST_5, 5}, + {LCONST_0, 0l}, + {LCONST_0, 0}, + {LCONST_1, 1l}, + {LCONST_1, 1}, + {FCONST_0, 0.0f}, + {FCONST_1, 1.0f}, + {FCONST_2, 2.0f}, + {DCONST_0, 0.0d}, + {DCONST_1, 1.0d}, + }; + } + + @DataProvider(name = "negativeCases") + public static Object[][] negativeCases() { + return new Object[][] { + {ACONST_NULL, 0}, + {SIPUSH, (int)Short.MIN_VALUE - 1}, + {SIPUSH, (int)Short.MAX_VALUE + 1}, + {BIPUSH, (int)Byte.MIN_VALUE - 1}, + {BIPUSH, (int)Byte.MAX_VALUE + 1}, + {ICONST_M1, -1l}, + {ICONST_0, 0l}, + {ICONST_1, 1l}, + {ICONST_2, 2l}, + {ICONST_3, 3l}, + {ICONST_4, 4l}, + {ICONST_5, 5l}, + {LCONST_0, null}, + {LCONST_0, 1l}, + {LCONST_1, 1.0d}, + {LCONST_1, 0}, + {FCONST_0, 0.0d}, + {FCONST_1, 1.01f}, + {FCONST_2, 2}, + {DCONST_0, 0.0f}, + {DCONST_1, 1.0f}, + {DCONST_1, 1}, + }; + } + + @Test(dataProvider = "positiveCases") + public void testPositive(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)))); + } + + @Test(dataProvider = "negativeCases", expectedExceptions = IllegalArgumentException.class) + public void testNegative(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..3a5d4086b5647 --- /dev/null +++ b/test/jdk/jdk/classfile/PackageDescTest.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. + */ + +/* + * @test + * @summary Testing PackageDesc. + * @run testng PackageDescTest + */ +import jdk.classfile.jdktypes.PackageDesc; +import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; +import static org.testng.Assert.assertEquals; + +/** + * + */ +public class PackageDescTest { + + @DataProvider(name = "invalidPackageNames") + public static String[] invalidPackageNames() { + return new String[] {"a/b.d", "a[]", "a;"}; + } + + @Test(dataProvider = "invalidPackageNames", expectedExceptions = IllegalArgumentException.class) + public void testInvalidPackageNames(String pkg) { + PackageDesc.of(pkg); + } + + @Test(dataProvider = "invalidPackageNames", expectedExceptions = IllegalArgumentException.class) + public void testInvalidInternalPackageNames(String pkg) { + PackageDesc.ofInternalName(pkg); + } + + @Test + public 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..377472fa9f149 --- /dev/null +++ b/test/jdk/jdk/classfile/ShortJumpsFixTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng 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.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.Instruction; +import jdk.classfile.MethodTransform; +import jdk.classfile.Opcode; +import static jdk.classfile.Opcode.*; +import jdk.classfile.instruction.ConstantInstruction; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + +/** + * + */ +public class ShortJumpsFixTest { + + public record Sample(Opcode jumpCode, Opcode... expected) { + @Override + public String toString() { + return jumpCode.name(); + } + } + + @DataProvider(name = "fwdJumpsCode") + public 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), + }; + } + + @DataProvider(name = "backJumpsCode") + public 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), + }; + } + + + @Test(dataProvider = "fwdJumpsCode") + public void testFixFwdJumpsDirectGen(Sample sample) throws Exception { + assertFixed(sample, generateFwd(sample, true, Classfile.Option.fixShortJumps(true))); + } + + @Test(dataProvider = "backJumpsCode") + public void testFixBackJumpsDirectGen(Sample sample) throws Exception { + assertFixed(sample, generateBack(sample, true, Classfile.Option.fixShortJumps(true))); + } + + @Test(dataProvider = "fwdJumpsCode", expectedExceptions = IllegalStateException.class) + public void testFailFwdJumpsDirectGen(Sample sample) throws Exception { + generateFwd(sample, true, Classfile.Option.fixShortJumps(false)); + } + + @Test(dataProvider = "backJumpsCode", expectedExceptions = IllegalStateException.class) + public void testFailBackJumpsDirectGen(Sample sample) throws Exception { + generateBack(sample, true, Classfile.Option.fixShortJumps(false)); + } + + @Test(dataProvider = "fwdJumpsCode") + public 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())); + } + + @Test(dataProvider = "backJumpsCode") + public 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())); + } + + @Test(dataProvider = "fwdJumpsCode", expectedExceptions = IllegalStateException.class) + public void testFailFwdJumpsTransform(Sample sample) throws Exception { + Classfile.parse( + generateFwd(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(false)) + .transform(overflow()); + } + + @Test(dataProvider = "backJumpsCode", expectedExceptions = IllegalStateException.class) + public void testFailBackJumpsTransform(Sample sample) throws Exception { + Classfile.parse( + generateBack(sample, false, Classfile.Option.generateStackmap(false), Classfile.Option.patchDeadCode(false)), + Classfile.Option.fixShortJumps(false)) + .transform(overflow()); + } + + @Test(dataProvider = "fwdJumpsCode") + public 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 + } + + @Test(dataProvider = "backJumpsCode") + public 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 + } + + @Test(dataProvider = "fwdJumpsCode", expectedExceptions = IllegalStateException.class) + public void testFailFwdJumpsChainedTransform(Sample sample) throws Exception { + 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 + } + + @Test(dataProvider = "backJumpsCode", expectedExceptions = IllegalStateException.class) + public void testFailBackJumpsChainedTransform(Sample sample) throws Exception { + 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.opcode() == NOP) + 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..bd893e010c61c --- /dev/null +++ b/test/jdk/jdk/classfile/SignaturesTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng SignaturesTest + */ +import helpers.CorpusTestHelper; +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.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import jdk.classfile.ClassSignature; +import jdk.classfile.Classfile; +import jdk.classfile.MethodSignature; +import jdk.classfile.Signature; +import jdk.classfile.Signature.*; +import jdk.classfile.Attributes; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static helpers.ClassRecord.assertEqualsDeep; +import static java.lang.constant.ConstantDescs.*; + +/** + * + */ +public class SignaturesTest { + + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + @Test + public void testBuildingSignatures() { + assertEqualsDeep( + ClassSignature.of( + ClassTypeSig.of( + ClassTypeSig.of(ClassDesc.of("java.util.LinkedHashMap"), TypeVarSig.of("K"), TypeVarSig.of("V")), + ClassDesc.of("LinkedHashIterator")), + ClassTypeSig.of(ClassDesc.of("java.util.Iterator"), + ClassTypeSig.of(ClassDesc.of("java.util.Map$Entry"), TypeVarSig.of("K"), 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"), TypeVarSig.of("K"), TypeVarSig.of("V")), + ClassTypeSig.of(ClassDesc.of("java.util.concurrent.ConcurrentMap"), TypeVarSig.of("K"), 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( + List.of( + 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")))))), + ClassTypeSig.of( + CD_Map, + ClassTypeSig.of( + CD_Class, + TypeArg.extendsOf( + ClassTypeSig.of(ClassDesc.of("java.lang.annotation.Annotation")))), + 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", null, ClassTypeSig.of(ClassDesc.of("java.lang.annotation.Annotation")))), + List.of( + ClassTypeSig.of(CD_Class, TypeVarSig.of("T"))), + ArrayTypeSig.of(TypeVarSig.of("T")), + List.of( + ClassTypeSig.of(ClassDesc.of("java.lang.IOException")), + ClassTypeSig.of(ClassDesc.of("java.lang.IllegalAccessError")))), + 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 + public 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(CorpusTestHelper.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..277ce1cb171f5 --- /dev/null +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng StackMapsTest + */ + +import jdk.classfile.Classfile; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import org.testng.annotations.Test; +import static helpers.TestUtil.assertEmpty; +import static jdk.classfile.Classfile.ACC_STATIC; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import jdk.classfile.jdktypes.AccessFlag; + +/** + * StackMapsTest + */ +public 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_())); + } + + @Test + public void testDeadCodePatternPatch() throws Exception { + testTransformedStackMaps(buildDeadCode()); + } + + @Test(expectedExceptions = VerifyError.class) + public void testDeadCodePatternFail() throws Exception { + testTransformedStackMaps(buildDeadCode(), Classfile.Option.patchDeadCode(false)); + } + + @Test + public void testUnresolvedPermission() throws Exception { + testTransformedStackMaps("modules/java.base/java/security/UnresolvedPermission.class"); + } + + @Test + public void testURL() throws Exception { + testTransformedStackMaps("modules/java.base/java/net/URL.class"); + } + + @Test + public void testPattern1() throws Exception { + testTransformedStackMaps("/testdata/Pattern1.class"); + } + + @Test + public void testPattern2() throws Exception { + testTransformedStackMaps("/testdata/Pattern2.class"); + } + + @Test + public void testPattern3() throws Exception { + testTransformedStackMaps("/testdata/Pattern3.class"); + } + + @Test + public void testPattern4() throws Exception { + testTransformedStackMaps("/testdata/Pattern4.class"); + } + + @Test + public void testPattern5() throws Exception { + testTransformedStackMaps("/testdata/Pattern5.class"); + } + + @Test + public void testPattern6() throws Exception { + testTransformedStackMaps("/testdata/Pattern6.class"); + } + + @Test + public void testPattern7() throws Exception { + testTransformedStackMaps("/testdata/Pattern7.class"); + } + + @Test + public void testPattern8() throws Exception { + testTransformedStackMaps("/testdata/Pattern8.class"); + } + + @Test + public void testPattern9() throws Exception { + testTransformedStackMaps("/testdata/Pattern9.class"); + } + + @Test + public void testPattern10() throws Exception { + testTransformedStackMaps("/testdata/Pattern10.class"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMethodSwitchFromStatic() { + 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(expectedExceptions = IllegalArgumentException.class) + public void testMethodSwitchToStatic() { + 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/StreamedVsListTest.java b/test/jdk/jdk/classfile/StreamedVsListTest.java new file mode 100644 index 0000000000000..5503857930aba --- /dev/null +++ b/test/jdk/jdk/classfile/StreamedVsListTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng StreamedVsListTest + */ +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.Instruction; +import jdk.classfile.MethodModel; +import jdk.classfile.impl.DirectCodeBuilder; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.InvokeDynamicInstruction; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewObjectInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.instruction.TypeCheckInstruction; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public class StreamedVsListTest { + static byte[] fileBytes; + + static { + try { + fileBytes = DirectCodeBuilder.class.getResourceAsStream("DirectCodeBuilder.class").readAllBytes(); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + + @Test + public 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++); + Assert.assertEquals(iim.opcode(), mim.opcode(), "Opcodes don't match"); + if (iim instanceof Instruction) + testInstruction(); + } + }); + } catch (Throwable ex) { + failed = true; + System.err.printf("%s.%s #%d[%s]: ", cm.thisClass().asInternalName(), meth, n - 1, iim == null ? "<->" : iim.opcode()); + System.err.printf("Threw: %s" + "%n", ex); + throw ex; + } + } + } + + void testInstruction() { + switch (iim.codeKind()) { + case LOAD: { + LoadInstruction i = (LoadInstruction) iim; + LoadInstruction x = (LoadInstruction) mim; + Assert.assertEquals(i.slot(), x.slot(), "variable"); + break; + } + case STORE: { + StoreInstruction i = (StoreInstruction) iim; + StoreInstruction x = (StoreInstruction) mim; + Assert.assertEquals(i.slot(), x.slot(), "variable"); + break; + } + case INCREMENT: { + IncrementInstruction i = (IncrementInstruction) iim; + IncrementInstruction x = (IncrementInstruction) mim; + Assert.assertEquals(i.slot(), x.slot(), "variable"); + Assert.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; + Assert.assertEquals(i.lowValue(), x.lowValue(), "lowValue"); + Assert.assertEquals(i.highValue(), x.highValue(), "highValue"); + Assert.assertEquals(i.cases().size(), x.cases().size(), "cases().size"); + //TODO: test labels + break; + } + case LOOKUP_SWITCH: { + LookupSwitchInstruction i = (LookupSwitchInstruction) iim; + LookupSwitchInstruction x = (LookupSwitchInstruction) mim; + Assert.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; + Assert.assertEquals(i.owner().asInternalName(), (Object) x.owner().asInternalName(), "owner"); + Assert.assertEquals(i.name().stringValue(), (Object) x.name().stringValue(), "name"); + Assert.assertEquals(i.type().stringValue(), (Object) x.type().stringValue(), "type"); + break; + } + case INVOKE: { + InvokeInstruction i = (InvokeInstruction) iim; + InvokeInstruction x = (InvokeInstruction) mim; + Assert.assertEquals(i.owner().asInternalName(), (Object) x.owner().asInternalName(), "owner"); + Assert.assertEquals(i.name().stringValue(), (Object) x.name().stringValue(), "name"); + Assert.assertEquals(i.type().stringValue(), (Object) x.type().stringValue(), "type"); + Assert.assertEquals(i.isInterface(), (Object) x.isInterface(), "isInterface"); + Assert.assertEquals(i.count(), x.count(), "count"); + break; + } + case INVOKE_DYNAMIC: { + InvokeDynamicInstruction i = (InvokeDynamicInstruction) iim; + InvokeDynamicInstruction x = (InvokeDynamicInstruction) mim; + Assert.assertEquals(i.bootstrapMethod(), x.bootstrapMethod(), "bootstrapMethod"); + Assert.assertEquals(i.bootstrapArgs(), (Object) x.bootstrapArgs(), "bootstrapArgs"); + Assert.assertEquals(i.name().stringValue(), (Object) x.name().stringValue(), "name"); + Assert.assertEquals(i.type().stringValue(), (Object) x.type().stringValue(), "type"); + break; + } + case NEW_OBJECT: { + NewObjectInstruction i = (NewObjectInstruction) iim; + NewObjectInstruction x = (NewObjectInstruction) mim; + Assert.assertEquals(i.className().asInternalName(), (Object) x.className().asInternalName(), "type"); + break; + } + case NEW_PRIMITIVE_ARRAY: + { + NewPrimitiveArrayInstruction i = (NewPrimitiveArrayInstruction) iim; + NewPrimitiveArrayInstruction x = (NewPrimitiveArrayInstruction) mim; + Assert.assertEquals(i.typeKind(), x.typeKind(), "type"); + break; + } + + case NEW_REF_ARRAY:{ + NewReferenceArrayInstruction i = (NewReferenceArrayInstruction) iim; + NewReferenceArrayInstruction x = (NewReferenceArrayInstruction) mim; + Assert.assertEquals(i.componentType().asInternalName(), (Object) x.componentType().asInternalName(), "type"); + break; + } + + case NEW_MULTI_ARRAY:{ + NewMultiArrayInstruction i = (NewMultiArrayInstruction) iim; + NewMultiArrayInstruction x = (NewMultiArrayInstruction) mim; + Assert.assertEquals(i.arrayType().asInternalName(), (Object) x.arrayType().asInternalName(), "type"); + Assert.assertEquals(i.dimensions(), x.dimensions(), "dimensions"); + break; + } + + case TYPE_CHECK: { + TypeCheckInstruction i = (TypeCheckInstruction) iim; + TypeCheckInstruction x = (TypeCheckInstruction) mim; + Assert.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; + Assert.assertEquals(i.constantValue(), x.constantValue(), "constantValue"); + } + break; + case MONITOR: + case NOP: + break; + + } + } + } +} diff --git a/test/jdk/jdk/classfile/TEST.properties b/test/jdk/jdk/classfile/TEST.properties new file mode 100644 index 0000000000000..7e51afc0e76b3 --- /dev/null +++ b/test/jdk/jdk/classfile/TEST.properties @@ -0,0 +1,15 @@ +maxOutputSize = 250000 +enablePreview = true +modules = \ + java.base/jdk.classfile \ + java.base/jdk.classfile.attribute \ + java.base/jdk.classfile.constantpool \ + java.base/jdk.classfile.instruction \ + java.base/jdk.classfile.impl \ + java.base/jdk.classfile.impl.verifier \ + java.base/jdk.classfile.jdktypes \ + java.base/jdk.classfile.transforms \ + java.base/jdk.classfile.util \ + java.base/jdk.internal.org.objectweb.asm \ + java.base/jdk.internal.org.objectweb.asm.tree \ + jdk.jdeps/com.sun.tools.classfile \ 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..f4192db47a056 --- /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 testng TempConstantPoolBuilderTest + */ +import jdk.classfile.*; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.SourceFileAttribute; +import jdk.classfile.jdktypes.AccessFlag; +import org.testng.annotations.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.classfile.Opcode.INVOKESPECIAL; +import static jdk.classfile.TypeKind.VoidType; + +public class TempConstantPoolBuilderTest { + + public static final ClassDesc INTERFACE = ClassDesc.ofDescriptor("Ljava/lang/FunctionalInterface;"); + + @Test() + public void createAnno() { + Annotation a = Annotation.of(INTERFACE, + AnnotationElement.ofString("foo", "bar")); + } + + @Test + public 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.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, System.out::println).printClass(m); + } +} diff --git a/test/jdk/jdk/classfile/TestRecordComponent.java b/test/jdk/jdk/classfile/TestRecordComponent.java new file mode 100644 index 0000000000000..38bc2b121c972 --- /dev/null +++ b/test/jdk/jdk/classfile/TestRecordComponent.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng 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.classfile.Attributes; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.attribute.RecordAttribute; +import jdk.classfile.attribute.RecordComponentInfo; +import jdk.classfile.impl.TemporaryConstantPool; +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test() +public class TestRecordComponent { + + static final String testClassName = "TestRecordComponent$TestRecord"; + static final Path testClassPath = Paths.get(URI.create(ArrayTest.class.getResource(testClassName + ".class").toString())); + + public 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); + } + + public 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); + } + + public 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(); + Assert.assertEquals(ra.components().size(), 2, "Should have two components"); + Assert.assertEquals(ra.components().get(0).name().stringValue(), "fooXYZ"); + Assert.assertEquals(ra.components().get(1).name().stringValue(), "barXYZ"); + Assert.assertTrue(ra.components().get(0).attributes().isEmpty()); + Assert.assertEquals(newModel.attributes().size(), cm.attributes().size()); + } + + public 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()); + }}); + Assert.assertEquals(count.get(), 2); + Assert.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..3be5dbb2d9cb0 --- /dev/null +++ b/test/jdk/jdk/classfile/TransformTests.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 transformations. + * @run testng 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.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.MethodModel; +import jdk.classfile.instruction.ConstantInstruction; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +/** + * TransformTests + */ +@Test() +public 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 + public 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 + public 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 + public 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..67c1da0841e9a --- /dev/null +++ b/test/jdk/jdk/classfile/Utf8EntryTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 testng Utf8EntryTest + */ +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +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.function.UnaryOperator; + +import static java.lang.constant.ConstantDescs.CD_void; +import static jdk.classfile.TypeKind.VoidType; + +public class Utf8EntryTest { + + @DataProvider + static Object[][] stringsProvider() { + return new Object[][]{ + {"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"}, + }; + } + + @Test(dataProvider = "stringsProvider") + public 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[] + Assert.assertTrue(utf8Entry.equalsString(s)); + + // Create string + Assert.assertEquals(utf8Entry.stringValue(), s); + } + + @DataProvider + static Object[][] 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().map(u -> new Object[]{u}).toArray(Object[][]::new); + } + + 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; + } + }; + } + + @Test(dataProvider = "malformedStringsProvider") + public 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()); + + Assert.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..291fa59d2ecfd --- /dev/null +++ b/test/jdk/jdk/classfile/UtilTest.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. + */ + +/* + * @test + * @summary Testing Classfile Util. + * @run testng UtilTest + */ +import jdk.classfile.impl.Util; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +/** + * UtilTest + */ +@Test +public class UtilTest { + public 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); + } + + public 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..9c43549f4179d --- /dev/null +++ b/test/jdk/jdk/classfile/VerifierSelfTest.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 Verifier. + * @run testng 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.classfile.Classfile; +import jdk.classfile.CodeModel; +import jdk.classfile.MethodModel; +import org.testng.annotations.Test; + +/** + * + */ +public class VerifierSelfTest { + + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + @Test + public 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 + public 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..5b9cd1ab9a2e9 --- /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 testng WriteTest + */ +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; + +import helpers.TestConstants; +import jdk.classfile.AccessFlags; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.Classfile; +import jdk.classfile.TypeKind; +import jdk.classfile.Label; +import jdk.classfile.attribute.SourceFileAttribute; +import org.testng.annotations.Test; + +import static helpers.TestConstants.MTD_VOID; +import static java.lang.constant.ConstantDescs.*; +import static jdk.classfile.Opcode.*; +import static jdk.classfile.TypeKind.IntType; +import static jdk.classfile.TypeKind.ReferenceType; +import static jdk.classfile.TypeKind.VoidType; + +@Test +public class WriteTest { + + public 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); + })); + }); + } + + public 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..3cfd9001870bd --- /dev/null +++ b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import helpers.CorpusTestHelper; +import jdk.classfile.Annotation; +import jdk.classfile.Attributes; +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.util.ClassPrinter; +import org.testng.Assert; +import org.testng.annotations.Factory; +import org.testng.annotations.Test; + +public class AnnotationsExamples extends CorpusTestHelper { + + @Factory(dataProvider = "corpus") + public AnnotationsExamples(Path path) throws IOException { + super(path); + } + + /** Add a single annotation to a class using a builder convenience */ + @Test(enabled = true) + public void addAnno() { + // @@@ Not correct + ClassModel m = Classfile.parse(bytes); + List annos = List.of(Annotation.of(ClassDesc.of("java.lang.FunctionalInterface"))); + var newBytes = m.transform(ClassTransform.endHandler(cb -> cb.with(RuntimeVisibleAnnotationsAttribute.of(annos)))); + ClassModel res = Classfile.parse(newBytes); + } + + /** + * Find classes with annotations of a certain type + */ + @Test(enabled = true) + public void findAnnotation() { + ClassModel m = Classfile.parse(bytes); + + 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. + */ + @Test(enabled = true) + public void swapAnnotation() { + ClassModel m = Classfile.parse(bytes); + 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 + */ + @Test(enabled = true) + public void addAnnotation() { + ClassModel m = Classfile.parse(bytes); + 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.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, sb::append).printClass(m2); + System.err.println(sb.toString()); + } + } + + @Test(enabled = true) + public void viaEndHandlerClassBuilderEdition() { + ClassModel m = Classfile.parse(bytes); + byte[] transformed = 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"))))); + } + } + })); + + ClassModel n = Classfile.parse(transformed); + + // This doesn't work + // Assert.assertTrue(m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent(), "All classes should have annotations by now"); + + // But this does + Assert.assertTrue(n.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent(), "All classes should have annotations by now"); + } + + @Test(enabled = true) + public void viaEndHandlerClassTransformEdition() { + ClassModel m = Classfile.parse(bytes); + + byte[] transformed = 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"))))); + } + } + })); + + ClassModel n = Classfile.parse(transformed); + + // This doesn't work + //Assert.assertTrue(m.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent(), "All classes should have annotations by now"); + + // But this does + Assert.assertTrue(n.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).isPresent(), "All classes should have annotations by now"); + } +} \ 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 100755 index 0000000000000..dd8240d5d0916 --- /dev/null +++ b/test/jdk/jdk/classfile/examples/ExampleGallery.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. + */ + +import java.lang.constant.ClassDesc; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import jdk.classfile.AccessFlags; +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassSignature; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.ClassfileVersion; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.FieldModel; +import jdk.classfile.Interfaces; +import jdk.classfile.MethodBuilder; +import jdk.classfile.MethodElement; +import jdk.classfile.MethodTransform; +import jdk.classfile.Signature; +import jdk.classfile.Signature.ClassTypeSig; +import jdk.classfile.Superclass; +import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.jdktypes.AccessFlag; + +/** + * 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"), + 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.codeKind()) { + case INVOKE -> { + 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 + && ci.constantValue() instanceof Integer i -> codeB.constantInstruction(i + 1); + 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 100755 index 0000000000000..c6915e6051a70 --- /dev/null +++ b/test/jdk/jdk/classfile/examples/ExperimentalTransformExamples.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. + */ + +import jdk.classfile.*; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import org.testng.annotations.Test; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; + +/** + * 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); + } + }); + } + + @Test(enabled=false) + public void testDropAnnos() throws Exception { + ClassModel model = Classfile.parse(Path.of("Some.class")); + var newBytes = deleteAnnotations(model); + ClassModel newModel = Classfile.parse(newBytes ); + // Compare the two + } +} \ 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..5d4616f2b807b --- /dev/null +++ b/test/jdk/jdk/classfile/examples/ModuleExamples.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please 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 jdk.classfile.Annotation; +import jdk.classfile.AnnotationElement; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleMainClassAttribute; +import jdk.classfile.attribute.ModulePackagesAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.Attributes; +import org.testng.annotations.Test; + +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.classfile.ModuleAttributeBuilder; + +public class ModuleExamples { + private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + + @Test + 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().asString()); + 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()); + } + + @Test(expectedExceptions = RuntimeException.class) + public void buildModuleFromScratch() { + String moduleName = "the.very.best.module"; + int moduleFlags = 0; + + Consumer handler = (mb -> {mb + .exports("export.some.pkg", 0) + .exports("qualified.export.to" , 0, "to.first.pkg", "to.another.pkg"); + }); + + // Build it + byte[] moduleInfo = Classfile.buildModule(ModuleAttribute.of(moduleName, moduleFlags, 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 100755 index 0000000000000..4876d54942e0c --- /dev/null +++ b/test/jdk/jdk/classfile/examples/TransformExamples.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. + */ + +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.FieldModel; +import jdk.classfile.MethodModel; +import jdk.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().asString().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..2c90487ec03e4 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/ClassRecord.java @@ -0,0 +1,1832 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.*; +import jdk.classfile.attribute.*; +import jdk.classfile.constantpool.ClassEntry; +import jdk.classfile.constantpool.ConstantDynamicEntry; +import jdk.classfile.constantpool.ConstantPool; +import jdk.classfile.constantpool.DoubleEntry; +import jdk.classfile.constantpool.FieldRefEntry; +import jdk.classfile.constantpool.FloatEntry; +import jdk.classfile.constantpool.IntegerEntry; +import jdk.classfile.constantpool.InterfaceMethodRefEntry; +import jdk.classfile.constantpool.InvokeDynamicEntry; +import jdk.classfile.constantpool.LongEntry; +import jdk.classfile.constantpool.MethodHandleEntry; +import jdk.classfile.constantpool.MethodRefEntry; +import jdk.classfile.constantpool.MethodTypeEntry; +import jdk.classfile.constantpool.ModuleEntry; +import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.PackageEntry; +import jdk.classfile.constantpool.PoolEntry; +import jdk.classfile.constantpool.StringEntry; +import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.impl.LabelResolver; +import jdk.classfile.instruction.*; + +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static jdk.classfile.Classfile.*; +import static jdk.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 static ClassRecord ofClassFile(com.sun.tools.classfile.ClassFile cf, CompatibilityFilter... compatibilityFilter) { + return wrapException(() -> new ClassRecord( + cf.major_version, + cf.minor_version, + cf.getName(), + cf.super_class == 0 ? null : cf.getSuperclassName(), + IntStream.range(0, cf.interfaces.length).boxed().map(wrapException(i -> cf.getInterfaceName(i))).collect(toSet()), + Flags.toString(cf.access_flags.flags, false), + Stream.of(cf.fields).collect(toMap(wrapException(f -> f.getName(cf.constant_pool) + f.descriptor.getValue(cf.constant_pool)), f + -> FieldRecord.ofClassFileField(f, cf.constant_pool, compatibilityFilter))), + Stream.of(cf.methods).collect(toMap(wrapException(m -> m.getName(cf.constant_pool) + m.descriptor.getValue(cf.constant_pool)), m + -> MethodRecord.ofClassFileMethod(m, cf.constant_pool, compatibilityFilter))), + AttributesRecord.ofClassFileAttributes(cf.attributes, cf.constant_pool, 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 static FieldRecord ofClassFileField(com.sun.tools.classfile.Field f, com.sun.tools.classfile.ConstantPool p, CompatibilityFilter... compatibilityFilter) { + return wrapException(() -> new FieldRecord( + f.getName(p), + f.descriptor.getValue(p), + Flags.toString(f.access_flags.flags, false), + AttributesRecord.ofClassFileAttributes(f.attributes, p, 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)); + } + + public static MethodRecord ofClassFileMethod(com.sun.tools.classfile.Method m, com.sun.tools.classfile.ConstantPool p, CompatibilityFilter... compatibilityFilter) { + return wrapException(() -> new MethodRecord( + m.getName(p), + m.descriptor.getValue(p), + Flags.toString(m.access_flags.flags, true), + AttributesRecord.ofClassFileAttributes(m.attributes, p, 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); + } + } + + private static Stream getAllByName(com.sun.tools.classfile.Attributes attrs, com.sun.tools.classfile.ConstantPool p, String name) { + return Stream.of(attrs.attrs).filter(a -> name.equals(wrapException( () -> a.getName(p)))); + } + + 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(), ((CodeModel)a)::elementStream, (LabelResolver)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 static AttributesRecord ofClassFileAttributes(com.sun.tools.classfile.Attributes attrs, com.sun.tools.classfile.ConstantPool p, CompatibilityFilter... cf) { + return new AttributesRecord( + map(attrs.get(com.sun.tools.classfile.Attribute.AnnotationDefault), a -> ElementValueRecord.ofClassFileElementValue(((com.sun.tools.classfile.AnnotationDefault_attribute) a).default_value, p)), + map(attrs.get(com.sun.tools.classfile.Attribute.BootstrapMethods), a -> Stream.of(((com.sun.tools.classfile.BootstrapMethods_attribute) a).bootstrap_method_specifiers) + .map(bm -> BootstrapMethodRecord.ofClassFileBootstrapMethodSpecifier(bm, p)).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.Code), a -> CodeRecord.ofClassFileCodeAttribute((com.sun.tools.classfile.Code_attribute) a, p, cf)), + map(attrs.get(com.sun.tools.classfile.Attribute.CompilationID), a -> p.getUTF8Value(((com.sun.tools.classfile.CompilationID_attribute) a).compilationID_index)), + map(attrs.get(com.sun.tools.classfile.Attribute.ConstantValue), a -> ConstantPoolEntryRecord.ofClassFileCPInfo(p.get(((com.sun.tools.classfile.ConstantValue_attribute) a).constantvalue_index), p)), + map(attrs.get(com.sun.tools.classfile.Attribute.Deprecated), a -> DefinedValue.DEFINED), + map(attrs.get(com.sun.tools.classfile.Attribute.EnclosingMethod), a -> EnclosingMethodRecord.ofClassFileEnclosingMethodAttribute((com.sun.tools.classfile.EnclosingMethod_attribute) a, p)), + map(attrs.get(com.sun.tools.classfile.Attribute.Exceptions), a -> IntStream.range(0, ((com.sun.tools.classfile.Exceptions_attribute)a).number_of_exceptions).boxed() + .map(wrapException(i -> ((com.sun.tools.classfile.Exceptions_attribute)a).getException(i, p))).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.InnerClasses), a -> Stream.of(((com.sun.tools.classfile.InnerClasses_attribute) a).classes) + .collect(toMap(wrapException(innerClassInfo -> innerClassInfo.getInnerClassInfo(p).getName()), innerClassInfo -> InnerClassRecord.ofClassFileICInfo(innerClassInfo, p)))), + map(attrs.get(com.sun.tools.classfile.Attribute.MethodParameters), a -> Stream.of(((com.sun.tools.classfile.MethodParameters_attribute) a).method_parameter_table).map(mp -> MethodParameterRecord.ofClassFileMPEntry(mp, p)).toList()), + map(attrs.get(com.sun.tools.classfile.Attribute.Module), a -> ModuleRecord.ofClassFileModuleAttribute((com.sun.tools.classfile.Module_attribute) a, p)), + map(attrs.get(com.sun.tools.classfile.Attribute.ModuleHashes), a -> ModuleHashesRecord.ofClassFileModuleHashesAttribute((com.sun.tools.classfile.ModuleHashes_attribute) a, p)), + map(attrs.get(com.sun.tools.classfile.Attribute.ModuleMainClass), a -> ((com.sun.tools.classfile.ModuleMainClass_attribute) a).getMainClassName(p)), + map(attrs.get(com.sun.tools.classfile.Attribute.ModulePackages), a -> Arrays.stream(((com.sun.tools.classfile.ModulePackages_attribute) a).packages_index).boxed() + .map(wrapException(i -> p.getPackageInfo(i).getName())).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.ModuleResolution), a -> ((com.sun.tools.classfile.ModuleResolution_attribute) a).resolution_flags), + map(attrs.get(com.sun.tools.classfile.Attribute.ModuleTarget), a -> p.getUTF8Value(((com.sun.tools.classfile.ModuleTarget_attribute) a).target_platform_index)), + map(attrs.get(com.sun.tools.classfile.Attribute.NestHost), a -> ((com.sun.tools.classfile.NestHost_attribute) a).getNestTop(p).getName()), + map(attrs.get(com.sun.tools.classfile.Attribute.NestMembers), a -> IntStream.of(((com.sun.tools.classfile.NestMembers_attribute) a).members_indexes).boxed() + .map(wrapException(i -> p.getClassInfo(i).getName())).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.PermittedSubclasses), a -> IntStream.range(0, ((com.sun.tools.classfile.PermittedSubclasses_attribute)a).subtypes.length).boxed() + .map(wrapException(i -> p.getClassInfo(((com.sun.tools.classfile.PermittedSubclasses_attribute)a).subtypes[i]).getName())).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.Record), a -> Stream.of(((com.sun.tools.classfile.Record_attribute) a).component_info_arr) + .map(ci -> RecordComponentRecord.ofClassFileComponentInfo(ci, p, cf)).toList()), + getAllByName(attrs, p, com.sun.tools.classfile.Attribute.RuntimeVisibleAnnotations).flatMap(a -> Stream.of(((com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute) a).annotations)) + .map(ann -> AnnotationRecord.ofClassFileAnnotation(ann, p)).collect(toSetOrNull()), + getAllByName(attrs, p, com.sun.tools.classfile.Attribute.RuntimeInvisibleAnnotations).flatMap(a -> Stream.of(((com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute) a).annotations)) + .map(ann -> AnnotationRecord.ofClassFileAnnotation(ann, p)).collect(toSetOrNull()), + map(attrs.get(com.sun.tools.classfile.Attribute.RuntimeVisibleParameterAnnotations), a -> Stream.of(((com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute) a).parameter_annotations) + .map(list -> Stream.of(list).map(ann -> AnnotationRecord.ofClassFileAnnotation(ann, p)).collect(toSet())).toList()), + map(attrs.get(com.sun.tools.classfile.Attribute.RuntimeInvisibleParameterAnnotations), a -> Stream.of(((com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute) a).parameter_annotations) + .map(list -> Stream.of(list).map(ann -> AnnotationRecord.ofClassFileAnnotation(ann, p)).collect(toSet())).toList()), + map(attrs.get(com.sun.tools.classfile.Attribute.RuntimeVisibleTypeAnnotations), a -> Stream.of(((com.sun.tools.classfile.RuntimeVisibleTypeAnnotations_attribute) a).annotations) + .map(ann -> TypeAnnotationRecord.ofClassFileTypeAnnotation(ann, p, null)).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.RuntimeInvisibleTypeAnnotations), a -> Stream.of(((com.sun.tools.classfile.RuntimeInvisibleTypeAnnotations_attribute) a).annotations) + .map(ann -> TypeAnnotationRecord.ofClassFileTypeAnnotation(ann, p, null)).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.Signature), a -> ((com.sun.tools.classfile.Signature_attribute) a).getSignature(p)), + map(attrs.get(com.sun.tools.classfile.Attribute.SourceDebugExtension), a -> ((com.sun.tools.classfile.SourceDebugExtension_attribute) a).getValue()), + map(attrs.get(com.sun.tools.classfile.Attribute.SourceFile), a -> ((com.sun.tools.classfile.SourceFile_attribute) a).getSourceFile(p)), + map(attrs.get(com.sun.tools.classfile.Attribute.SourceID), a -> p.getUTF8Value(((com.sun.tools.classfile.SourceID_attribute) a).sourceID_index)), + map(attrs.get(com.sun.tools.classfile.Attribute.Synthetic), a -> DefinedValue.DEFINED)); + } + } + + public record CodeAttributesRecord( + Set characterRangeTableAttribute, + Set lineNumbersTableAttribute, + Set localVariableTableAttribute, + Set localVariableTypeTableAttribute, + Set runtimeVisibleTypeAnnotationsAttribute, + Set runtimeInvisibleTypeAnnotationsAttribute) { + + static CodeAttributesRecord ofStreamingElements(Supplier> elements, LabelResolver 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, LabelResolver 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()))); + } + + static CodeAttributesRecord ofClassFileAttributes(com.sun.tools.classfile.Attributes attrs, com.sun.tools.classfile.ConstantPool p, CodeNormalizerHelper code, CompatibilityFilter... cf) { + return new CodeAttributesRecord( + getAllByName(attrs, p, com.sun.tools.classfile.Attribute.CharacterRangeTable).flatMap(a -> Stream.of(((com.sun.tools.classfile.CharacterRangeTable_attribute) a).character_range_table)) + .map(cr -> CharacterRangeRecord.ofClassFileCRTEntry(cr, code)).collect(toSetOrNull()), + getAllByName(attrs, p, com.sun.tools.classfile.Attribute.LineNumberTable).flatMap(a -> Stream.of(((com.sun.tools.classfile.LineNumberTable_attribute)a).line_number_table)) + .map(ln -> new LineNumberRecord(ln.line_number, code.targetIndex(ln.start_pc))).collect(toSetOrNull()), + getAllByName(attrs, p, com.sun.tools.classfile.Attribute.LocalVariableTable).flatMap(a -> Stream.of(((com.sun.tools.classfile.LocalVariableTable_attribute) a).local_variable_table)) + .map(lv -> LocalVariableRecord.ofClassFileLVTEntry(lv, code, p)).collect(toSetOrNull()), + getAllByName(attrs, p, com.sun.tools.classfile.Attribute.LocalVariableTypeTable).flatMap(a -> Stream.of(((com.sun.tools.classfile.LocalVariableTypeTable_attribute) a).local_variable_table)) + .map(lv -> LocalVariableTypeRecord.ofClassFileLVTTEntry(lv, code, p)).collect(toSetOrNull()), + map(attrs.get(com.sun.tools.classfile.Attribute.RuntimeVisibleTypeAnnotations), a -> Stream.of(((com.sun.tools.classfile.RuntimeVisibleTypeAnnotations_attribute) a).annotations) + .map(ann -> TypeAnnotationRecord.ofClassFileTypeAnnotation(ann, p, code)).collect(toSet())), + map(attrs.get(com.sun.tools.classfile.Attribute.RuntimeInvisibleTypeAnnotations), a -> Stream.of(((com.sun.tools.classfile.RuntimeInvisibleTypeAnnotations_attribute) a).annotations) + .map(ann -> TypeAnnotationRecord.ofClassFileTypeAnnotation(ann, p, 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 static AnnotationRecord ofClassFileAnnotation(com.sun.tools.classfile.Annotation ann, com.sun.tools.classfile.ConstantPool p) { + return new AnnotationRecord( + wrapException(() -> p.getUTF8Value(ann.type_index)), + Stream.of(ann.element_value_pairs).collect(toMap(wrapException(evp -> p.getUTF8Value(evp.element_name_index)), evp -> ElementValueRecord.ofClassFileElementValue(evp.value, p)))); + } + } + + 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 static BootstrapMethodRecord ofClassFileBootstrapMethodSpecifier(com.sun.tools.classfile.BootstrapMethods_attribute.BootstrapMethodSpecifier bm, com.sun.tools.classfile.ConstantPool p) { + return new BootstrapMethodRecord( + ConstantPoolEntryRecord.ofClassFileCPInfo(wrapException(() -> p.get(bm.bootstrap_method_ref)), p), + IntStream.of(bm.bootstrap_arguments).boxed().map(wrapException(i -> ConstantPoolEntryRecord.ofClassFileCPInfo(p.get(i), p))).toList()); + } + } + + public record CharacterRangeRecord( + int startIndex, + int endIndex, + int characterRangeStart, + int characterRangeEnd, + int flags) { + + public static CharacterRangeRecord ofCharacterRange(CharacterRange cr, LabelResolver 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()); + } + + public static CharacterRangeRecord ofClassFileCRTEntry(com.sun.tools.classfile.CharacterRangeTable_attribute.Entry cre, CodeNormalizerHelper code) { + return new CharacterRangeRecord(code.targetIndex(cre.start_pc), code.targetIndex(cre.end_pc + 1), cre.character_range_start, cre.character_range_end, cre.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, LabelResolver 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, LabelResolver 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(((CodeModel)a)::elementStream, codeHelper, a), + a.exceptionHandlers().stream().map(eh -> ExceptionHandlerRecord.ofExceptionCatch(eh, codeHelper, (LabelResolver)a)).collect(toSet()), + CodeAttributesRecord.ofAttributes(((CodeModel) a)::attributes, codeHelper, a, cf)); + } + + public static CodeRecord ofClassFileCodeAttribute(com.sun.tools.classfile.Code_attribute a, com.sun.tools.classfile.ConstantPool p, CompatibilityFilter... cf) { + var codeHelper = new CodeNormalizerHelper(a.code); + return new CodeRecord( + By_ClassBuilder.isNotDirectlyComparable(cf, a.max_stack), + By_ClassBuilder.isNotDirectlyComparable(cf, a.max_locals), + By_ClassBuilder.isNotDirectlyComparable(cf, a.code_length), + ((Supplier>) (() -> { + ArrayList insList = new ArrayList<>(); + for (com.sun.tools.classfile.Instruction ins : a.getInstructions()) { + String opCode[] = {opcodeMask(ins.getOpcode().name())}; + Integer hash = ins.accept(new com.sun.tools.classfile.Instruction.KindVisitor() { + + private int parametersHash(com.sun.tools.classfile.Instruction instr) { + return codeHelper.hash(instr.getPC() + 1, instr.length()); + } + + @Override + public Integer visitNoOperands(com.sun.tools.classfile.Instruction instr, Void v) { + return null; + } + + @Override + public Integer visitArrayType(com.sun.tools.classfile.Instruction instr, com.sun.tools.classfile.Instruction.TypeKind kind, Void v) { + return new ConstantPoolEntryRecord.CpClassRecord("[" + (" ZCFDBSIJ".charAt(kind.value))).hashCode() + 1; + } + + @Override + public Integer visitBranch(com.sun.tools.classfile.Instruction instr, int offset, Void v) { + return codeHelper.targetIndex(instr.getPC() + offset); + } + + @Override + public Integer visitConstantPoolRef(com.sun.tools.classfile.Instruction instr, int index, Void v) { + var cper = ConstantPoolEntryRecord.ofClassFileCPInfo(wrapException(() -> p.get(index)), p); + String altOpcode; + if (opCode[0].startsWith("LDC") && (altOpcode = cper.altOpcode()) != null) { + opCode[0] = altOpcode; + return null; + } else if (opCode[0].startsWith("NEWARRAY")) { + String type = ((ConstantPoolEntryRecord.CpClassRecord)cper).cpClass; + if (!type.startsWith("[")) type = "L" + type + ";"; + return new ConstantPoolEntryRecord.CpClassRecord("[" + type).hashCode() + 1; + } else { + return cper.hashCode(); + } + } + + @Override + public Integer visitConstantPoolRefAndValue(com.sun.tools.classfile.Instruction instr, int index, int value, Void v) { + return ConstantPoolEntryRecord.ofClassFileCPInfo(wrapException(() -> p.get(index)), p).hashCode() + (instr.getOpcode() == com.sun.tools.classfile.Opcode.INVOKEINTERFACE ? 0 : value); + } + + @Override + public Integer visitLocal(com.sun.tools.classfile.Instruction instr, int index, Void v) { + return index; + } + + @Override + public Integer visitLocalAndValue(com.sun.tools.classfile.Instruction instr, int index, int value, Void v) { + return parametersHash(instr); + } + + @Override + public Integer visitLookupSwitch(com.sun.tools.classfile.Instruction instr, int default_, int npairs, int[] matches, int[] offsets, Void v) { + return codeHelper.multipleTargetsHash(instr.getPC(), default_, offsets, matches); + } + + @Override + public Integer visitTableSwitch(com.sun.tools.classfile.Instruction instr, int default_, int low, int high, int[] offsets, Void v) { + return codeHelper.multipleTargetsHash(instr.getPC(), default_, Arrays.stream(offsets).filter(o -> o != default_).toArray(), low, high); + } + + @Override + public Integer visitValue(com.sun.tools.classfile.Instruction instr, int value, Void v) { + return value; + } + + @Override + public Integer visitUnknown(com.sun.tools.classfile.Instruction instr, Void v) { + return parametersHash(instr); + } + }, null); + insList.add(opCode[0] + (hash != null ? '#' + Integer.toHexString(hash & 0xfff) : "")); + } + return insList; + })).get(), + Stream.of(a.exception_table).map(eh -> ExceptionHandlerRecord.ofClassFileExceptionData(eh, codeHelper, p)).collect(toSet()), + CodeAttributesRecord.ofClassFileAttributes(a.attributes, p, codeHelper, cf)); + } + + public record ExceptionHandlerRecord( + int startIndex, + int endIndex, + int handlerIndex, + ConstantPoolEntryRecord catchType) { + + public static ExceptionHandlerRecord ofExceptionCatch(ExceptionCatch et, CodeNormalizerHelper code, LabelResolver 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 static ExceptionHandlerRecord ofClassFileExceptionData(com.sun.tools.classfile.Code_attribute.Exception_data ed, CodeNormalizerHelper code, com.sun.tools.classfile.ConstantPool p) { + return new ExceptionHandlerRecord( + code.targetIndex(ed.start_pc), + code.targetIndex(ed.end_pc), + code.targetIndex(ed.handler_pc), + wrapException(() -> ed.catch_type == 0 ? null : ConstantPoolEntryRecord.ofClassFileCPInfo(p.get(ed.catch_type), p))); + } + } + } + + 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 static EnclosingMethodRecord ofClassFileEnclosingMethodAttribute(com.sun.tools.classfile.EnclosingMethod_attribute ema, com.sun.tools.classfile.ConstantPool p) { + return wrapException(() -> new EnclosingMethodRecord( + ema.getClassName(p), + ema.method_index == 0 ? null : ConstantPoolEntryRecord.ofClassFileCPInfo(p.get(ema.method_index), p))); + } + } + + 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 static InnerClassRecord ofClassFileICInfo(com.sun.tools.classfile.InnerClasses_attribute.Info ic, com.sun.tools.classfile.ConstantPool p) { + return wrapException(() -> new InnerClassRecord( + ic.getInnerClassInfo(p).getName(), + ic.getInnerName(p), + map(ic.getOuterClassInfo(p), ici -> ici.getName()), + Flags.toString(ic.inner_class_access_flags.flags, 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, LabelResolver 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 static LocalVariableRecord ofClassFileLVTEntry(com.sun.tools.classfile.LocalVariableTable_attribute.Entry lv, CodeNormalizerHelper code, com.sun.tools.classfile.ConstantPool p) { + return wrapException(() -> new LocalVariableRecord( + code.targetIndex(lv.start_pc), + code.targetIndex(lv.start_pc + lv.length), + p.getUTF8Value(lv.name_index), + p.getUTF8Value(lv.descriptor_index), + lv.index)); + } + } + + public record LocalVariableTypeRecord( + int startIndex, + int endIndex, + String name, + String signature, + int index) { + + public static LocalVariableTypeRecord ofLocalVariableType(LocalVariableType lvt, LabelResolver 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 static LocalVariableTypeRecord ofClassFileLVTTEntry(com.sun.tools.classfile.LocalVariableTypeTable_attribute.Entry lv, CodeNormalizerHelper code, com.sun.tools.classfile.ConstantPool p) { + return wrapException(() -> new LocalVariableTypeRecord( + code.targetIndex(lv.start_pc), + code.targetIndex(lv.start_pc + lv.length), + p.getUTF8Value(lv.name_index), + p.getUTF8Value(lv.signature_index), + lv.index)); + } + } + + 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 static MethodParameterRecord ofClassFileMPEntry(com.sun.tools.classfile.MethodParameters_attribute.Entry mp, com.sun.tools.classfile.ConstantPool p) { + return new MethodParameterRecord(mp.name_index == 0 ? null : wrapException(() -> p.getUTF8Value(mp.name_index)), mp.flags); + } + } + + 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 static ModuleRecord ofClassFileModuleAttribute(com.sun.tools.classfile.Module_attribute m, com.sun.tools.classfile.ConstantPool p) { + return wrapException(() -> new ModuleRecord( + p.getModuleInfo(m.module_name).getName(), + m.module_flags, + m.module_version_index == 0 ? null : p.getUTF8Value(m.module_version_index), + Stream.of(m.requires).map(re -> RequiresRecord.ofClassFileRequiresEntry(re, p)).collect(toSet()), + Stream.of(m.exports).map(ee -> ExportsRecord.ofClassFileExportsEntry(ee, p)).collect(toSet()), + Stream.of(m.opens).map(oe -> OpensRecord.ofClassFileOpensEntry(oe, p)).collect(toSet()), + Arrays.stream(m.uses_index).boxed().map(wrapException(ui -> p.getClassInfo(ui).getName())).collect(toSet()), + Stream.of(m.provides).map(pe -> ProvidesRecord.ofClassFileProvidesEntry(pe, 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 static RequiresRecord ofClassFileRequiresEntry(com.sun.tools.classfile.Module_attribute.RequiresEntry re, com.sun.tools.classfile.ConstantPool p) { + return wrapException(() -> new RequiresRecord( + p.getModuleInfo(re.requires_index).getName(), + re.requires_flags, + re.requires_version_index == 0 ? null : p.getUTF8Value(re.requires_version_index))); + } + } + + 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 static ExportsRecord ofClassFileExportsEntry(com.sun.tools.classfile.Module_attribute.ExportsEntry ee, com.sun.tools.classfile.ConstantPool p) { + return new ExportsRecord( + wrapException(() -> p.getPackageInfo(ee.exports_index).getName()), + ee.exports_flags, + Arrays.stream(ee.exports_to_index).boxed().map(wrapException(i -> p.getModuleInfo(i).getName())).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 static OpensRecord ofClassFileOpensEntry(com.sun.tools.classfile.Module_attribute.OpensEntry oe, com.sun.tools.classfile.ConstantPool p) { + return new OpensRecord( + wrapException(() -> p.getPackageInfo(oe.opens_index).getName()), + oe.opens_flags, + Arrays.stream(oe.opens_to_index).boxed().map(wrapException(i -> p.getModuleInfo(i).getName())).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 static ProvidesRecord ofClassFileProvidesEntry(com.sun.tools.classfile.Module_attribute.ProvidesEntry pe, com.sun.tools.classfile.ConstantPool p) { + return new ProvidesRecord( + wrapException(() -> p.getClassInfo(pe.provides_index).getName()), + Arrays.stream(pe.with_index).boxed().map(wrapException(w -> p.getClassInfo(w).getName())).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 static ModuleHashesRecord ofClassFileModuleHashesAttribute(com.sun.tools.classfile.ModuleHashes_attribute mh, com.sun.tools.classfile.ConstantPool p) { + return new ModuleHashesRecord( + wrapException(() -> p.getUTF8Value(mh.algorithm_index)), + Stream.of(mh.hashes_table).collect(toMap(wrapException(e -> p.getModuleInfo(e.module_name_index).getName()), 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 static RecordComponentRecord ofClassFileComponentInfo(com.sun.tools.classfile.Record_attribute.ComponentInfo ci, com.sun.tools.classfile.ConstantPool p, CompatibilityFilter... compatibilityFilter) { + return wrapException(() -> new RecordComponentRecord( + ci.getName(p), + ci.descriptor.getValue(p), + AttributesRecord.ofClassFileAttributes(ci.attributes, p, 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, LabelResolver 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 static TypeAnnotationRecord ofClassFileTypeAnnotation(com.sun.tools.classfile.TypeAnnotation ann, com.sun.tools.classfile.ConstantPool p, CodeNormalizerHelper code) { + return new TypeAnnotationRecord( + ann.position.type.targetTypeValue(), + TargetInfoRecord.ofClassFilePossition(ann.position, code), + ann.position.location.stream().map(tpe -> TypePathRecord.ofClassFileTypePathEntry(tpe)).collect(toSet()), + AnnotationRecord.ofClassFileAnnotation(ann.annotation, p)); + } + + public interface TargetInfoRecord { + + public static TargetInfoRecord ofTargetInfo(TypeAnnotation.TargetInfo tiu, LabelResolver 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 static TargetInfoRecord ofClassFilePossition(com.sun.tools.classfile.TypeAnnotation.Position pos, CodeNormalizerHelper code) { + return switch (pos.type) { + case INSTANCEOF: + case NEW: + case CONSTRUCTOR_REFERENCE: + case METHOD_REFERENCE: + yield new OffsetTargetRecord(code.targetIndex(pos.offset)); + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + yield new LocalVarTargetRecord(IntStream.range(0, pos.lvarOffset.length).mapToObj(i + -> new LocalVarTargetRecord.EntryRecord(code.targetIndex(pos.lvarOffset[i]), code.targetIndex(pos.lvarOffset[i] + pos.lvarLength[i]), pos.lvarIndex[i])).collect(toSet())); + case EXCEPTION_PARAMETER: + yield new CatchTargetRecord(pos.exception_index); + case CLASS_TYPE_PARAMETER: + case METHOD_TYPE_PARAMETER: + yield new TypeParameterTargetRecord(pos.parameter_index); + case CLASS_TYPE_PARAMETER_BOUND: + case METHOD_TYPE_PARAMETER_BOUND: + yield new TypeParameterBoundTargetRecord(pos.parameter_index, pos.bound_index); + case CLASS_EXTENDS: + yield new SupertypeTargetRecord(pos.type_index); + case THROWS: + yield new ThrowsTargetRecord(pos.type_index); + case METHOD_FORMAL_PARAMETER: + yield new FormalParameterTargetRecord(pos.parameter_index); + case CAST: + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case METHOD_INVOCATION_TYPE_ARGUMENT: + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case METHOD_REFERENCE_TYPE_ARGUMENT: + yield new TypeArgumentTargetRecord(code.targetIndex(pos.offset), pos.type_index); + case METHOD_RECEIVER: + case METHOD_RETURN: + case FIELD: + yield new EmptyTargetRecord(); + case UNKNOWN: + throw new IllegalArgumentException(pos.toString()); + }; + } + + 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 static TypePathRecord ofClassFileTypePathEntry(com.sun.tools.classfile.TypeAnnotation.Position.TypePathEntry tpe) { + return new TypePathRecord(tpe.tag.tag, tpe.arg); + } + } + } + + 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())); + }; + } + + public static ConstantPoolEntryRecord ofClassFileCPInfo(com.sun.tools.classfile.ConstantPool.CPInfo cpInfo, com.sun.tools.classfile.ConstantPool p) { + return cpInfo.accept(new com.sun.tools.classfile.ConstantPool.Visitor() { + @Override + public ConstantPoolEntryRecord visitClass(com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info info, Void v) { + return wrapException(() -> new CpClassRecord(info.getName())); + } + + @Override + public ConstantPoolEntryRecord visitDouble(com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info info, Void v) { + return new CpDoubleRecord(info.value); + } + + @Override + public ConstantPoolEntryRecord visitFieldref(com.sun.tools.classfile.ConstantPool.CONSTANT_Fieldref_info info, Void v) { + return wrapException(() -> new CpFieldRefRecord( + info.getClassName(), + info.getNameAndTypeInfo().getName(), + info.getNameAndTypeInfo().getType())); + } + + @Override + public ConstantPoolEntryRecord visitFloat(com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info info, Void v) { + return new CpFloatRecord(info.value); + } + + @Override + public ConstantPoolEntryRecord visitInteger(com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info info, Void v) { + return new CpIntegerRecord(info.value); + } + + @Override + public ConstantPoolEntryRecord visitInterfaceMethodref(com.sun.tools.classfile.ConstantPool.CONSTANT_InterfaceMethodref_info info, Void v) { + return wrapException(() -> new CpInterfaceMethodRefRecord( + info.getClassName(), + info.getNameAndTypeInfo().getName(), + info.getNameAndTypeInfo().getType())); + } + + @Override + public ConstantPoolEntryRecord visitInvokeDynamic(com.sun.tools.classfile.ConstantPool.CONSTANT_InvokeDynamic_info info, Void v) { + return wrapException(() -> new CpInvokeDynamicRecord( + info.getNameAndTypeInfo().getName(), + info.getNameAndTypeInfo().getType())); + } + + @Override + public ConstantPoolEntryRecord visitDynamicConstant(com.sun.tools.classfile.ConstantPool.CONSTANT_Dynamic_info info, Void v) { + return wrapException(() -> new CpConstantDynamicRecord( + info.getNameAndTypeInfo().getName(), + info.getNameAndTypeInfo().getType())); + } + + @Override + public ConstantPoolEntryRecord visitLong(com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info info, Void v) { + return new CpLongRecord(info.value); + } + + @Override + public ConstantPoolEntryRecord visitMethodref(com.sun.tools.classfile.ConstantPool.CONSTANT_Methodref_info info, Void v) { + return wrapException(() -> new CpMethodRefRecord( + info.getClassName(), + info.getNameAndTypeInfo().getName(), + info.getNameAndTypeInfo().getType())); + } + + @Override + public ConstantPoolEntryRecord visitMethodHandle(com.sun.tools.classfile.ConstantPool.CONSTANT_MethodHandle_info info, Void v) { + return wrapException(() -> new CpMethodHandleRecord( + ConstantPoolEntryRecord.ofClassFileCPInfo(info.getCPRefInfo(), p), + info.reference_kind.tag)); + } + + @Override + public ConstantPoolEntryRecord visitMethodType(com.sun.tools.classfile.ConstantPool.CONSTANT_MethodType_info info, Void v) { + return wrapException(() -> new CpMethodTypeRecord(info.getType())); + } + + @Override + public ConstantPoolEntryRecord visitModule(com.sun.tools.classfile.ConstantPool.CONSTANT_Module_info info, Void v) { + return wrapException(() -> new CpModuleRecord(info.getName())); + } + + @Override + public ConstantPoolEntryRecord visitNameAndType(com.sun.tools.classfile.ConstantPool.CONSTANT_NameAndType_info info, Void v) { + return wrapException(() -> new CpNameAndTypeRecord( + info.getName(), + info.getType())); + } + + @Override + public ConstantPoolEntryRecord visitPackage(com.sun.tools.classfile.ConstantPool.CONSTANT_Package_info info, Void v) { + return wrapException(() -> new CpPackageRecord(info.getName())); + } + + @Override + public ConstantPoolEntryRecord visitString(com.sun.tools.classfile.ConstantPool.CONSTANT_String_info info, Void v) { + return wrapException(() -> new CpStringRecord(info.getString())); + } + + @Override + public ConstantPoolEntryRecord visitUtf8(com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info info, Void v) { + return new CpUTF8Record(info.value); + } + }, null); + } + + 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 static ElementValueRecord ofClassFileElementValue(com.sun.tools.classfile.Annotation.element_value ev, com.sun.tools.classfile.ConstantPool p) { + return ev.accept(new com.sun.tools.classfile.Annotation.element_value.Visitor() { + @Override + public ElementValueRecord visitPrimitive(com.sun.tools.classfile.Annotation.Primitive_element_value ev, Void v) { + return new EvConstRecord(ev.tag, ConstantPoolEntryRecord.ofClassFileCPInfo(wrapException(() -> p.get(ev.const_value_index)), p)); + } + + @Override + public ElementValueRecord visitEnum(com.sun.tools.classfile.Annotation.Enum_element_value ev, Void v) { + return wrapException(() -> new EvEnumConstRecord(ev.tag, p.getUTF8Value(ev.type_name_index), p.getUTF8Value(ev.const_name_index))); + } + + @Override + public ElementValueRecord visitClass(com.sun.tools.classfile.Annotation.Class_element_value ev, Void v) { + return new EvClassRecord(ev.tag, wrapException(() -> p.getUTF8Value(ev.class_info_index))); + } + + @Override + public ElementValueRecord visitAnnotation(com.sun.tools.classfile.Annotation.Annotation_element_value ev, Void v) { + return new EvAnnotationRecord(ev.tag, AnnotationRecord.ofClassFileAnnotation(ev.annotation_value, p)); + } + + @Override + public ElementValueRecord visitArray(com.sun.tools.classfile.Annotation.Array_element_value ev, Void v) { + return new EvArrayRecord(ev.tag, Stream.of(ev.values).map(e -> ElementValueRecord.ofClassFileElementValue(e, p)).toList()); + } + }, null); + } + + 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); + } + }; + } + + private static ClassRecord readClassRecord(Path path, String thisSwitch, String otherClassSwitch) throws IOException, com.sun.tools.classfile.ConstantPoolException { + var filter = "-b".equals(otherClassSwitch) ? CompatibilityFilter.By_ClassBuilder : CompatibilityFilter.Read_all; + return switch (thisSwitch) { + case "-m" -> ClassRecord.ofClassModel(Classfile.parse(path), filter); + case "-b" -> ClassRecord.ofClassModel(Classfile.parse(path), CompatibilityFilter.By_ClassBuilder); + case "-s" -> ClassRecord.ofStreamingElements(Classfile.parse(path), filter); + case "-f" -> ClassRecord.ofClassFile(com.sun.tools.classfile.ClassFile.read(path), filter); + default -> throw new IllegalArgumentException("Unknown command line option: " + thisSwitch); + }; + } + + public static void main(String args[]) throws Exception { + try { + Iterator cmd = Arrays.asList(args).iterator(); + var switches = new String[2]; + var paths = new Path[2]; + for (int i=0; i < 2; i++) { + switches[i] = cmd.next(); + if (switches[i].charAt(0) == '-') { + paths[i] = Path.of(cmd.next()); + } else { + paths[i] = Path.of(switches[i]); + switches[i] = "-m"; + } + } + assertEqualsDeep( + readClassRecord(paths[0], switches[0], switches[1]), + readClassRecord(paths[1], switches[1], switches[0]), + paths[0].getFileName().toString() + " [" + switches[0] + "] (actual) vs " + paths[1].getFileName().toString() + " [" + switches[1] + "] (expected)"); + System.out.println("no differences found"); + } catch (AssertionError e) { + System.err.println(e.getLocalizedMessage()); + System.exit(1); + } catch (NoSuchElementException e) { + System.err.println( + """ + missing argument(s) + usage: java --enable-preview -jar bytecode-lib-1.0-SNAPSHOT-tests.jar [-m | -b | -s | -f] [-m | -b | -s | -f] + + options: + -m following class is analyzed from ClassModel (default) + -b following class is analyzed from ClassModel, filtering only features comparable after passing through ClassBuilder + -s following class is analyzed from streaming Elements + -f following class is analyzed from com.sun.tools.classfile.ClassFile (requires JVM option --add-exports jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED) + + examples: + java --enable-preview -jar bytecode-lib-1.0-SNAPSHOT-tests.jar MyClass.class MySecondClass.class + - analyzes MyClass.class and MySecondClass.class files by ClassModel, asserts their equality and prints differences + + java --enable-preview --add-exports jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED -jar bytecode-lib-1.0-SNAPSHOT-tests.jar -s MyClass.class -f MyClass.class + - analyzes MyClass.class file from streaming Elements and compares it with the MyClass.class analyzed from com.sun.tools.classfile.ClassFile + """); + System.exit(1); + } + } +} diff --git a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java new file mode 100644 index 0000000000000..2bbc77ecca702 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.Classfile; +import jdk.classfile.impl.UnboundAttribute; +import jdk.classfile.instruction.LineNumber; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; +import org.testng.ITest; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +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 java.util.stream.Stream; +import jdk.classfile.BufWriter; +import jdk.classfile.ClassTransform; +import jdk.classfile.Attributes; +import jdk.classfile.impl.DirectCodeBuilder; + +public class CorpusTestHelper implements ITest { + + protected static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); + protected static final String testFilter = null; //"modules/java.base/java/util/function/Supplier.class"; + + static void splitTableAttributes(String sourceClassFile, String targetClassFile) throws IOException, URISyntaxException { + var root = Paths.get(URI.create(CorpusTestHelper.class.getResource("CorpusTestHelper.class").toString())).getParent().getParent(); + Files.write(root.resolve(targetClassFile), Classfile.parse(root.resolve(sourceClassFile)).transform(ClassTransform.transformingMethodBodies((cob, coe) -> { + 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, dcob); + } + }); + 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, dcob); + } + }); + 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.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, System.out::print).printClass(ClassModel.of(Files.readAllBytes(root.resolve(targetClassFile)))); + } + + @DataProvider(name = "corpus") + public static Object[] provide() 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(CorpusTestHelper.class.getResource("CorpusTestHelper.class").toString())).getParent().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(); + } + + + protected String testMethod = ""; + protected final Path path; + protected final byte[] bytes; + + public CorpusTestHelper(Path path) throws IOException { + this.path = path; + this.bytes = Files.readAllBytes(path); + } + + @BeforeMethod + public void handleTestMethodName(Method method) { + testMethod = method.getName(); + } + + @Override + public String getTestName() { + return testMethod + "[" + path.toString() + "]"; + } +} diff --git a/test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.java b/test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.java new file mode 100644 index 0000000000000..5d261875f0fa4 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/InstructionModelToCodeBuilder.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 helpers; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.MethodTypeDesc; + +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.instruction.ArrayLoadInstruction; +import jdk.classfile.instruction.ArrayStoreInstruction; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.ConstantInstruction; +import jdk.classfile.instruction.ConvertInstruction; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.FieldInstruction; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.InvokeDynamicInstruction; +import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.LabelTarget; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.MonitorInstruction; +import jdk.classfile.instruction.NewMultiArrayInstruction; +import jdk.classfile.instruction.NewObjectInstruction; +import jdk.classfile.instruction.NewPrimitiveArrayInstruction; +import jdk.classfile.instruction.NewReferenceArrayInstruction; +import jdk.classfile.instruction.OperatorInstruction; +import jdk.classfile.instruction.ReturnInstruction; +import jdk.classfile.instruction.StackInstruction; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.instruction.TypeCheckInstruction; + +public class InstructionModelToCodeBuilder { + + public static void toBuilder(CodeElement model, CodeBuilder cb) { + switch (model.codeKind()) { + case LOAD: { + LoadInstruction im = (LoadInstruction) model; + cb.loadInstruction(im.typeKind(), im.slot()); + return; + } + case STORE: { + StoreInstruction im = (StoreInstruction) model; + cb.storeInstruction(im.typeKind(), im.slot()); + return; + } + case INCREMENT: { + IncrementInstruction im = (IncrementInstruction) model; + cb.incrementInstruction(im.slot(), im.constant()); + return; + } + case BRANCH: { + BranchInstruction im = (BranchInstruction) model; + cb.branchInstruction(im.opcode(), im.target()); + return; + } + case LOOKUP_SWITCH: { + LookupSwitchInstruction im = (LookupSwitchInstruction) model; + cb.lookupSwitchInstruction(im.defaultTarget(), im.cases()); + return; + } + case TABLE_SWITCH: { + TableSwitchInstruction im = (TableSwitchInstruction) model; + cb.tableSwitchInstruction(im.lowValue(), im.highValue(), im.defaultTarget(), im.cases()); + return; + } + case RETURN: { + ReturnInstruction im = (ReturnInstruction) model; + cb.returnInstruction(im.typeKind()); + return; + } + case THROW_EXCEPTION: { + cb.throwInstruction(); + return; + } + case FIELD_ACCESS: { + FieldInstruction im = (FieldInstruction) model; + cb.fieldInstruction(im.opcode(), im.owner().asSymbol(), im.name().stringValue(), im.typeSymbol()); + return; + } + case INVOKE: { + InvokeInstruction im = (InvokeInstruction) model; + cb.invokeInstruction(im.opcode(), im.owner().asSymbol(), im.name().stringValue(), im.typeSymbol(), im.isInterface()); + return; + } + case INVOKE_DYNAMIC: { + InvokeDynamicInstruction im = (InvokeDynamicInstruction) model; + cb.invokeDynamicInstruction(DynamicCallSiteDesc.of(im.bootstrapMethod(), im.name().stringValue(), MethodTypeDesc.ofDescriptor(im.type().stringValue()), im.bootstrapArgs().toArray(ConstantDesc[]::new))); + return; + } + case NEW_OBJECT: { + NewObjectInstruction im = (NewObjectInstruction) model; + cb.newObjectInstruction(im.className().asSymbol()); + return; + } + case NEW_PRIMITIVE_ARRAY: + cb.newPrimitiveArrayInstruction(((NewPrimitiveArrayInstruction) model).typeKind()); + return; + + case NEW_REF_ARRAY: + cb.newReferenceArrayInstruction(((NewReferenceArrayInstruction) model).componentType()); + return; + + case NEW_MULTI_ARRAY: { + NewMultiArrayInstruction im = (NewMultiArrayInstruction) model; + cb.newMultidimensionalArrayInstruction(im.dimensions(), im.arrayType()); + return; + } + + case TYPE_CHECK: { + TypeCheckInstruction im = (TypeCheckInstruction) model; + cb.typeCheckInstruction(im.opcode(), im.type().asSymbol()); + return; + } + case ARRAY_LOAD: { + ArrayLoadInstruction im = (ArrayLoadInstruction) model; + cb.arrayLoadInstruction(im.typeKind()); + return; + } + case ARRAY_STORE: { + ArrayStoreInstruction im = (ArrayStoreInstruction) model; + cb.arrayStoreInstruction(im.typeKind()); + return; + } + case STACK: { + StackInstruction im = (StackInstruction) model; + cb.stackInstruction(im.opcode()); + return; + } + case CONVERT: { + ConvertInstruction im = (ConvertInstruction) model; + cb.convertInstruction(im.fromType(), im.toType()); + return; + } + case OPERATOR: { + OperatorInstruction im = (OperatorInstruction) model; + cb.operatorInstruction(im.opcode()); + return; + } + case CONSTANT: { + ConstantInstruction im = (ConstantInstruction) model; + cb.constantInstruction(im.opcode(), im.constantValue()); + return; + } + case MONITOR: { + MonitorInstruction im = (MonitorInstruction) model; + cb.monitorInstruction(im.opcode()); + return; + } + case NOP: { + cb.nopInstruction(); + return; + } + case LABEL_TARGET: { + LabelTarget im = (LabelTarget) model; + cb.labelBinding(im.label()); + return; + } + case EXCEPTION_CATCH: { + ExceptionCatch im = (ExceptionCatch) model; + cb.exceptionCatch(im.tryStart(), im.tryEnd(), im.handler(), im.catchType()); + return; + } + case LOCAL_VARIABLE: { + throw new IllegalArgumentException("not yet implemented: " + model); + } + case LINE_NUMBER: { + throw new IllegalArgumentException("not yet implemented: " + model); + } + } + } +} 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..fdddec166411a --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/TestUtil.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. + */ +package helpers; + +import com.sun.tools.classfile.ConstantPool; +import com.sun.tools.classfile.LocalVariableTable_attribute; +import com.sun.tools.classfile.LocalVariableTypeTable_attribute; +import jdk.classfile.impl.LabelContext; +import jdk.classfile.impl.LabelImpl; +import jdk.classfile.instruction.LocalVariable; +import jdk.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; + ConstantPool cp; + + ExpectedLvRecord(int slot, String name, String desc, int start, int length, ConstantPool cp) { + this.slot = slot; + this.name = name; + this.desc = desc; + this.start = start; + this.length = length; + this.cp = cp; + } + + @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; + } + + if (other instanceof LocalVariableTable_attribute.Entry e) { + try { + if (!(slot == e.index && + name.equals(cp.getUTF8Value(e.name_index)) && + desc.equals(cp.getUTF8Value(e.descriptor_index)) && + start == e.start_pc && + length == e.length)) throw new RuntimeException(); + return slot == e.index && + name.equals(cp.getUTF8Value(e.name_index)) && + desc.equals(cp.getUTF8Value(e.descriptor_index)) && + start == e.start_pc && + length == e.length; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + 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, null); + } + + public static ExpectedLvRecord of(int slot, String name, String desc, int start, int length, ConstantPool cp) { + return new ExpectedLvRecord(slot, name, desc, start, length, cp); + } + } + + public static class ExpectedLvtRecord { + int slot; + String signature; + String name; + int start; + int length; + ConstantPool cp; + + ExpectedLvtRecord(int slot, String name, String signature, int start, int length, ConstantPool cp) { + this.slot = slot; + this.name = name; + this.signature = signature; + this.start = start; + this.length = length; + this.cp = cp; + } + + @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; + } + + if (other instanceof LocalVariableTypeTable_attribute.Entry e) { + try { + return slot == e.index && + name.equals(cp.getUTF8Value(e.name_index)) && + signature.equals(cp.getUTF8Value(e.signature_index)) && + start == e.start_pc && + length == e.length; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + 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, null); + } + + public static ExpectedLvtRecord of(int slot, String name, String signature, int start, int length, ConstantPool cp) { + return new ExpectedLvtRecord(slot, name, signature, start, length, cp); + } + + 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..9b6c1b28c3cce --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/Transforms.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.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 com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ConstantPoolException; +import java.lang.constant.ConstantDescs; +import java.util.stream.Stream; + +import jdk.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.MethodModel; +import jdk.classfile.MethodTransform; +import jdk.classfile.transforms.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 Optional classRecord(byte[] bytes) throws IOException, ConstantPoolException { + return switch (this) { + case ARRAYCOPY -> Optional.of(ClassRecord.ofClassFile(ClassFile.read(new ByteArrayInputStream(bytes)))); + case SHARED_1, SHARED_2, SHARED_3, + UNSHARED_1, UNSHARED_2, UNSHARED_3 + -> 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 100755 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 100755 index 0000000000000..32615f539f53e --- /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.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.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 100755 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 100755 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 100755 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/ParseOptions.java b/test/micro/org/openjdk/bench/jdk/classfile/ParseOptions.java new file mode 100755 index 0000000000000..e7a3530049840 --- /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.classfile.ClassModel; +import jdk.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 100755 index 0000000000000..0a7e9c3231dad --- /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.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.classfile.ClassfileElement; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.CompoundElement; +import jdk.classfile.MethodModel; +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.codeKind() == CodeElement.Kind.LOAD) { + ++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 100755 index 0000000000000..66858bfd88888 --- /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 jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.Classfile; +import jdk.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..acea89f2cb358 --- /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.classfile.ClassBuilder; +import jdk.classfile.ClassElement; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.MethodModel; +import jdk.classfile.MethodTransform; +import jdk.classfile.transforms.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 100755 index 0000000000000..48d8472829956 --- /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.classfile.AccessFlags; +import jdk.classfile.jdktypes.AccessFlag; +import jdk.classfile.Classfile; +import jdk.classfile.TypeKind; +import jdk.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.classfile.Opcode.*; +import static jdk.classfile.TypeKind.*; +import static jdk.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.classfile.Label loopTop = c0.newLabel(); + jdk.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.classfile.Label loopTop = c0.newLabel(); + jdk.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); + } + } +} + From 6663f05bc8cf38d89ac87454ffbf1f8a2832f72b Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 13 Jun 2022 08:43:42 +0200 Subject: [PATCH 002/190] fix of javadoc link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 17b2c2b90fd1d..6bfa00d33bd2c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Provide an API for parsing, generating, and transforming Java class files. This will initially serve as an internal replacement for ASM in the JDK, to be later opened as a public API. See [JEP ???](https://bugs.openjdk.java.net/browse/JDK-8280389) -or [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openjdk/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/Classfile_20Processing_20API/) +or [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/asotona/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/jdk/classfile/package-summary.html) for more information about Classfile Processing API. See for more information about From 63d0e3ba015b89de42bc01375175691e4aa5db67 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 13 Jun 2022 14:08:51 +0200 Subject: [PATCH 003/190] fixed ClassPrinterTest on Windows --- test/jdk/jdk/classfile/ClassPrinterTest.java | 605 ++++++++++--------- 1 file changed, 306 insertions(+), 299 deletions(-) diff --git a/test/jdk/jdk/classfile/ClassPrinterTest.java b/test/jdk/jdk/classfile/ClassPrinterTest.java index b1ca7d572a160..b75cfe1cadbce 100644 --- a/test/jdk/jdk/classfile/ClassPrinterTest.java +++ b/test/jdk/jdk/classfile/ClassPrinterTest.java @@ -61,352 +61,359 @@ ClassModel getClassModel() { public void testPrintYamlTraceAll() throws IOException { var out = new StringBuilder(); ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); - assertEquals(out.toString(), -""" - - class name: 'Foo' - version: '61.0' - flags: [PUBLIC] - superclass: 'Boo' - interfaces: ['Phee', 'Phoo'] - attributes: [SourceFile] - constant pool: - 1: [CONSTANT_Utf8, 'Foo'] - 2: [CONSTANT_Class, {name index: 1, name: 'Foo'}] - 3: [CONSTANT_Utf8, 'Boo'] - 4: [CONSTANT_Class, {name index: 3, name: 'Boo'}] - 5: [CONSTANT_Utf8, 'f'] - 6: [CONSTANT_Utf8, 'Ljava/lang/String;'] - 7: [CONSTANT_Utf8, 'm'] - 8: [CONSTANT_Utf8, '(ZLjava/lang/Throwable;)Ljava/lang/Void;'] - 9: [CONSTANT_Utf8, 'Phee'] - 10: [CONSTANT_Class, {name index: 9, name: 'Phee'}] - 11: [CONSTANT_Utf8, 'Phoo'] - 12: [CONSTANT_Class, {name index: 11, name: 'Phoo'}] - 13: [CONSTANT_Utf8, 'Code'] - 14: [CONSTANT_Utf8, 'StackMapTable'] - 15: [CONSTANT_Utf8, 'SourceFile'] - 16: [CONSTANT_Utf8, 'Foo.java'] - source: 'Foo.java' - fields: - - field name: 'f' - flags: [PRIVATE] - descriptor: 'Ljava/lang/String;' - attributes: [] - methods: - - method name: 'm' - flags: [PROTECTED] - descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' - attributes: [Code] - code: - max stack: 1 - max locals: 3 - attributes: [StackMapTable] - stack map frames: - 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 0: [ILOAD_1, {slot: 1}] - 1: [IFEQ, {target: 6}] - 4: [ALOAD_2, {slot: 2}] - 5: [ATHROW] - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 6: [RETURN] -"""); + assertOut(out, + """ + - class name: 'Foo' + version: '61.0' + flags: [PUBLIC] + superclass: 'Boo' + interfaces: ['Phee', 'Phoo'] + attributes: [SourceFile] + constant pool: + 1: [CONSTANT_Utf8, 'Foo'] + 2: [CONSTANT_Class, {name index: 1, name: 'Foo'}] + 3: [CONSTANT_Utf8, 'Boo'] + 4: [CONSTANT_Class, {name index: 3, name: 'Boo'}] + 5: [CONSTANT_Utf8, 'f'] + 6: [CONSTANT_Utf8, 'Ljava/lang/String;'] + 7: [CONSTANT_Utf8, 'm'] + 8: [CONSTANT_Utf8, '(ZLjava/lang/Throwable;)Ljava/lang/Void;'] + 9: [CONSTANT_Utf8, 'Phee'] + 10: [CONSTANT_Class, {name index: 9, name: 'Phee'}] + 11: [CONSTANT_Utf8, 'Phoo'] + 12: [CONSTANT_Class, {name index: 11, name: 'Phoo'}] + 13: [CONSTANT_Utf8, 'Code'] + 14: [CONSTANT_Utf8, 'StackMapTable'] + 15: [CONSTANT_Utf8, 'SourceFile'] + 16: [CONSTANT_Utf8, 'Foo.java'] + source: 'Foo.java' + fields: + - field name: 'f' + flags: [PRIVATE] + descriptor: 'Ljava/lang/String;' + attributes: [] + methods: + - method name: 'm' + flags: [PROTECTED] + descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' + attributes: [Code] + code: + max stack: 1 + max locals: 3 + attributes: [StackMapTable] + stack map frames: + 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 0: [ILOAD_1, {slot: 1}] + 1: [IFEQ, {target: 6}] + 4: [ALOAD_2, {slot: 2}] + 5: [ATHROW] + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 6: [RETURN] + """); } @Test public void testPrintYamlCriticalAttributes() throws IOException { var out = new StringBuilder(); ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); - assertEquals(out.toString(), -""" - - class name: 'Foo' - version: '61.0' - flags: [PUBLIC] - superclass: 'Boo' - interfaces: ['Phee', 'Phoo'] - attributes: [SourceFile] - fields: - - field name: 'f' - flags: [PRIVATE] - descriptor: 'Ljava/lang/String;' - attributes: [] - methods: - - method name: 'm' - flags: [PROTECTED] - descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' - attributes: [Code] - code: - max stack: 1 - max locals: 3 - attributes: [StackMapTable] - stack map frames: - 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 0: [ILOAD_1, {slot: 1}] - 1: [IFEQ, {target: 6}] - 4: [ALOAD_2, {slot: 2}] - 5: [ATHROW] - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 6: [RETURN] -"""); + assertOut(out, + """ + - class name: 'Foo' + version: '61.0' + flags: [PUBLIC] + superclass: 'Boo' + interfaces: ['Phee', 'Phoo'] + attributes: [SourceFile] + fields: + - field name: 'f' + flags: [PRIVATE] + descriptor: 'Ljava/lang/String;' + attributes: [] + methods: + - method name: 'm' + flags: [PROTECTED] + descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' + attributes: [Code] + code: + max stack: 1 + max locals: 3 + attributes: [StackMapTable] + stack map frames: + 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 0: [ILOAD_1, {slot: 1}] + 1: [IFEQ, {target: 6}] + 4: [ALOAD_2, {slot: 2}] + 5: [ATHROW] + #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] + 6: [RETURN] + """); } @Test public void testPrintYamlMembersOnly() throws IOException { var out = new StringBuilder(); ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); - assertEquals(out.toString(), -""" - - class name: 'Foo' - version: '61.0' - flags: [PUBLIC] - superclass: 'Boo' - interfaces: ['Phee', 'Phoo'] - attributes: [SourceFile] - fields: - - field name: 'f' - flags: [PRIVATE] - descriptor: 'Ljava/lang/String;' - attributes: [] - methods: - - method name: 'm' - flags: [PROTECTED] - descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' - attributes: [Code] -"""); + assertOut(out, + """ + - class name: 'Foo' + version: '61.0' + flags: [PUBLIC] + superclass: 'Boo' + interfaces: ['Phee', 'Phoo'] + attributes: [SourceFile] + fields: + - field name: 'f' + flags: [PRIVATE] + descriptor: 'Ljava/lang/String;' + attributes: [] + methods: + - method name: 'm' + flags: [PROTECTED] + descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' + attributes: [Code] + """); } @Test public void testPrintJsonTraceAll() throws IOException { var out = new StringBuilder(); ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); - assertEquals(out.toString().trim(), -""" -{ "class name": "Foo", - "version": "61.0", - "flags": ["PUBLIC"], - "superclass": "Boo", - "interfaces": ["Phee", "Phoo"], - "attributes": ["SourceFile"], - "constant pool": { - "1": ["CONSTANT_Utf8", "Foo"], - "2": ["CONSTANT_Class", { "name index:": 1, "name:": "Foo" }], - "3": ["CONSTANT_Utf8", "Boo"], - "4": ["CONSTANT_Class", { "name index:": 3, "name:": "Boo" }], - "5": ["CONSTANT_Utf8", "f"], - "6": ["CONSTANT_Utf8", "Ljava/lang/String;"], - "7": ["CONSTANT_Utf8", "m"], - "8": ["CONSTANT_Utf8", "(ZLjava/lang/Throwable;)Ljava/lang/Void;"], - "9": ["CONSTANT_Utf8", "Phee"], - "10": ["CONSTANT_Class", { "name index:": 9, "name:": "Phee" }], - "11": ["CONSTANT_Utf8", "Phoo"], - "12": ["CONSTANT_Class", { "name index:": 11, "name:": "Phoo" }], - "13": ["CONSTANT_Utf8", "Code"], - "14": ["CONSTANT_Utf8", "StackMapTable"], - "15": ["CONSTANT_Utf8", "SourceFile"], - "16": ["CONSTANT_Utf8", "Foo.java"] }, - "source": "Foo.java", - "fields": [ - { "field name": "f", - "flags": ["PRIVATE"], - "descriptor": "Ljava/lang/String;", - "attributes": [] }], - "methods": [ - { "method name": "m", - "flags": ["PROTECTED"], - "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", - "attributes": ["Code"], - "code": { - "max stack": 1, - "max locals": 3, - "attributes": ["StackMapTable"], - "stack map frames": { - "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, - "0": ["ILOAD_1", { "slot": 1 }], - "1": ["IFEQ", { "target": 6 }], - "4": ["ALOAD_2", { "slot": 2 }], - "5": ["ATHROW"], - "6": ["RETURN"] } }] - } -""".trim()); + assertOut(out, + """ + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile"], + "constant pool": { + "1": ["CONSTANT_Utf8", "Foo"], + "2": ["CONSTANT_Class", { "name index:": 1, "name:": "Foo" }], + "3": ["CONSTANT_Utf8", "Boo"], + "4": ["CONSTANT_Class", { "name index:": 3, "name:": "Boo" }], + "5": ["CONSTANT_Utf8", "f"], + "6": ["CONSTANT_Utf8", "Ljava/lang/String;"], + "7": ["CONSTANT_Utf8", "m"], + "8": ["CONSTANT_Utf8", "(ZLjava/lang/Throwable;)Ljava/lang/Void;"], + "9": ["CONSTANT_Utf8", "Phee"], + "10": ["CONSTANT_Class", { "name index:": 9, "name:": "Phee" }], + "11": ["CONSTANT_Utf8", "Phoo"], + "12": ["CONSTANT_Class", { "name index:": 11, "name:": "Phoo" }], + "13": ["CONSTANT_Utf8", "Code"], + "14": ["CONSTANT_Utf8", "StackMapTable"], + "15": ["CONSTANT_Utf8", "SourceFile"], + "16": ["CONSTANT_Utf8", "Foo.java"] }, + "source": "Foo.java", + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "descriptor": "Ljava/lang/String;", + "attributes": [] }], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["Code"], + "code": { + "max stack": 1, + "max locals": 3, + "attributes": ["StackMapTable"], + "stack map frames": { + "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, + "0": ["ILOAD_1", { "slot": 1 }], + "1": ["IFEQ", { "target": 6 }], + "4": ["ALOAD_2", { "slot": 2 }], + "5": ["ATHROW"], + "6": ["RETURN"] } }] + } + """); } @Test public void testPrintJsonCriticalAttributes() throws IOException { var out = new StringBuilder(); ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); - assertEquals(out.toString().trim(), -""" - { "class name": "Foo", - "version": "61.0", - "flags": ["PUBLIC"], - "superclass": "Boo", - "interfaces": ["Phee", "Phoo"], - "attributes": ["SourceFile"], - "fields": [ - { "field name": "f", - "flags": ["PRIVATE"], - "descriptor": "Ljava/lang/String;", - "attributes": [] }], - "methods": [ - { "method name": "m", - "flags": ["PROTECTED"], - "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", - "attributes": ["Code"], - "code": { - "max stack": 1, - "max locals": 3, - "attributes": ["StackMapTable"], - "stack map frames": { - "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, - "0": ["ILOAD_1", { "slot": 1 }], - "1": ["IFEQ", { "target": 6 }], - "4": ["ALOAD_2", { "slot": 2 }], - "5": ["ATHROW"], - "6": ["RETURN"] } }] - } -""".trim()); + assertOut(out, + """ + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "descriptor": "Ljava/lang/String;", + "attributes": [] }], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["Code"], + "code": { + "max stack": 1, + "max locals": 3, + "attributes": ["StackMapTable"], + "stack map frames": { + "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, + "0": ["ILOAD_1", { "slot": 1 }], + "1": ["IFEQ", { "target": 6 }], + "4": ["ALOAD_2", { "slot": 2 }], + "5": ["ATHROW"], + "6": ["RETURN"] } }] + } + """); } @Test public void testPrintJsonMembersOnly() throws IOException { var out = new StringBuilder(); ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); - assertEquals(out.toString().trim(), -""" - { "class name": "Foo", - "version": "61.0", - "flags": ["PUBLIC"], - "superclass": "Boo", - "interfaces": ["Phee", "Phoo"], - "attributes": ["SourceFile"], - "fields": [ - { "field name": "f", - "flags": ["PRIVATE"], - "descriptor": "Ljava/lang/String;", - "attributes": [] }], - "methods": [ - { "method name": "m", - "flags": ["PROTECTED"], - "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", - "attributes": ["Code"] }] - } -""".trim()); + assertOut(out, + """ + { "class name": "Foo", + "version": "61.0", + "flags": ["PUBLIC"], + "superclass": "Boo", + "interfaces": ["Phee", "Phoo"], + "attributes": ["SourceFile"], + "fields": [ + { "field name": "f", + "flags": ["PRIVATE"], + "descriptor": "Ljava/lang/String;", + "attributes": [] }], + "methods": [ + { "method name": "m", + "flags": ["PROTECTED"], + "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["Code"] }] + } + """); } @Test public void testPrintXmlTraceAll() throws IOException { var out = new StringBuilder(); ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); - assertEquals(out.toString(), -""" - - - - <:>1Foo - <:>2 - <:>3Boo - <:>4 - <:>5f - <:>6Ljava/lang/String; - <:>7m - <:>8(ZLjava/lang/Throwable;)Ljava/lang/Void; - <:>9Phee - <:>10 - <:>11Phoo - <:>12 - <:>13Code - <:>14StackMapTable - <:>15SourceFile - <:>16Foo.java - Foo.java - - - - - - - <:>6 - - <:>0 - <:>1 - <:>4 - <:>5 - - <:>6 -"""); + assertOut(out, + """ + + + + <:>1Foo + <:>2 + <:>3Boo + <:>4 + <:>5f + <:>6Ljava/lang/String; + <:>7m + <:>8(ZLjava/lang/Throwable;)Ljava/lang/Void; + <:>9Phee + <:>10 + <:>11Phoo + <:>12 + <:>13Code + <:>14StackMapTable + <:>15SourceFile + <:>16Foo.java + Foo.java + + + + + + + <:>6 + + <:>0 + <:>1 + <:>4 + <:>5 + + <:>6 + + """); } @Test public void testPrintXmlCriticalAttributes() throws IOException { var out = new StringBuilder(); ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); - assertEquals(out.toString(), -""" - - - - - - - - - <:>6 - - <:>0 - <:>1 - <:>4 - <:>5 - - <:>6 -"""); + assertOut(out, + """ + + + + + + + + + <:>6 + + <:>0 + <:>1 + <:>4 + <:>5 + + <:>6 + + """); } @Test public void testPrintXmlMembersOnly() throws IOException { var out = new StringBuilder(); ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); - assertEquals(out.toString(), -""" - - - - - - -"""); + assertOut(out, + """ + + + + + + + + """); + } + + private static void assertOut(StringBuilder out, String expected) { + assertEquals(out.toString().replaceAll("\\\r", "").trim(), expected.trim()); } } From 02bf8b4191fab743ce6893861137e86a90b6e6a6 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 14 Jun 2022 11:02:57 +0200 Subject: [PATCH 004/190] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bfa00d33bd2c..7a3c166c8913d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Provide an API for parsing, generating, and transforming Java class files. This will initially serve as an internal replacement for ASM in the JDK, to be later opened as a public API. See [JEP ???](https://bugs.openjdk.java.net/browse/JDK-8280389) -or [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/asotona/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/jdk/classfile/package-summary.html) +or [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openjdk/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/jdk/classfile/package-summary.html) for more information about Classfile Processing API. See for more information about From e1386dfd5acba9642d835a649ea4a4b0e9ae70bb Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 21 Jun 2022 16:13:36 +0200 Subject: [PATCH 005/190] fixed dead code patching in StackMapGenerator::removeRangeFromExcTable and added test (#11) --- .../jdk/classfile/impl/StackMapGenerator.java | 2 +- test/jdk/jdk/classfile/StackMapsTest.java | 34 ++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index a2d1db6a14b73..0835075f27e51 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -360,7 +360,7 @@ private void removeRangeFromExcTable(int rangeStart, int rangeEnd) { int handlerEnd = labelContext.labelToBci(e.tryEnd()); if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) { //out of range - return; + continue; } if (trace) System.out.println(" Removing dead code range from exception handler start: " + handlerStart + " end: " + handlerEnd); if (rangeStart <= handlerStart) { diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index 277ce1cb171f5..c11a99765ad2c 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -55,10 +55,36 @@ private byte[] buildDeadCode() { 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_())); + "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 From 0daac4ab60619d76d22e2bf6971404ce00ea34c6 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore <54672762+mcimadamore@users.noreply.github.com> Date: Fri, 24 Jun 2022 08:38:30 +0100 Subject: [PATCH 006/190] Tweak JEP link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a3c166c8913d..54e554af99c78 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Provide an API for parsing, generating, and transforming Java class files. This will initially serve as an internal replacement for ASM in the JDK, to be later opened as a public API. -See [JEP ???](https://bugs.openjdk.java.net/browse/JDK-8280389) +See [JEP](https://bugs.openjdk.java.net/browse/JDK-8280389) or [online API documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openjdk/jdk-sandbox/classfile-api-javadoc-branch/doc/classfile-api/javadoc/jdk/classfile/package-summary.html) for more information about Classfile Processing API. From b5267ad25cbf2cb8d2b909ab2098f06daacabcc0 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 27 Jun 2022 12:54:51 +0200 Subject: [PATCH 007/190] removal of jdk.classfile.jdktypes.AccessFlag and redirection to java.lang.reflect.AccesFlag in Classfile API and tests --- .../classes/jdk/classfile/AccessFlags.java | 2 +- .../classes/jdk/classfile/ClassBuilder.java | 2 +- .../classes/jdk/classfile/Classfile.java | 2 +- .../classes/jdk/classfile/FieldBuilder.java | 2 +- .../classes/jdk/classfile/MethodBuilder.java | 2 +- .../classfile/attribute/InnerClassInfo.java | 2 +- .../attribute/MethodParameterInfo.java | 2 +- .../classfile/attribute/ModuleAttribute.java | 2 +- .../classfile/attribute/ModuleExportInfo.java | 2 +- .../classfile/attribute/ModuleOpenInfo.java | 2 +- .../attribute/ModuleRequireInfo.java | 2 +- .../jdk/classfile/impl/AccessFlagsImpl.java | 2 +- .../classes/jdk/classfile/impl/ClassImpl.java | 2 +- .../jdk/classfile/impl/StackMapDecoder.java | 2 +- .../classes/jdk/classfile/impl/Util.java | 2 +- .../impl/verifier/VerificationWrapper.java | 2 +- .../jdk/classfile/jdktypes/AccessFlag.java | 421 ------------------ .../classfile/snippets/PackageSnippets.java | 2 +- .../transforms/CodeLocalsShifter.java | 2 +- test/jdk/jdk/classfile/AccessFlagsTest.java | 2 +- .../AdvancedTransformationsTest.java | 2 +- test/jdk/jdk/classfile/BuilderBlockTest.java | 2 +- test/jdk/jdk/classfile/LDCTest.java | 2 +- test/jdk/jdk/classfile/LowAdaptTest.java | 2 +- test/jdk/jdk/classfile/LvtTest.java | 2 +- .../MassAdaptCopyPrimitiveMatchCodeTest.java | 2 +- test/jdk/jdk/classfile/OneToOneTest.java | 2 +- .../jdk/classfile/OpcodesValidationTest.java | 2 +- test/jdk/jdk/classfile/StackMapsTest.java | 2 +- .../TempConstantPoolBuilderTest.java | 2 +- test/jdk/jdk/classfile/WriteTest.java | 2 +- .../classfile/examples/ExampleGallery.java | 2 +- .../bench/jdk/classfile/ReadMetadata.java | 2 +- .../openjdk/bench/jdk/classfile/Write.java | 2 +- 34 files changed, 33 insertions(+), 454 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java diff --git a/src/java.base/share/classes/jdk/classfile/AccessFlags.java b/src/java.base/share/classes/jdk/classfile/AccessFlags.java index 13045bd462d47..2f1d96ec02296 100755 --- a/src/java.base/share/classes/jdk/classfile/AccessFlags.java +++ b/src/java.base/share/classes/jdk/classfile/AccessFlags.java @@ -26,7 +26,7 @@ import java.util.Set; import jdk.classfile.impl.AccessFlagsImpl; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; /** * Models the access flags for a class, method, or field. Delivered as a diff --git a/src/java.base/share/classes/jdk/classfile/ClassBuilder.java b/src/java.base/share/classes/jdk/classfile/ClassBuilder.java index a576ba87c759b..3db4e661e528e 100755 --- a/src/java.base/share/classes/jdk/classfile/ClassBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/ClassBuilder.java @@ -38,7 +38,7 @@ import jdk.classfile.impl.ChainedClassBuilder; import jdk.classfile.impl.DirectClassBuilder; import jdk.classfile.impl.Util; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; /** * A builder for classfiles. Builders are not created directly; they are passed diff --git a/src/java.base/share/classes/jdk/classfile/Classfile.java b/src/java.base/share/classes/jdk/classfile/Classfile.java index 2d30fac2a1ac6..0ac5428d9946d 100755 --- a/src/java.base/share/classes/jdk/classfile/Classfile.java +++ b/src/java.base/share/classes/jdk/classfile/Classfile.java @@ -45,7 +45,7 @@ import jdk.classfile.impl.DirectClassBuilder; import jdk.classfile.impl.Options; import jdk.classfile.impl.UnboundAttribute; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.jdktypes.PackageDesc; /** diff --git a/src/java.base/share/classes/jdk/classfile/FieldBuilder.java b/src/java.base/share/classes/jdk/classfile/FieldBuilder.java index bf04f93c68f2e..4a568355d04f2 100755 --- a/src/java.base/share/classes/jdk/classfile/FieldBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/FieldBuilder.java @@ -28,7 +28,7 @@ import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.ChainedFieldBuilder; import jdk.classfile.impl.TerminalFieldBuilder; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import java.util.Optional; import java.util.function.Consumer; diff --git a/src/java.base/share/classes/jdk/classfile/MethodBuilder.java b/src/java.base/share/classes/jdk/classfile/MethodBuilder.java index 8d55b32bfd733..54228b38712af 100755 --- a/src/java.base/share/classes/jdk/classfile/MethodBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/MethodBuilder.java @@ -31,7 +31,7 @@ import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.ChainedMethodBuilder; import jdk.classfile.impl.TerminalMethodBuilder; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; /** * A builder for methods. Builders are not created directly; they are passed diff --git a/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java index e9688ec0e857f..906fd15b3cff2 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java @@ -30,7 +30,7 @@ import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.constantpool.Utf8Entry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; diff --git a/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java index 9cbcac3c29d7b..5008c9b6937f0 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java @@ -28,7 +28,7 @@ import java.util.Set; import jdk.classfile.constantpool.Utf8Entry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.java index 18f11fd5434ca..ff30a2e8e96ea 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleAttribute.java @@ -38,7 +38,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.jdktypes.ModuleDesc; import jdk.classfile.jdktypes.PackageDesc; import jdk.classfile.impl.ModuleAttributeBuilderImpl; diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.java index 7bf43ef3070c4..c6c50a0c1d4c5 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleExportInfo.java @@ -30,7 +30,7 @@ import jdk.classfile.constantpool.ModuleEntry; import jdk.classfile.constantpool.PackageEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.impl.UnboundAttribute; diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java index 63ca4eb1bbac8..ded44833c67fa 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleOpenInfo.java @@ -30,7 +30,7 @@ import jdk.classfile.constantpool.ModuleEntry; import jdk.classfile.constantpool.PackageEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.impl.UnboundAttribute; import jdk.classfile.impl.Util; diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.java index 0e428c1992ad4..ae94e0a55987b 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/ModuleRequireInfo.java @@ -30,7 +30,7 @@ import jdk.classfile.constantpool.ModuleEntry; import jdk.classfile.constantpool.Utf8Entry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.jdktypes.ModuleDesc; import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; diff --git a/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java b/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java index fa7e18cfc76d6..68a51a49b009f 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java @@ -26,7 +26,7 @@ import java.util.Set; import jdk.classfile.AccessFlags; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; /** * AccessFlagsImpl */ diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java index 717d00e57204b..151b0c625d30a 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java @@ -33,7 +33,7 @@ import jdk.classfile.ClassBuilder; import jdk.classfile.constantpool.ClassEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.AccessFlags; import jdk.classfile.Attribute; import jdk.classfile.AttributeMapper; diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java index abda13c2f6b38..87499869acd19 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java @@ -29,7 +29,7 @@ import java.util.List; import jdk.classfile.constantpool.ClassEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.attribute.StackMapTableAttribute.*; import jdk.classfile.ClassReader; diff --git a/src/java.base/share/classes/jdk/classfile/impl/Util.java b/src/java.base/share/classes/jdk/classfile/impl/Util.java index f1662c92b35f5..b0dab0bd5fab3 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/Util.java +++ b/src/java.base/share/classes/jdk/classfile/impl/Util.java @@ -35,7 +35,7 @@ import jdk.classfile.CodeElement; import jdk.classfile.Opcode; import jdk.classfile.constantpool.ClassEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import static jdk.classfile.Classfile.ACC_STATIC; diff --git a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.java index 527afeab71f85..7fc8df906f2cd 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.java +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerificationWrapper.java @@ -30,7 +30,7 @@ import jdk.classfile.constantpool.DynamicConstantPoolEntry; import jdk.classfile.constantpool.MemberRefEntry; import jdk.classfile.constantpool.NameAndTypeEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.ClassModel; import jdk.classfile.constantpool.ConstantPool; import jdk.classfile.MethodModel; diff --git a/src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java b/src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java deleted file mode 100644 index ee1e5cffb749b..0000000000000 --- a/src/java.base/share/classes/jdk/classfile/jdktypes/AccessFlag.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact 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.classfile.jdktypes; - -import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import static java.util.Map.entry; - -/** - * Represents a JVM access or module-related flag on a runtime member, - * such as a {@linkplain Class class}, {@linkplain Field field}, or - * {@linkplain Executable method}. - * - *

JVM access and module-related flags are related to, but distinct - * from Java language {@linkplain Modifier modifiers}. Some modifiers - * and access flags have a one-to-one correspondence, such as {@code - * public}. In other cases, some language-level modifiers do - * not have an access flag, such as {@code sealed} (JVMS - * {@jvms 4.7.31}) and some access flags have no corresponding - * modifier, such as {@linkplain #SYNTHETIC synthetic}. - * - *

The values for the constants representing the access and module - * flags are taken from sections of The Java Virtual Machine - * Specification including {@jvms 4.1} (class access and - * property modifiers), {@jvms 4.5} (field access and property flags), - * {@jvms 4.6} (method access and property flags), {@jvms 4.7.6} - * (nested class access and property flags), {@jvms 4.7.24} (method - * parameters), and {@jvms 4.7.25} (module flags and requires, - * exports, and opens flags). - * - *

The {@linkplain #mask() mask} values for the different access - * flags are not distinct. Flags are defined for different - * kinds of JVM structures and the same bit position has different - * meanings in different contexts. For example, {@code 0x0000_0040} - * indicates a {@link #VOLATILE volatile} field but a {@linkplain - * #BRIDGE bridge method}; {@code 0x0000_0080} indicates a {@link - * #TRANSIENT transient} field but a {@linkplain #VARARGS variable - * arity (varargs)} method. - * - *

The access flag constants are ordered by non-decreasing mask - * value; that is the mask value of a constant is greater than or - * equal to the mask value of an immediate neighbor to its (syntactic) - * left. If new constants are added, this property will be - * maintained. That implies new constants will not necessarily be - * added at the end of the existing list. - * - * @see java.lang.reflect.Modifier - * @see java.lang.module.ModuleDescriptor.Modifier - * @see java.lang.module.ModuleDescriptor.Requires.Modifier - * @see java.lang.module.ModuleDescriptor.Exports.Modifier - * @see java.lang.module.ModuleDescriptor.Opens.Modifier - * @see java.compiler/javax.lang.model.element.Modifier - * @since 19 - */ -@SuppressWarnings("doclint:reference") // cross-module link -public enum AccessFlag { - /** - * The access flag {@code ACC_PUBLIC}, corresponding to the source - * modifier {@link Modifier#PUBLIC public} with a mask value of - * {@code 0x0001}. - */ - PUBLIC(Modifier.PUBLIC, true, - Set.of(Location.CLASS, Location.FIELD, Location.METHOD, - Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_PRIVATE}, corresponding to the - * source modifier {@link Modifier#PRIVATE private} with a mask - * value of {@code 0x0002}. - */ - PRIVATE(Modifier.PRIVATE, true, - Set.of(Location.FIELD, Location.METHOD, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_PROTECTED}, corresponding to the - * source modifier {@link Modifier#PROTECTED protected} with a mask - * value of {@code 0x0004}. - */ - PROTECTED(Modifier.PROTECTED, true, - Set.of(Location.FIELD, Location.METHOD, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_STATIC}, corresponding to the source - * modifier {@link Modifier#STATIC static} with a mask value of - * {@code 0x0008}. - */ - STATIC(Modifier.STATIC, true, - Set.of(Location.FIELD, Location.METHOD, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_FINAL}, corresponding to the source - * modifier {@link Modifier#FINAL final} with a mask - * value of {@code 0x0010}. - */ - FINAL(Modifier.FINAL, true, - Set.of(Location.CLASS, Location.FIELD, Location.METHOD, - Location.INNER_CLASS, Location.METHOD_PARAMETER)), - - /** - * The access flag {@code ACC_SUPER} with a mask value of {@code - * 0x0020}. - */ - SUPER(0x0000_0020, false, Set.of(Location.CLASS)), - - /** - * The module flag {@code ACC_OPEN} with a mask value of {@code - * 0x0020}. - * @see java.lang.module.ModuleDescriptor#isOpen - */ - OPEN(0x0000_0020, false, Set.of(Location.MODULE)), - - /** - * The module requires flag {@code ACC_TRANSITIVE} with a mask - * value of {@code 0x0020}. - * @see java.lang.module.ModuleDescriptor.Requires.Modifier#TRANSITIVE - */ - TRANSITIVE(0x0000_0020, false, Set.of(Location.MODULE_REQUIRES)), - - /** - * The access flag {@code ACC_SYNCHRONIZED}, corresponding to the - * source modifier {@link Modifier#SYNCHRONIZED synchronized} with - * a mask value of {@code 0x0020}. - */ - SYNCHRONIZED(Modifier.SYNCHRONIZED, true, Set.of(Location.METHOD)), - - /** - * The module requires flag {@code ACC_STATIC_PHASE} with a mask - * value of {@code 0x0040}. - * @see java.lang.module.ModuleDescriptor.Requires.Modifier#STATIC - */ - STATIC_PHASE(0x0000_0040, false, Set.of(Location.MODULE_REQUIRES)), - - /** - * The access flag {@code ACC_VOLATILE}, corresponding to the - * source modifier {@link Modifier#VOLATILE volatile} with a mask - * value of {@code 0x0040}. - */ - VOLATILE(Modifier.VOLATILE, true, Set.of(Location.FIELD)), - - /** - * The access flag {@code ACC_BRIDGE} with a mask value of {@code - * 0x0040}. - * @see Method#isBridge() - */ - BRIDGE(0x0000_0040, false, Set.of(Location.METHOD)), - - /** - * The access flag {@code ACC_TRANSIENT}, corresponding to the - * source modifier {@link Modifier#TRANSIENT transient} with a - * mask value of {@code 0x0080}. - */ - TRANSIENT(Modifier.TRANSIENT, true, Set.of(Location.FIELD)), - - /** - * The access flag {@code ACC_VARARGS} with a mask value of {@code - * 0x0080}. - * @see Executable#isVarArgs() - */ - VARARGS(0x0000_0080, false, Set.of(Location.METHOD)), - - /** - * The access flag {@code ACC_NATIVE}, corresponding to the source - * modifier {@link Modifier#NATIVE native} with a mask value of {@code - * 0x0100}. - */ - NATIVE(Modifier.NATIVE, true, Set.of(Location.METHOD)), - - /** - * The access flag {@code ACC_INTERFACE} with a mask value of - * {@code 0x0200}. - * @see Class#isInterface() - */ - INTERFACE(Modifier.INTERFACE, false, - Set.of(Location.CLASS, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_ABSTRACT}, corresponding to the - * source modifier {@link Modifier#ABSTRACT abstract} with a mask - * value of {@code 0x0400}. - */ - ABSTRACT(Modifier.ABSTRACT, true, - Set.of(Location.CLASS, Location.METHOD, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_STRICT}, corresponding to the source - * modifier {@link Modifier#STRICT strictfp} with a mask value of - * {@code 0x0800}. - */ - STRICT(Modifier.STRICT, true, Set.of(Location.METHOD)), - - /** - * The access flag {@code ACC_SYNTHETIC} with a mask value of - * {@code 0x1000}. - * @see Class#isSynthetic() - * @see Executable#isSynthetic() - * @see java.lang.module.ModuleDescriptor.Modifier#SYNTHETIC - */ - SYNTHETIC(0x0000_1000, false, - Set.of(Location.CLASS, Location.FIELD, Location.METHOD, - Location.INNER_CLASS, Location.METHOD_PARAMETER, - Location.MODULE, Location.MODULE_REQUIRES, - Location.MODULE_EXPORTS, Location.MODULE_OPENS)), - - /** - * The access flag {@code ACC_ANNOTATION} with a mask value of - * {@code 0x2000}. - * @see Class#isAnnotation() - */ - ANNOTATION(0x0000_2000, false, - Set.of(Location.CLASS, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_ENUM} with a mask value of {@code - * 0x4000}. - * @see Class#isEnum() - */ - ENUM(0x0000_4000, false, - Set.of(Location.CLASS, Location.FIELD, Location.INNER_CLASS)), - - /** - * The access flag {@code ACC_MANDATED} with a mask value of - * {@code 0x8000}. - */ - MANDATED(0x0000_8000, false, - Set.of(Location.METHOD_PARAMETER, - Location.MODULE, Location.MODULE_REQUIRES, - Location.MODULE_EXPORTS, Location.MODULE_OPENS)), - - /** - * The access flag {@code ACC_MODULE} with a mask value of {@code - * 0x8000}. - */ - MODULE(0x0000_8000, false, Set.of(Location.CLASS)) - ; - - // May want to override toString for a different enum constant -> - // name mapping. - - private int mask; - private boolean sourceModifier; - - // Intentionally using Set rather than EnumSet since EnumSet is - // mutable. - private Set locations; - - private AccessFlag(int mask, boolean sourceModifier, Set locations) { - this.mask = mask; - this.sourceModifier = sourceModifier; - this.locations = locations; - } - - /** - * {@return the corresponding integer mask for the access flag} - */ - public int mask() { - return mask; - } - - /** - * {@return whether or not the flag has a directly corresponding - * modifier in the Java programming language} - */ - public boolean sourceModifier() { - return sourceModifier; - } - - /** - * {@return kinds of constructs the flag can be applied to} - */ - public Set locations() { - return locations; - } - - /** - * {@return a set of access flags for the given mask value - * appropriate for the location in question} - * - * @param mask bit mask of access flags - * @param location context to interpret mask value - * @throws IllegalArgumentException if the mask contains bit - * positions not support for the location in question - */ - public static Set maskToAccessFlags(int mask, Location location) { - Set result = java.util.EnumSet.noneOf(AccessFlag.class); - for (var accessFlag : LocationToFlags.locationToFlags.get(location)) { - int accessMask = accessFlag.mask(); - if ((mask & accessMask) != 0) { - result.add(accessFlag); - mask = mask & ~accessMask; - } - } - if (mask != 0) { - throw new IllegalArgumentException("Unmatched bit position 0x" + - Integer.toHexString(mask) + - " for location " + location); - } - return Collections.unmodifiableSet(result); - } - - /** - * A location within a class file where flags can be applied. - * - * Note that since these locations represent class file structures - * rather than language structures many language structures, such - * as constructors and interfaces, are not present. - * @since 19 - */ - public enum Location { - /** - * Class location. - * @jvms 4.1 The ClassFile Structure - */ - CLASS, - - /** - * Field location. - * @jvms 4.5 Fields - */ - FIELD, - - /** - * Method location. - * @jvms 4.6 Method - */ - METHOD, - - /** - * Inner class location. - * @jvms 4.7.6 The InnerClasses Attribute - */ - INNER_CLASS, - - /** - * Method parameter loccation. - * @jvms 4.7.24. The MethodParameters Attribute - */ - METHOD_PARAMETER, - - /** - * Module location - * @jvms 4.7.25. The Module Attribute - */ - MODULE, - - /** - * Module requires location - * @jvms 4.7.25. The Module Attribute - */ - MODULE_REQUIRES, - - /** - * Module exports location - * @jvms 4.7.25. The Module Attribute - */ - MODULE_EXPORTS, - - /** - * Module opens location - * @jvms 4.7.25. The Module Attribute - */ - MODULE_OPENS; - - } - - private static class LocationToFlags { - private static Map> locationToFlags = - Map.ofEntries(entry(Location.CLASS, - Set.of(PUBLIC, FINAL, SUPER, - INTERFACE, ABSTRACT, - SYNTHETIC, ANNOTATION, - ENUM, AccessFlag.MODULE)), - entry(Location.FIELD, - Set.of(PUBLIC, PRIVATE, PROTECTED, - STATIC, FINAL, VOLATILE, - TRANSIENT, SYNTHETIC, ENUM)), - entry(Location.METHOD, - Set.of(PUBLIC, PRIVATE, PROTECTED, - STATIC, FINAL, SYNCHRONIZED, - BRIDGE, VARARGS, NATIVE, - ABSTRACT, STRICT, SYNTHETIC)), - entry(Location.INNER_CLASS, - Set.of(PUBLIC, PRIVATE, PROTECTED, - STATIC, FINAL, INTERFACE, ABSTRACT, - SYNTHETIC, ANNOTATION, ENUM)), - entry(Location.METHOD_PARAMETER, - Set.of(FINAL, SYNTHETIC, MANDATED)), - entry(Location.MODULE, - Set.of(OPEN, SYNTHETIC, MANDATED)), - entry(Location.MODULE_REQUIRES, - Set.of(TRANSITIVE, STATIC_PHASE, SYNTHETIC, MANDATED)), - entry(Location.MODULE_EXPORTS, - Set.of(SYNTHETIC, MANDATED)), - entry(Location.MODULE_OPENS, - Set.of(SYNTHETIC, MANDATED))); - } -} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java b/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java index 71856aaac48f7..83b0202f15c61 100755 --- a/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java +++ b/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java @@ -31,7 +31,7 @@ import java.util.HashSet; import java.util.Set; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.ClassElement; import jdk.classfile.ClassModel; import jdk.classfile.ClassTransform; diff --git a/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java b/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java index b44641d54d87f..0db457934c0e8 100644 --- a/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java +++ b/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java @@ -26,7 +26,7 @@ import java.lang.constant.MethodTypeDesc; import java.util.Arrays; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.AccessFlags; import jdk.classfile.CodeBuilder; import jdk.classfile.CodeElement; diff --git a/test/jdk/jdk/classfile/AccessFlagsTest.java b/test/jdk/jdk/classfile/AccessFlagsTest.java index 584f60b35bdec..0c64a6dc66eee 100644 --- a/test/jdk/jdk/classfile/AccessFlagsTest.java +++ b/test/jdk/jdk/classfile/AccessFlagsTest.java @@ -33,7 +33,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.IntFunction; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.AccessFlags; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index 1060c96a6150d..cbfdf19880726 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -57,7 +57,7 @@ import jdk.classfile.impl.RawBytecodeHelper; import jdk.classfile.impl.Util; import jdk.classfile.instruction.InvokeInstruction; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.transforms.LabelsRemapper; public class AdvancedTransformationsTest { diff --git a/test/jdk/jdk/classfile/BuilderBlockTest.java b/test/jdk/jdk/classfile/BuilderBlockTest.java index 0f952df0dbfc7..ab0adab9fe327 100644 --- a/test/jdk/jdk/classfile/BuilderBlockTest.java +++ b/test/jdk/jdk/classfile/BuilderBlockTest.java @@ -39,7 +39,7 @@ import helpers.ByteArrayClassLoader; import jdk.classfile.AccessFlags; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.Label; import jdk.classfile.TypeKind; diff --git a/test/jdk/jdk/classfile/LDCTest.java b/test/jdk/jdk/classfile/LDCTest.java index 98811208c36d0..c7be46942972f 100644 --- a/test/jdk/jdk/classfile/LDCTest.java +++ b/test/jdk/jdk/classfile/LDCTest.java @@ -35,7 +35,7 @@ import jdk.classfile.*; import jdk.classfile.constantpool.ConstantPoolBuilder; import jdk.classfile.constantpool.StringEntry; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import org.testng.Assert; import org.testng.annotations.Test; import static helpers.TestConstants.MTD_VOID; diff --git a/test/jdk/jdk/classfile/LowAdaptTest.java b/test/jdk/jdk/classfile/LowAdaptTest.java index 19144709b727a..3f505851bdab9 100644 --- a/test/jdk/jdk/classfile/LowAdaptTest.java +++ b/test/jdk/jdk/classfile/LowAdaptTest.java @@ -38,7 +38,7 @@ import java.nio.file.Paths; import jdk.classfile.AccessFlags; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.ClassModel; import jdk.classfile.Classfile; import jdk.classfile.Opcode; diff --git a/test/jdk/jdk/classfile/LvtTest.java b/test/jdk/jdk/classfile/LvtTest.java index 1ce7b50ac9b8e..27e7b1c42d917 100644 --- a/test/jdk/jdk/classfile/LvtTest.java +++ b/test/jdk/jdk/classfile/LvtTest.java @@ -50,7 +50,7 @@ import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.instruction.LocalVariable; import jdk.classfile.instruction.LocalVariableType; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java b/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java index bd101ab590f5b..f6fef3f8b7db0 100644 --- a/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java +++ b/test/jdk/jdk/classfile/MassAdaptCopyPrimitiveMatchCodeTest.java @@ -29,7 +29,7 @@ * @run testng MassAdaptCopyPrimitiveMatchCodeTest */ import helpers.InstructionModelToCodeBuilder; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.attribute.CodeAttribute; import jdk.classfile.Attributes; diff --git a/test/jdk/jdk/classfile/OneToOneTest.java b/test/jdk/jdk/classfile/OneToOneTest.java index 1dd8c523d75e2..b1663af7be344 100644 --- a/test/jdk/jdk/classfile/OneToOneTest.java +++ b/test/jdk/jdk/classfile/OneToOneTest.java @@ -34,7 +34,7 @@ import java.util.List; import jdk.classfile.AccessFlags; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.ClassModel; import jdk.classfile.Classfile; import jdk.classfile.Instruction; diff --git a/test/jdk/jdk/classfile/OpcodesValidationTest.java b/test/jdk/jdk/classfile/OpcodesValidationTest.java index 7a6567105a082..fa88624191724 100644 --- a/test/jdk/jdk/classfile/OpcodesValidationTest.java +++ b/test/jdk/jdk/classfile/OpcodesValidationTest.java @@ -33,7 +33,7 @@ import static java.lang.constant.ConstantDescs.CD_void; import java.lang.constant.MethodTypeDesc; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.Opcode; import org.testng.annotations.Test; diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index c11a99765ad2c..0315a404eec8f 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -43,7 +43,7 @@ import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; import java.util.List; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; /** * StackMapsTest diff --git a/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java b/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java index f4192db47a056..6edb82b797fa1 100644 --- a/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java +++ b/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java @@ -31,7 +31,7 @@ import jdk.classfile.*; import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; import jdk.classfile.attribute.SourceFileAttribute; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import org.testng.annotations.Test; import java.lang.constant.ClassDesc; diff --git a/test/jdk/jdk/classfile/WriteTest.java b/test/jdk/jdk/classfile/WriteTest.java index 5b9cd1ab9a2e9..866ee2bb5007e 100644 --- a/test/jdk/jdk/classfile/WriteTest.java +++ b/test/jdk/jdk/classfile/WriteTest.java @@ -33,7 +33,7 @@ import helpers.TestConstants; import jdk.classfile.AccessFlags; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.TypeKind; import jdk.classfile.Label; diff --git a/test/jdk/jdk/classfile/examples/ExampleGallery.java b/test/jdk/jdk/classfile/examples/ExampleGallery.java index dd8240d5d0916..00a04d5ff91ee 100755 --- a/test/jdk/jdk/classfile/examples/ExampleGallery.java +++ b/test/jdk/jdk/classfile/examples/ExampleGallery.java @@ -51,7 +51,7 @@ import jdk.classfile.attribute.SignatureAttribute; import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.instruction.ConstantInstruction; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; /** * ExampleGallery diff --git a/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java b/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java index 66858bfd88888..cf4df15e9d191 100755 --- a/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java +++ b/test/micro/org/openjdk/bench/jdk/classfile/ReadMetadata.java @@ -24,7 +24,7 @@ */ package org.openjdk.bench.jdk.classfile; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.ClassElement; import jdk.classfile.ClassModel; import jdk.classfile.Classfile; diff --git a/test/micro/org/openjdk/bench/jdk/classfile/Write.java b/test/micro/org/openjdk/bench/jdk/classfile/Write.java index 48d8472829956..b214c24e8af78 100755 --- a/test/micro/org/openjdk/bench/jdk/classfile/Write.java +++ b/test/micro/org/openjdk/bench/jdk/classfile/Write.java @@ -25,7 +25,7 @@ package org.openjdk.bench.jdk.classfile; import jdk.classfile.AccessFlags; -import jdk.classfile.jdktypes.AccessFlag; +import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.TypeKind; import jdk.classfile.attribute.SourceFileAttribute; From 941dfb0ebac505caf6926cac97c279019817fb49 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 27 Jun 2022 14:19:20 +0200 Subject: [PATCH 008/190] Classfile API javadoc build fix --- make/Docs.gmk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/make/Docs.gmk b/make/Docs.gmk index e230eb9d2fd40..e779211fe1136 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -501,13 +501,13 @@ $(eval $(call SetupApiDocsGeneration, REFERENCE_API, \ # Setup generation of Classfile Processing API javadoc CLASSFILE_SRC := $(TOPDIR)/src/java.base/share/classes -CLASSFILE_TARGET := $(DOCS_OUTPUTDIR)/classfile-api +CLASSFILE_TARGET := $(DOCS_OUTPUTDIR)/classfile-api/javadoc $(eval $(call SetupExecute, CLASSFILE_API_TARGET, \ DEPS := $(BUILD_TOOLS_JDK), \ OUTPUT_DIR := $(CLASSFILE_TARGET), \ SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/docs, \ - COMMAND := $(JAVA) -Djava.awt.headless=true \ + COMMAND := $(BUILD_JAVA) -Djava.awt.headless=true \ $(NEW_JAVADOC) -d $(CLASSFILE_TARGET) \ -XDignorePreview=true -notimestamp -public \ -encoding ISO-8859-1 -docencoding UTF-8 -Xdoclint:none \ From 0fe61c507a2fa0a66d2477e97f58f5551c3b56fd Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 29 Jun 2022 19:31:32 +0200 Subject: [PATCH 009/190] fixed InvokeDynamicEntry::asSymbol and BytecodeHelpers::handleDescToHandleInfo implemented RebuildingTransformation and added to Transforms and CorpusTest reduced CorpusTestHelper output and adjusted TEST.properties --- .../constantpool/InvokeDynamicEntry.java | 2 +- .../jdk/classfile/impl/BytecodeHelpers.java | 4 +- test/jdk/jdk/classfile/TEST.properties | 2 +- .../classfile/helpers/CorpusTestHelper.java | 15 +- .../helpers/RebuildingTransformation.java | 283 ++++++++++++++++++ .../jdk/jdk/classfile/helpers/Transforms.java | 6 +- 6 files changed, 293 insertions(+), 19 deletions(-) create mode 100644 test/jdk/jdk/classfile/helpers/RebuildingTransformation.java diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java index 8c8731de57b5d..ee294bed9f9f1 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/InvokeDynamicEntry.java @@ -43,7 +43,7 @@ sealed public interface InvokeDynamicEntry default DynamicCallSiteDesc asSymbol() { return DynamicCallSiteDesc.of(bootstrap().bootstrapMethod().asSymbol(), name().stringValue(), - MethodTypeDesc.ofDescriptor(name().stringValue()), + MethodTypeDesc.ofDescriptor(type().stringValue()), bootstrap().arguments().stream() .map(LoadableConstantEntry::constantValue) .toArray(ConstantDesc[]::new)); diff --git a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java index aaf927e0bd426..a0c9de2060fbf 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java @@ -276,8 +276,8 @@ static void validateBIPUSH(ConstantDesc d) { public static MethodHandleEntry handleDescToHandleInfo(ConstantPoolBuilder constantPool, DirectMethodHandleDesc bootstrapMethod) { ClassEntry bsOwner = constantPool.classEntry(bootstrapMethod.owner()); - NameAndTypeEntry bsNameAndType = constantPool.natEntry(bootstrapMethod.methodName(), - MethodTypeDesc.ofDescriptor(bootstrapMethod.lookupDescriptor())); + NameAndTypeEntry bsNameAndType = constantPool.natEntry(constantPool.utf8Entry(bootstrapMethod.methodName()), + constantPool.utf8Entry(bootstrapMethod.lookupDescriptor())); int bsRefKind = bootstrapMethod.refKind(); MemberRefEntry bsReference = toBootstrapMemberRef(constantPool, bsRefKind, bsOwner, bsNameAndType, bootstrapMethod.isOwnerInterface()); diff --git a/test/jdk/jdk/classfile/TEST.properties b/test/jdk/jdk/classfile/TEST.properties index 7e51afc0e76b3..5bc418e95324c 100644 --- a/test/jdk/jdk/classfile/TEST.properties +++ b/test/jdk/jdk/classfile/TEST.properties @@ -1,4 +1,4 @@ -maxOutputSize = 250000 +maxOutputSize = 500000 enablePreview = true modules = \ java.base/jdk.classfile \ diff --git a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java index 2bbc77ecca702..73241e6aaa3f1 100644 --- a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java +++ b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java @@ -29,8 +29,6 @@ import jdk.classfile.instruction.LineNumber; import jdk.classfile.instruction.LocalVariable; import jdk.classfile.instruction.LocalVariableType; -import org.testng.ITest; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import java.io.IOException; @@ -48,7 +46,7 @@ import jdk.classfile.Attributes; import jdk.classfile.impl.DirectCodeBuilder; -public class CorpusTestHelper implements ITest { +public class CorpusTestHelper { protected static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); protected static final String testFilter = null; //"modules/java.base/java/util/function/Supplier.class"; @@ -104,7 +102,6 @@ public static Object[] provide() throws IOException, URISyntaxException { } - protected String testMethod = ""; protected final Path path; protected final byte[] bytes; @@ -112,14 +109,4 @@ public CorpusTestHelper(Path path) throws IOException { this.path = path; this.bytes = Files.readAllBytes(path); } - - @BeforeMethod - public void handleTestMethodName(Method method) { - testMethod = method.getName(); - } - - @Override - public String getTestName() { - return testMethod + "[" + path.toString() + "]"; - } } diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java new file mode 100644 index 0000000000000..513c4f86dcb12 --- /dev/null +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.reflect.AccessFlag; +import java.util.HashMap; +import java.util.List; +import jdk.classfile.*; +import jdk.classfile.attribute.*; +import jdk.classfile.constantpool.*; +import jdk.classfile.instruction.*; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.jdktypes.PackageDesc; + +class RebuildingTransformation { + + static byte[] transform(ClassModel clm) { + return Classfile.build(clm.thisClass().asSymbol(), 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.descriptorSymbol(), fb -> { + for (var fe : fm) { + switch (fe) { + case AccessFlags af -> fb.withFlags(af.flagsMask()); + case ConstantValueAttribute a -> fb.with(ConstantValueAttribute.of(a.constant())); //missing ConstantValueAttribute factory method accepting ConstantDesc or individual types + 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(a.asTypeSignature())); + 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.descriptorSymbol(), mm.flags().flagsMask(), mb -> { + for (var me : mm) { + switch (me) { + case AccessFlags af -> mb.withFlags(af.flagsMask()); + case CodeModel com -> mb.withCode(cob -> { + var labels = new HashMap(); + for (var coe : com) { + switch (coe) { + case ArrayLoadInstruction i -> cob.arrayLoadInstruction(i.typeKind()); + case ArrayStoreInstruction i -> cob.arrayStoreInstruction(i.typeKind()); + case BranchInstruction i -> cob.branchInstruction(i.opcode(), labels.computeIfAbsent(i.target(), l -> cob.newLabel())); + case ConstantInstruction i -> cob.constantInstruction(i.constantValue()); + case ConvertInstruction i -> cob.convertInstruction(i.fromType(), i.toType()); + case FieldInstruction i -> cob.fieldInstruction(i.opcode(), i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); + case InvokeDynamicInstruction i -> cob.invokeDynamicInstruction(i.invokedynamic().asSymbol()); + case InvokeInstruction i -> cob.invokeInstruction(i.opcode(), i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol(), i.isInterface()); + case LoadInstruction i -> cob.loadInstruction(i.typeKind(), i.slot()); + case StoreInstruction i -> cob.storeInstruction(i.typeKind(), i.slot()); + case IncrementInstruction i -> cob.incrementInstruction(i.slot(), i.constant()); + case LookupSwitchInstruction i -> cob.lookupSwitchInstruction(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 -> cob.monitorInstruction(i.opcode()); + case NewMultiArrayInstruction i -> cob.newMultidimensionalArrayInstruction(i.dimensions(), i.arrayType().asSymbol()); + case NewObjectInstruction i -> cob.newObjectInstruction(i.className().asSymbol()); + case NewPrimitiveArrayInstruction i -> cob.newPrimitiveArrayInstruction(i.typeKind()); + case NewReferenceArrayInstruction i -> cob.newReferenceArrayInstruction(i.componentType().asSymbol()); + case NopInstruction i -> cob.nopInstruction(); + case OperatorInstruction i -> cob.operatorInstruction(i.opcode()); + case ReturnInstruction i -> cob.returnInstruction(i.typeKind()); + case StackInstruction i -> cob.stackInstruction(i.opcode()); + case TableSwitchInstruction i -> cob.tableSwitchInstruction(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.throwInstruction(); + case TypeCheckInstruction i -> cob.typeCheckInstruction(i.opcode(), i.type().asSymbol()); + 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(), pi.signatureSymbol(), 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 CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); + } + } + }); + 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.of(mp.name().map(Utf8Entry::stringValue).orElse(null), mp.flags().toArray(AccessFlag[]::new))).toArray(MethodParameterInfo[]::new))); //missing MethodParameterInfo factory method accepting String and int + 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(a.asMethodSignature())); + 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())); //missing attribute factory method accepting String + case DeprecatedAttribute a -> clb.with(DeprecatedAttribute.of()); + case EnclosingMethodAttribute a -> clb.with(EnclosingMethodAttribute.of(a.enclosingClass(), a.enclosingMethod().orElse(null))); //missing attribute factory method accepting symbols + case InnerClassesAttribute a -> clb.with(InnerClassesAttribute.of(a.classes().stream().map(ici -> InnerClassInfo.of( + ici.innerClass().asSymbol(), + ici.outerClass().map(ClassEntry::asSymbol).orElse(null), + ici.innerName().map(Utf8Entry::stringValue).orElse(null), + 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())); //missing attribute factory method accpeting ClassDesc + 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(sa.asTypeSignature())); + 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(a.asClassSignature())); + 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())); //missing attribute factory method accepting String + 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.OfConstant oc -> //missing distinction between constant annotation value types + switch (oc.tag()) { + case 's' -> AnnotationValue.of((String)oc.constantValue()); + case 'D' -> AnnotationValue.of((double)oc.constantValue()); + case 'F' -> AnnotationValue.of((float)oc.constantValue()); + case 'J' -> AnnotationValue.of((long)oc.constantValue()); + case 'I' -> AnnotationValue.of((int)oc.constantValue()); + case 'S' -> AnnotationValue.of((short)(int)oc.constantValue()); + case 'C' -> AnnotationValue.of((char)(int)oc.constantValue()); + case 'B' -> AnnotationValue.of((byte)(int)oc.constantValue()); + case 'Z' -> AnnotationValue.of(1 == (int)oc.constantValue()); + default -> throw new AssertionError("Unexpected annotation value tag: " + oc.tag()); + }; + case AnnotationValue.OfClass oc -> AnnotationValue.ofClass(ClassDesc.ofDescriptor(oc.className().stringValue())); //missing AnnotationValue factory method accepting ClassDesc + case AnnotationValue.OfEnum oe -> AnnotationValue.ofEnum(ClassDesc.ofDescriptor(oe.className().stringValue()), oe.constantName().stringValue()); //missing AnnotationValue factory method accepting ClassDesc + }; + } + + static TypeAnnotation[] transformTypeAnnotations(List annotations, CodeBuilder cob, HashMap labels) { + return annotations.stream().map(ta -> TypeAnnotation.of( //missing TypeAnnotation factory method accepting ClassDesc and AnnotationElement vararg + transformTargetInfo(ta.targetInfo(), cob, labels), + ta.targetPath().stream().map(tpc -> TypeAnnotation.TypePathComponent.of(tpc.typePathKind().tag(), tpc.typeArgumentIndex())).toList(), + ta.className(), + 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) { //missing flat decompositions to individual target types + case TypeAnnotation.CatchTarget t -> TypeAnnotation.TargetInfo.ofExceptionParameter(t.exceptionTableIndex()); + case TypeAnnotation.EmptyTarget t -> + switch (t.targetType()) { + case FIELD -> TypeAnnotation.TargetInfo.ofField(); + case METHOD_RETURN -> TypeAnnotation.TargetInfo.ofMethodReturn(); + case METHOD_RECEIVER -> TypeAnnotation.TargetInfo.ofMethodReceiver(); + default -> throw new AssertionError("Unexpected type annotation target type: " + 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 -> + switch (t.targetType()) { + case CLASS_TYPE_PARAMETER_BOUND -> TypeAnnotation.TargetInfo.ofClassTypeParameterBound(t.typeParameterIndex(), t.boundIndex()); + case METHOD_TYPE_PARAMETER_BOUND -> TypeAnnotation.TargetInfo.ofMethodTypeParameterBound(t.typeParameterIndex(), t.boundIndex()); + default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); + }; + case TypeAnnotation.TypeParameterTarget t -> + switch (t.targetType()) { + case CLASS_TYPE_PARAMETER -> TypeAnnotation.TargetInfo.ofClassTypeParameter(t.typeParameterIndex()); + case METHOD_TYPE_PARAMETER -> TypeAnnotation.TargetInfo.ofMethodTypeParameter(t.typeParameterIndex()); + default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); + }; + case TypeAnnotation.LocalVarTarget t -> + switch (t.targetType()) { + case LOCAL_VARIABLE -> TypeAnnotation.TargetInfo.ofLocalVariable(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 RESOURCE_VARIABLE -> TypeAnnotation.TargetInfo.ofResourceVariable(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()); + default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); + }; + case TypeAnnotation.OffsetTarget t -> + switch (t.targetType()) { + case INSTANCEOF -> TypeAnnotation.TargetInfo.ofInstanceofExpr(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); + case NEW -> TypeAnnotation.TargetInfo.ofNewExpr(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); + case CONSTRUCTOR_REFERENCE -> TypeAnnotation.TargetInfo.ofConstructorReference(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); + case METHOD_REFERENCE -> TypeAnnotation.TargetInfo.ofMethodReference(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); + default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); + }; + case TypeAnnotation.TypeArgumentTarget t -> + switch (t.targetType()) { + case CAST -> TypeAnnotation.TargetInfo.ofCastExpr(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofConstructorInvocationTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); + case METHOD_INVOCATION_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofMethodInvocationTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofConstructorReferenceTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); + case METHOD_REFERENCE_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofMethodReferenceTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); + default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); + }; + }; + } +} diff --git a/test/jdk/jdk/classfile/helpers/Transforms.java b/test/jdk/jdk/classfile/helpers/Transforms.java index 9b6c1b28c3cce..9cee790c70577 100644 --- a/test/jdk/jdk/classfile/helpers/Transforms.java +++ b/test/jdk/jdk/classfile/helpers/Transforms.java @@ -116,6 +116,9 @@ public enum NoOpTransform { 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), @@ -194,7 +197,8 @@ public Optional classRecord(byte[] bytes) throws IOException, Const return switch (this) { case ARRAYCOPY -> Optional.of(ClassRecord.ofClassFile(ClassFile.read(new ByteArrayInputStream(bytes)))); case SHARED_1, SHARED_2, SHARED_3, - UNSHARED_1, UNSHARED_2, UNSHARED_3 + UNSHARED_1, UNSHARED_2, UNSHARED_3, + BUILD_FROM_SCRATCH -> Optional.of(ClassRecord.ofClassModel(Classfile.parse(bytes), ClassRecord.CompatibilityFilter.By_ClassBuilder)); default -> Optional.empty(); }; From a89d822eed587a09e7b5ed8388e9d828ef753c18 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 30 Jun 2022 18:06:51 +0200 Subject: [PATCH 010/190] added ConstantValueAttribute::of(ConstantDesc) MethodParameterInfo name parameter changed to Optional added MethodParameterInfo::ofParameter(Optional,int) implemented TemporaryConstantPool::stringEntry adjusted BoundAttribute and RebuildingTransformation test helper --- .../attribute/ConstantValueAttribute.java | 17 +++++++++++++ .../attribute/MethodParameterInfo.java | 25 +++++++++++++------ .../jdk/classfile/impl/BoundAttribute.java | 2 +- .../classfile/impl/TemporaryConstantPool.java | 2 +- .../helpers/RebuildingTransformation.java | 4 +-- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java index 73ad15eb92c40..32c242f44b224 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/ConstantValueAttribute.java @@ -24,10 +24,12 @@ */ package jdk.classfile.attribute; +import java.lang.constant.ConstantDesc; import jdk.classfile.Attribute; import jdk.classfile.FieldElement; import jdk.classfile.constantpool.ConstantValueEntry; import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; /** @@ -53,4 +55,19 @@ public sealed interface ConstantValueAttribute 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/classfile/attribute/MethodParameterInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java index 5008c9b6937f0..fa1311581efdf 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/MethodParameterInfo.java @@ -74,19 +74,28 @@ default boolean has(AccessFlag flag) { /** * {@return a method parameter description} - * @param name the method name - * @param flags the method access flags + * @param name the method parameter name + * @param flags the method parameter access flags */ - static MethodParameterInfo of(Utf8Entry name, int flags) { - return new UnboundAttribute.UnboundMethodParameterInfo(Optional.ofNullable(name), flags); + static MethodParameterInfo of(Optional name, int flags) { + return new UnboundAttribute.UnboundMethodParameterInfo(name, flags); } /** * {@return a method parameter description} - * @param name the method name - * @param flags the method access flags + * @param name the method parameter name + * @param flags the method parameter access flags */ - static MethodParameterInfo of(String name, AccessFlag... flags) { - return of(name == null ? null : TemporaryConstantPool.INSTANCE.utf8Entry(name), Util.flagsToBits(AccessFlag.Location.METHOD_PARAMETER, 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/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java index 59d6649978565..82541c3731e75 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java @@ -388,7 +388,7 @@ public List parameters() { for (int i = 0; p < pEnd; p += 4, i++) { Utf8Entry name = classReader.readUtf8Entry(p); int accessFlags = classReader.readU2(p + 2); - elements[i] = MethodParameterInfo.of(name, accessFlags); + elements[i] = MethodParameterInfo.of(Optional.ofNullable(name), accessFlags); } parameters = List.of(elements); } diff --git a/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java b/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java index ee331da70fc24..68bbc9bf9912c 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java +++ b/src/java.base/share/classes/jdk/classfile/impl/TemporaryConstantPool.java @@ -156,7 +156,7 @@ public ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapM @Override public StringEntry stringEntry(Utf8Entry utf8) { - throw new UnsupportedOperationException(); + return new ConcreteEntry.ConcreteStringEntry(this, -2, (ConcreteEntry.ConcreteUtf8Entry) utf8); } @Override diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 513c4f86dcb12..ff36c8be210be 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -50,7 +50,7 @@ static byte[] transform(ClassModel clm) { for (var fe : fm) { switch (fe) { case AccessFlags af -> fb.withFlags(af.flagsMask()); - case ConstantValueAttribute a -> fb.with(ConstantValueAttribute.of(a.constant())); //missing ConstantValueAttribute factory method accepting ConstantDesc or individual types + 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))); @@ -121,7 +121,7 @@ static byte[] transform(ClassModel clm) { 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.of(mp.name().map(Utf8Entry::stringValue).orElse(null), mp.flags().toArray(AccessFlag[]::new))).toArray(MethodParameterInfo[]::new))); //missing MethodParameterInfo factory method accepting String and int + 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))); From 7bf66b1bc62849a05252a0571d7c857c0040bdbd Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 30 Jun 2022 20:05:08 +0200 Subject: [PATCH 011/190] added CompilationIDAttribute::of(String) EnclosingMethodAttribute factory method changed to accept Optionals added EnclosingMethodAttribute::of(ClassDesc,Optional,Optional) added EnclosingMethodAttribute accessor methods InnerClassInfo all factory methods changed to accept Optionals added NestHostAttribute::of(ClassDesc) added SourceIDAttribute::of(String) changes reflected in BoundAttribute and RebuildTransformation test helper --- .../attribute/CompilationIDAttribute.java | 9 ++++ .../attribute/EnclosingMethodAttribute.java | 48 ++++++++++++++++++- .../classfile/attribute/InnerClassInfo.java | 16 +++---- .../attribute/NestHostAttribute.java | 10 ++++ .../attribute/SourceIDAttribute.java | 9 ++++ .../jdk/classfile/impl/BoundAttribute.java | 2 +- .../helpers/RebuildingTransformation.java | 12 ++--- 7 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java index d0a00087ae41e..8b5ff009c350b 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/CompilationIDAttribute.java @@ -29,6 +29,7 @@ import jdk.classfile.ClassElement; import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; /** @@ -55,4 +56,12 @@ public sealed interface CompilationIDAttribute 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/classfile/attribute/EnclosingMethodAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/EnclosingMethodAttribute.java index 3077d761eecc0..3d017aab829dd 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/EnclosingMethodAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/EnclosingMethodAttribute.java @@ -24,13 +24,17 @@ */ package jdk.classfile.attribute; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import java.util.Optional; import jdk.classfile.Attribute; import jdk.classfile.ClassElement; import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.constantpool.NameAndTypeEntry; +import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; /** @@ -56,13 +60,53 @@ public sealed interface EnclosingMethodAttribute */ 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, - NameAndTypeEntry method) { - return new UnboundAttribute.UnboundEnclosingMethodAttribute(className, method); + 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.natEntry(methodName.get(), methodType.get()) + : null); } } diff --git a/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java index 906fd15b3cff2..b1f0928ac0ae9 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/InnerClassInfo.java @@ -88,9 +88,9 @@ default boolean has(AccessFlag flag) { * @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, ClassEntry outerClass, - Utf8Entry innerName, int flags) { - return new UnboundAttribute.UnboundInnerClassInfo(innerClass, Optional.ofNullable(outerClass), Optional.ofNullable(innerName), flags); + static InnerClassInfo of(ClassEntry innerClass, Optional outerClass, + Optional innerName, int flags) { + return new UnboundAttribute.UnboundInnerClassInfo(innerClass, outerClass, innerName, flags); } /** @@ -100,10 +100,10 @@ static InnerClassInfo of(ClassEntry innerClass, ClassEntry outerClass, * @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, ClassDesc outerClass, String innerName, int flags) { - return new UnboundAttribute.UnboundInnerClassInfo(TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(innerClass))), - outerClass == null ? Optional.empty() : Optional.of(TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(outerClass)))), - innerName == null ? Optional.empty() : Optional.of(TemporaryConstantPool.INSTANCE.utf8Entry(innerName)), + 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); } @@ -114,7 +114,7 @@ static InnerClassInfo of(ClassDesc innerClass, ClassDesc outerClass, String inne * @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, ClassDesc outerClass, String innerName, AccessFlag... 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/classfile/attribute/NestHostAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/NestHostAttribute.java index c3407ace1d730..8b7d31a4e146d 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/NestHostAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/NestHostAttribute.java @@ -25,10 +25,12 @@ package jdk.classfile.attribute; +import java.lang.constant.ClassDesc; import jdk.classfile.Attribute; import jdk.classfile.ClassElement; import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; /** @@ -53,4 +55,12 @@ public sealed interface NestHostAttribute extends Attribute, 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/classfile/attribute/SourceIDAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/SourceIDAttribute.java index 9f9ae893aa48d..fa15bf67672b6 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/SourceIDAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/SourceIDAttribute.java @@ -30,6 +30,7 @@ import jdk.classfile.ClassModel; import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.BoundAttribute; +import jdk.classfile.impl.TemporaryConstantPool; import jdk.classfile.impl.UnboundAttribute; /** @@ -55,4 +56,12 @@ public sealed interface SourceIDAttribute 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/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java index 82541c3731e75..a8e92a3bbe9aa 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java @@ -822,7 +822,7 @@ public List classes() { : (Utf8Entry) classReader.entryByIndex(innerNameIndex); int flags = classReader.readU2(p + 6); p += 8; - elements[i] = InnerClassInfo.of(innerClass, outerClass, innerName, flags); + elements[i] = InnerClassInfo.of(innerClass, Optional.ofNullable(outerClass), Optional.ofNullable(innerName), flags); } classes = List.of(elements); } diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index ff36c8be210be..58fd4b06639a8 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -136,13 +136,13 @@ static byte[] transform(ClassModel clm) { } }); } - case CompilationIDAttribute a -> clb.with(CompilationIDAttribute.of(a.compilationId())); //missing attribute factory method accepting String + 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(), a.enclosingMethod().orElse(null))); //missing attribute factory method accepting symbols + 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).orElse(null), - ici.innerName().map(Utf8Entry::stringValue).orElse(null), + 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()); @@ -159,7 +159,7 @@ static byte[] transform(ClassModel clm) { 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())); //missing attribute factory method accpeting ClassDesc + 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 -> @@ -179,7 +179,7 @@ static byte[] transform(ClassModel clm) { case SignatureAttribute a -> clb.with(SignatureAttribute.of(a.asClassSignature())); 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())); //missing attribute factory method accepting String + 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()); From bb7e29474ecfcfbd1eb01d237593eb80d062944f Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 1 Jul 2022 15:07:52 +0200 Subject: [PATCH 012/190] TypeAnnotation and AnnotationValue API improvements * added TypeAnnotation factory methods accepting ClassDesc and AnnotationElement... AnnotationValue.OfConstant sub-classed to allow switch pattern matching RebuildingTransformation test helper adjusted * added TypeAnnotation.TargetInfo factory methods with validity checking for multi-target types adjusted RebuildTransformation test helper --- .../jdk/classfile/AnnotationValue.java | 97 ++++++++++-- .../classes/jdk/classfile/TypeAnnotation.java | 104 ++++++++++--- .../jdk/classfile/impl/AnnotationImpl.java | 145 +++++++++++++++++- .../jdk/classfile/impl/AnnotationReader.java | 13 +- .../jdk/classfile/impl/TargetInfoImpl.java | 40 +++++ .../helpers/RebuildingTransformation.java | 84 +++------- 6 files changed, 380 insertions(+), 103 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/AnnotationValue.java b/src/java.base/share/classes/jdk/classfile/AnnotationValue.java index 0817fe36d3fa0..ea0970acd6c25 100644 --- a/src/java.base/share/classes/jdk/classfile/AnnotationValue.java +++ b/src/java.base/share/classes/jdk/classfile/AnnotationValue.java @@ -66,18 +66,90 @@ sealed interface OfArray extends AnnotationValue /** Models a constant-valued element */ sealed interface OfConstant extends AnnotationValue - permits AnnotationImpl.OfConstantImpl { + 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 */ @@ -86,6 +158,11 @@ sealed interface OfEnum extends AnnotationValue /** {@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(); } @@ -136,7 +213,7 @@ static OfClass ofClass(ClassDesc className) { * @param value the string */ static OfConstant ofString(Utf8Entry value) { - return new AnnotationImpl.OfConstantImpl('s', value); + return new AnnotationImpl.OfStringImpl(value); } /** @@ -152,7 +229,7 @@ static OfConstant ofString(String value) { * @param value the double value */ static OfConstant ofDouble(DoubleEntry value) { - return new AnnotationImpl.OfConstantImpl('D', value); + return new AnnotationImpl.OfDoubleImpl(value); } /** @@ -168,7 +245,7 @@ static OfConstant ofDouble(double value) { * @param value the float value */ static OfConstant ofFloat(FloatEntry value) { - return new AnnotationImpl.OfConstantImpl('F', value); + return new AnnotationImpl.OfFloatImpl(value); } /** @@ -184,7 +261,7 @@ static OfConstant ofFloat(float value) { * @param value the long value */ static OfConstant ofLong(LongEntry value) { - return new AnnotationImpl.OfConstantImpl('J', value); + return new AnnotationImpl.OfLongImpl(value); } /** @@ -200,7 +277,7 @@ static OfConstant ofLong(long value) { * @param value the int value */ static OfConstant ofInt(IntegerEntry value) { - return new AnnotationImpl.OfConstantImpl('I', value); + return new AnnotationImpl.OfIntegerImpl(value); } /** @@ -216,7 +293,7 @@ static OfConstant ofInt(int value) { * @param value the short value */ static OfConstant ofShort(IntegerEntry value) { - return new AnnotationImpl.OfConstantImpl('S', value); + return new AnnotationImpl.OfShortImpl(value); } /** @@ -232,7 +309,7 @@ static OfConstant ofShort(short value) { * @param value the char value */ static OfConstant ofChar(IntegerEntry value) { - return new AnnotationImpl.OfConstantImpl('C', value); + return new AnnotationImpl.OfCharacterImpl(value); } /** @@ -248,7 +325,7 @@ static OfConstant ofChar(char value) { * @param value the byte value */ static OfConstant ofByte(IntegerEntry value) { - return new AnnotationImpl.OfConstantImpl('B', value); + return new AnnotationImpl.OfByteImpl(value); } /** @@ -264,7 +341,7 @@ static OfConstant ofByte(byte value) { * @param value the boolean value */ static OfConstant ofBoolean(IntegerEntry value) { - return new AnnotationImpl.OfConstantImpl('Z', value); + return new AnnotationImpl.OfBooleanImpl(value); } /** diff --git a/src/java.base/share/classes/jdk/classfile/TypeAnnotation.java b/src/java.base/share/classes/jdk/classfile/TypeAnnotation.java index 8c6ef114e9ca9..67b89bfd946cd 100755 --- a/src/java.base/share/classes/jdk/classfile/TypeAnnotation.java +++ b/src/java.base/share/classes/jdk/classfile/TypeAnnotation.java @@ -25,6 +25,7 @@ package jdk.classfile; +import java.lang.constant.ClassDesc; import java.util.List; import java.util.Set; @@ -56,6 +57,7 @@ import static jdk.classfile.Classfile.TAT_NEW; import static jdk.classfile.Classfile.TAT_RESOURCE_VARIABLE; import static jdk.classfile.Classfile.TAT_THROWS; +import jdk.classfile.impl.TemporaryConstantPool; /** * Models an annotation on a type use. @@ -176,7 +178,7 @@ Set whereApplicable() { * @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 annltation elements + * @param annotationElements the annotation elements */ static TypeAnnotation of(TargetInfo targetInfo, List targetPath, Utf8Entry annotationClassUtf8Entry, @@ -185,6 +187,46 @@ static TypeAnnotation of(TargetInfo targetInfo, List targetPa 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. */ @@ -196,36 +238,48 @@ 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 new TargetInfoImpl.TypeParameterTargetImpl(TargetType.CLASS_TYPE_PARAMETER, typeParameterIndex); + return ofTypeParameter(TargetType.CLASS_TYPE_PARAMETER, typeParameterIndex); } static TypeParameterTarget ofMethodTypeParameter(int typeParameterIndex) { - return new TargetInfoImpl.TypeParameterTargetImpl(TargetType.METHOD_TYPE_PARAMETER, 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 new TargetInfoImpl.TypeParameterBoundTargetImpl(TargetType.CLASS_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + return ofTypeParameterBound(TargetType.CLASS_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); } static TypeParameterBoundTarget ofMethodTypeParameterBound(int typeParameterIndex, int boundIndex) { - return new TargetInfoImpl.TypeParameterBoundTargetImpl(TargetType.METHOD_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + return ofTypeParameterBound(TargetType.METHOD_TYPE_PARAMETER_BOUND, typeParameterIndex, boundIndex); + } + + static EmptyTarget of(TargetType targetType) { + return new TargetInfoImpl.EmptyTargetImpl(targetType); } static EmptyTarget ofField() { - return new TargetInfoImpl.EmptyTargetImpl(TargetType.FIELD); + return of(TargetType.FIELD); } static EmptyTarget ofMethodReturn() { - return new TargetInfoImpl.EmptyTargetImpl(TargetType.METHOD_RETURN); + return of(TargetType.METHOD_RETURN); } static EmptyTarget ofMethodReceiver() { - return new TargetInfoImpl.EmptyTargetImpl(TargetType.METHOD_RECEIVER); + return of(TargetType.METHOD_RECEIVER); } static FormalParameterTarget ofMethodFormalParameter(int formalParameterIndex) { @@ -236,52 +290,64 @@ 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 new TargetInfoImpl.LocalVarTargetImpl(TargetType.LOCAL_VARIABLE, table); + return ofVariable(TargetType.LOCAL_VARIABLE, table); } static LocalVarTarget ofResourceVariable(List table) { - return new TargetInfoImpl.LocalVarTargetImpl(TargetType.RESOURCE_VARIABLE, 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 new TargetInfoImpl.OffsetTargetImpl(TargetType.INSTANCEOF, target); + return ofOffset(TargetType.INSTANCEOF, target); } static OffsetTarget ofNewExpr(Label target) { - return new TargetInfoImpl.OffsetTargetImpl(TargetType.NEW, target); + return ofOffset(TargetType.NEW, target); } static OffsetTarget ofConstructorReference(Label target) { - return new TargetInfoImpl.OffsetTargetImpl(TargetType.CONSTRUCTOR_REFERENCE, target); + return ofOffset(TargetType.CONSTRUCTOR_REFERENCE, target); } static OffsetTarget ofMethodReference(Label target) { - return new TargetInfoImpl.OffsetTargetImpl(TargetType.METHOD_REFERENCE, 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 new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.CAST, target, typeArgumentIndex); + return ofTypeArgument(TargetType.CAST, target, typeArgumentIndex); } static TypeArgumentTarget ofConstructorInvocationTypeArgument(Label target, int typeArgumentIndex) { - return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); + return ofTypeArgument(TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); } static TypeArgumentTarget ofMethodInvocationTypeArgument(Label target, int typeArgumentIndex) { - return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); + return ofTypeArgument(TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, target, typeArgumentIndex); } static TypeArgumentTarget ofConstructorReferenceTypeArgument(Label target, int typeArgumentIndex) { - return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); + return ofTypeArgument(TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); } static TypeArgumentTarget ofMethodReferenceTypeArgument(Label target, int typeArgumentIndex) { - return new TargetInfoImpl.TypeArgumentTargetImpl(TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); + return ofTypeArgument(TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, target, typeArgumentIndex); } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java b/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java index dc207ab8a30ff..44767ad0fa9c7 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AnnotationImpl.java @@ -25,8 +25,7 @@ package jdk.classfile.impl; import jdk.classfile.*; -import jdk.classfile.constantpool.AnnotationConstantValueEntry; -import jdk.classfile.constantpool.Utf8Entry; +import jdk.classfile.constantpool.*; import java.lang.constant.ConstantDesc; import java.util.List; @@ -89,20 +88,150 @@ public void writeTo(BufWriter buf) { } } - public record OfConstantImpl(char tag, AnnotationConstantValueEntry constant) - implements AnnotationValue.OfConstant { + 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 - public void writeTo(BufWriter buf) { + default void writeTo(BufWriter buf) { buf.writeU1(tag()); - buf.writeIndex(constant); + 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 ConstantDesc constantValue() { - return constant.constantValue(); + 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) diff --git a/src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java b/src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java index 4a49f8877ba3f..8cbd5bfaa2157 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AnnotationReader.java @@ -29,7 +29,7 @@ import jdk.classfile.AnnotationElement; import jdk.classfile.AnnotationValue; import jdk.classfile.ClassReader; -import jdk.classfile.constantpool.AnnotationConstantValueEntry; +import jdk.classfile.constantpool.*; import jdk.classfile.TypeAnnotation; import static jdk.classfile.Classfile.*; import static jdk.classfile.TypeAnnotation.TargetInfo.*; @@ -58,8 +58,15 @@ public static AnnotationValue readElementValue(ClassReader classReader, int p) { char tag = (char) classReader.readU1(p); ++p; return switch (tag) { - case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z' -> new AnnotationImpl.OfConstantImpl(tag, (AnnotationConstantValueEntry) classReader.readEntry(p)); - case 's' -> new AnnotationImpl.OfConstantImpl(tag, classReader.readUtf8Entry(p)); + 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)); diff --git a/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java b/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java index 70ba5fec68117..c1073954319bb 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/TargetInfoImpl.java @@ -25,8 +25,10 @@ package jdk.classfile.impl; import java.util.List; +import java.util.Objects; import jdk.classfile.Label; import jdk.classfile.TypeAnnotation.*; +import static jdk.classfile.Classfile.*; /** * @@ -36,8 +38,20 @@ 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 { @@ -49,9 +63,19 @@ public TargetType targetType() { 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 { @@ -70,6 +94,11 @@ public TargetType targetType() { 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(); @@ -88,9 +117,20 @@ public TargetType targetType() { } 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/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 58fd4b06639a8..a197075cd4c8c 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -25,7 +25,6 @@ package helpers; import java.lang.constant.ClassDesc; -import java.lang.reflect.AccessFlag; import java.util.HashMap; import java.util.List; import jdk.classfile.*; @@ -200,84 +199,43 @@ 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.OfConstant oc -> //missing distinction between constant annotation value types - switch (oc.tag()) { - case 's' -> AnnotationValue.of((String)oc.constantValue()); - case 'D' -> AnnotationValue.of((double)oc.constantValue()); - case 'F' -> AnnotationValue.of((float)oc.constantValue()); - case 'J' -> AnnotationValue.of((long)oc.constantValue()); - case 'I' -> AnnotationValue.of((int)oc.constantValue()); - case 'S' -> AnnotationValue.of((short)(int)oc.constantValue()); - case 'C' -> AnnotationValue.of((char)(int)oc.constantValue()); - case 'B' -> AnnotationValue.of((byte)(int)oc.constantValue()); - case 'Z' -> AnnotationValue.of(1 == (int)oc.constantValue()); - default -> throw new AssertionError("Unexpected annotation value tag: " + oc.tag()); - }; - case AnnotationValue.OfClass oc -> AnnotationValue.ofClass(ClassDesc.ofDescriptor(oc.className().stringValue())); //missing AnnotationValue factory method accepting ClassDesc - case AnnotationValue.OfEnum oe -> AnnotationValue.ofEnum(ClassDesc.ofDescriptor(oe.className().stringValue()), oe.constantName().stringValue()); //missing AnnotationValue factory method accepting ClassDesc + 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( //missing TypeAnnotation factory method accepting ClassDesc and AnnotationElement vararg + return annotations.stream().map(ta -> TypeAnnotation.of( transformTargetInfo(ta.targetInfo(), cob, labels), ta.targetPath().stream().map(tpc -> TypeAnnotation.TypePathComponent.of(tpc.typePathKind().tag(), tpc.typeArgumentIndex())).toList(), - ta.className(), + 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) { //missing flat decompositions to individual target types + return switch (ti) { case TypeAnnotation.CatchTarget t -> TypeAnnotation.TargetInfo.ofExceptionParameter(t.exceptionTableIndex()); - case TypeAnnotation.EmptyTarget t -> - switch (t.targetType()) { - case FIELD -> TypeAnnotation.TargetInfo.ofField(); - case METHOD_RETURN -> TypeAnnotation.TargetInfo.ofMethodReturn(); - case METHOD_RECEIVER -> TypeAnnotation.TargetInfo.ofMethodReceiver(); - default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); - }; + 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 -> - switch (t.targetType()) { - case CLASS_TYPE_PARAMETER_BOUND -> TypeAnnotation.TargetInfo.ofClassTypeParameterBound(t.typeParameterIndex(), t.boundIndex()); - case METHOD_TYPE_PARAMETER_BOUND -> TypeAnnotation.TargetInfo.ofMethodTypeParameterBound(t.typeParameterIndex(), t.boundIndex()); - default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); - }; - case TypeAnnotation.TypeParameterTarget t -> - switch (t.targetType()) { - case CLASS_TYPE_PARAMETER -> TypeAnnotation.TargetInfo.ofClassTypeParameter(t.typeParameterIndex()); - case METHOD_TYPE_PARAMETER -> TypeAnnotation.TargetInfo.ofMethodTypeParameter(t.typeParameterIndex()); - default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); - }; - case TypeAnnotation.LocalVarTarget t -> - switch (t.targetType()) { - case LOCAL_VARIABLE -> TypeAnnotation.TargetInfo.ofLocalVariable(t.table().stream().map(lvti -> + 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 RESOURCE_VARIABLE -> TypeAnnotation.TargetInfo.ofResourceVariable(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()); - default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); - }; - case TypeAnnotation.OffsetTarget t -> - switch (t.targetType()) { - case INSTANCEOF -> TypeAnnotation.TargetInfo.ofInstanceofExpr(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); - case NEW -> TypeAnnotation.TargetInfo.ofNewExpr(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); - case CONSTRUCTOR_REFERENCE -> TypeAnnotation.TargetInfo.ofConstructorReference(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); - case METHOD_REFERENCE -> TypeAnnotation.TargetInfo.ofMethodReference(labels.computeIfAbsent(t.target(), l -> cob.newLabel())); - default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); - }; - case TypeAnnotation.TypeArgumentTarget t -> - switch (t.targetType()) { - case CAST -> TypeAnnotation.TargetInfo.ofCastExpr(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); - case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofConstructorInvocationTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); - case METHOD_INVOCATION_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofMethodInvocationTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); - case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofConstructorReferenceTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); - case METHOD_REFERENCE_TYPE_ARGUMENT -> TypeAnnotation.TargetInfo.ofMethodReferenceTypeArgument(labels.computeIfAbsent(t.target(), l -> cob.newLabel()), t.typeArgumentIndex()); - default -> throw new AssertionError("Unexpected type annotation target type: " + t.targetType()); - }; + 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()); }; } } From d8f30a7f8986cafa9909bfe603a4bd6e14dcfc9d Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 1 Jul 2022 17:52:13 +0200 Subject: [PATCH 013/190] refactored to FieldModel::fieldTypeSymbol and MethodModel::methodTypeSymbol (#13) refactored to FieldModel::fieldTypeSymbol and MethodModel::methodTypeSymbol (#13) added round testing of signatures in RebuildTransformation test helper --- .../share/classes/jdk/classfile/FieldModel.java | 2 +- .../share/classes/jdk/classfile/MethodModel.java | 2 +- .../jdk/classfile/impl/StackMapDecoder.java | 2 +- .../jdk/classfile/snippets/PackageSnippets.java | 2 +- .../jdk/classfile/transforms/ClassRemapper.java | 4 ++-- .../classfile/AdvancedTransformationsTest.java | 10 +++++----- .../helpers/RebuildingTransformation.java | 16 ++++++++-------- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/FieldModel.java b/src/java.base/share/classes/jdk/classfile/FieldModel.java index 90956785b7c52..27de1c502659b 100755 --- a/src/java.base/share/classes/jdk/classfile/FieldModel.java +++ b/src/java.base/share/classes/jdk/classfile/FieldModel.java @@ -54,7 +54,7 @@ public sealed interface FieldModel Utf8Entry fieldType(); /** {@return the field descriptor of this field, as a symbolic descriptor} */ - default ClassDesc descriptorSymbol() { + default ClassDesc fieldTypeSymbol() { return ClassDesc.ofDescriptor(fieldType().stringValue()); } } diff --git a/src/java.base/share/classes/jdk/classfile/MethodModel.java b/src/java.base/share/classes/jdk/classfile/MethodModel.java index 125d6785c9a58..16a71fd8f4612 100755 --- a/src/java.base/share/classes/jdk/classfile/MethodModel.java +++ b/src/java.base/share/classes/jdk/classfile/MethodModel.java @@ -54,7 +54,7 @@ public sealed interface MethodModel Utf8Entry methodType(); /** {@return the method descriptor of this method, as a symbolic descriptor} */ - default MethodTypeDesc descriptorSymbol() { + default MethodTypeDesc methodTypeSymbol() { return MethodTypeDesc.ofDescriptor(methodType().stringValue()); } diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java index 87499869acd19..23aba3cbcfaa6 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java @@ -64,7 +64,7 @@ public class StackMapDecoder { static StackMapFrame initFrame(MethodModel method) { VerificationTypeInfo vtis[]; - var mdesc = method.descriptorSymbol(); + var mdesc = method.methodTypeSymbol(); int i = 0; if (!method.flags().has(AccessFlag.STATIC)) { vtis = new VerificationTypeInfo[mdesc.parameterCount() + 1]; diff --git a/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java b/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java index 83b0202f15c61..e55f3dd794e26 100755 --- a/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java +++ b/src/java.base/share/classes/jdk/classfile/snippets/PackageSnippets.java @@ -197,7 +197,7 @@ void fooToBarUnrolled(ClassModel classModel) { classBuilder -> { for (ClassElement ce : classModel) { if (ce instanceof MethodModel mm) { - classBuilder.withMethod(mm.methodName().stringValue(), mm.descriptorSymbol(), + classBuilder.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), methodBuilder -> { for (MethodElement me : mm) { diff --git a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java b/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java index 24915be77259d..a620fc84af2b5 100644 --- a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java +++ b/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java @@ -107,9 +107,9 @@ public ClassTransform classTransform() { return (ClassBuilder clb, ClassElement cle) -> { switch (cle) { case FieldModel fm -> - clb.withField(fm.fieldName().stringValue(), map(fm.descriptorSymbol()), fb -> fm.forEachElement(fieldTransform().resolve(fb).consumer())); + clb.withField(fm.fieldName().stringValue(), map(fm.fieldTypeSymbol()), fb -> fm.forEachElement(fieldTransform().resolve(fb).consumer())); case MethodModel mm -> - clb.withMethod(mm.methodName().stringValue(), mapMethodDesc(mm.descriptorSymbol()), mm.flags().flagsMask(), mb -> mm.forEachElement(methodTransform().resolve(mb).consumer())); + clb.withMethod(mm.methodName().stringValue(), mapMethodDesc(mm.methodTypeSymbol()), mm.flags().flagsMask(), mb -> mm.forEachElement(methodTransform().resolve(mb).consumer())); case Superclass sc -> clb.withSuperclass(map(sc.superclassEntry().asSymbol())); case Interfaces ins -> diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index cbfdf19880726..181fb75a00f8e 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -70,7 +70,7 @@ public void testShiftLocals() throws Exception { if (cle instanceof MethodModel mm) { clb.transformMethod(mm, (mb, me) -> { if (me instanceof CodeModel com) { - var shifter = new CodeLocalsShifter(mm.flags(), mm.descriptorSymbol()); + var shifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); shifter.addLocal(TypeKind.ReferenceType); shifter.addLocal(TypeKind.LongType); shifter.addLocal(TypeKind.IntType); @@ -105,9 +105,9 @@ public void testRemapClass() throws Exception { .orElse(ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER) , null)); //System.out::print)); remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> - verifySignature(f.descriptorSymbol(), sa.asTypeSignature()))); + verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature()))); remapped.methods().forEach(m -> m.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> { - var md = m.descriptorSymbol(); + var md = m.methodTypeSymbol(); var ms = sa.asMethodSignature(); verifySignature(md.returnType(), ms.result()); var args = ms.arguments(); @@ -200,11 +200,11 @@ private static byte[] instrument(ClassModel target, ClassModel instrumentor, Pre target.forEachElement(cle -> { CodeModel instrumentorCodeModel; if (cle instanceof MethodModel mm && ((instrumentorCodeModel = instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue())) != null)) { - clb.withMethod(mm.methodName().stringValue(), mm.descriptorSymbol(), mm.flags().flagsMask(), + clb.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), mb -> mm.forEachElement(me -> { if (me instanceof CodeModel targetCodeModel) { //instrumented methods are merged - var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.descriptorSymbol()); + var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); var instrumentorCodeRemapperAndShifter = instrumentorClassRemapper.codeTransform() .andThen(instrumentorLocalsShifter); diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index a197075cd4c8c..6c6a852e5ddbc 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -45,7 +45,7 @@ static byte[] transform(ClassModel clm) { 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.descriptorSymbol(), fb -> { + clb.withField(fm.fieldName().stringValue(), fm.fieldTypeSymbol(), fb -> { for (var fe : fm) { switch (fe) { case AccessFlags af -> fb.withFlags(af.flagsMask()); @@ -55,7 +55,7 @@ static byte[] transform(ClassModel clm) { 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(a.asTypeSignature())); + 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()); @@ -63,7 +63,7 @@ static byte[] transform(ClassModel clm) { } }); case MethodModel mm -> { - clb.withMethod(mm.methodName().stringValue(), mm.descriptorSymbol(), mm.flags().flagsMask(), mb -> { + clb.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), mb -> { for (var me : mm) { switch (me) { case AccessFlags af -> mb.withFlags(af.flagsMask()); @@ -108,8 +108,8 @@ static byte[] transform(ClassModel clm) { 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(), pi.signatureSymbol(), 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 CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); @@ -127,7 +127,7 @@ static byte[] transform(ClassModel clm) { 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(a.asMethodSignature())); + 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()); @@ -168,14 +168,14 @@ static byte[] transform(ClassModel clm) { 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(sa.asTypeSignature())); + 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(a.asClassSignature())); + 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())); From 59479ff0276885803c7f09e58cc3c81b6d8e533f Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Mon, 4 Jul 2022 09:04:45 +0200 Subject: [PATCH 014/190] Model stack map frames in a type hierarchy where each type represents a frame type. Doing so, make the chop size available to consumers of frames. --- .../attribute/StackMapTableAttribute.java | 38 +++++++- .../jdk/classfile/impl/BoundAttribute.java | 4 +- .../jdk/classfile/impl/StackMapDecoder.java | 94 +++++++++++++------ 3 files changed, 102 insertions(+), 34 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java index 4ea64d7f7d168..fac2f019e9d80 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java @@ -49,7 +49,7 @@ public sealed interface StackMapTableAttribute /** * {@return the initial frame} */ - StackMapFrame initFrame(); + StackMapFrame.Full initFrame(); /** * The possible types for a stack slot. @@ -144,16 +144,44 @@ sealed interface UninitializedVerificationTypeInfo extends VerificationTypeInfo * A stack map frame. */ sealed interface StackMapFrame - permits StackMapDecoder.StackMapFrameImpl { + permits StackMapFrame.Same, StackMapFrame.Same1, StackMapFrame.Append, StackMapFrame.Chop, StackMapFrame.Full { int frameType(); FrameKind frameKind(); int offsetDelta(); - List declaredLocals(); - List declaredStack(); - int absoluteOffset(); List effectiveLocals(); List effectiveStack(); + + sealed interface Same extends StackMapFrame permits StackMapDecoder.StackMapFrameSameImpl { + + boolean extended(); + } + sealed interface Same1 extends StackMapFrame permits StackMapDecoder.StackMapFrameSame1Impl { + + boolean extended(); + + VerificationTypeInfo declaredStack(); + } + + sealed interface Append extends StackMapFrame permits StackMapDecoder.StackMapFrameAppendImpl { + + List declaredLocals(); + } + + sealed interface Chop extends StackMapFrame permits StackMapDecoder.StackMapFrameChopImpl { + + List choppedLocals(); + } + + sealed interface Full extends StackMapFrame permits StackMapDecoder.StackMapFrameFullImpl { + + default List declaredStack() { + return effectiveStack(); + } + default List declaredLocals() { + return effectiveLocals(); + } + } } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java index a8e92a3bbe9aa..ddc9895edabae 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java @@ -230,7 +230,7 @@ public static final class BoundStackMapTableAttribute implements StackMapTableAttribute { final MethodModel method; List entries = null; - StackMapFrame initFrame = null; + StackMapFrame.Full initFrame = null; public BoundStackMapTableAttribute(CodeModel code, ClassReader cf, AttributeMapper mapper, int pos) { super(cf, mapper, pos); @@ -238,7 +238,7 @@ public BoundStackMapTableAttribute(CodeModel code, ClassReader cf, AttributeMapp } @Override - public StackMapFrame initFrame() { + public StackMapFrame.Full initFrame() { if (initFrame == null) initFrame = StackMapDecoder.initFrame(method); return initFrame; diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java index 23aba3cbcfaa6..0a0abf54db092 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java @@ -53,16 +53,16 @@ public class StackMapDecoder { private final ClassReader classReader; private final int pos; - private final StackMapFrame initFrame; + private final StackMapFrame.Full initFrame; private int p; - StackMapDecoder(ClassReader classReader, int pos, StackMapFrame initFrame) { + StackMapDecoder(ClassReader classReader, int pos, StackMapFrame.Full initFrame) { this.classReader = classReader; this.pos = pos; this.initFrame = initFrame; } - static StackMapFrame initFrame(MethodModel method) { + static StackMapFrame.Full initFrame(MethodModel method) { VerificationTypeInfo vtis[]; var mdesc = method.methodTypeSymbol(); int i = 0; @@ -87,45 +87,47 @@ static StackMapFrame initFrame(MethodModel method) { default -> new StackMapDecoder.ObjectVerificationTypeInfoImpl(TemporaryConstantPool.INSTANCE.classEntry(arg)); }; } - return new StackMapFrameImpl(FULL, FrameKind.FULL_FRAME, -1, -1, List.of(vtis), List.of(), List.of(vtis), List.of()); + return new StackMapFrameFullImpl(FULL, FrameKind.FULL_FRAME, -1, -1, List.of(vtis), List.of()); } List entries() { p = pos; - var frame = initFrame; + StackMapFrame frame = initFrame; var entries = new StackMapFrame[u2()]; for (int ei = 0; ei < entries.length; ei++) { int frameType = classReader.readU1(p++); if (frameType < 64) { - frame = new StackMapFrameImpl(frameType, FrameKind.SAME, + frame = new StackMapFrameSameImpl(frameType, FrameKind.SAME, frameType, frame.absoluteOffset() + frameType + 1, - List.of(), List.of(), + false, frame.effectiveLocals(), List.of()); } else if (frameType < 128) { - var stack = List.of(readVerificationTypeInfo()); - frame = new StackMapFrameImpl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM, + var stack = readVerificationTypeInfo(); + frame = new StackMapFrameSame1Impl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM, frameType - 64, frame.absoluteOffset() + frameType - 63, - List.of(), stack, - frame.effectiveLocals(), stack); + false, + stack, + frame.effectiveLocals(), List.of(stack)); } else { if (frameType < SAME_LOCALS_1_STACK_ITEM_EXTENDED) throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType); int offsetDelta = u2(); if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { - var stack = List.of(readVerificationTypeInfo()); - frame = new StackMapFrameImpl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM_EXTENDED, + var stack = readVerificationTypeInfo(); + frame = new StackMapFrameSame1Impl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM_EXTENDED, offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - List.of(), stack, - frame.effectiveLocals(), stack); + true, + stack, + frame.effectiveLocals(), List.of(stack)); } else if (frameType < SAME_EXTENDED) { - frame = new StackMapFrameImpl(frameType, FrameKind.CHOP, + frame = new StackMapFrameChopImpl(frameType, FrameKind.CHOP, offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - List.of(), List.of(), + frame.effectiveLocals().subList(frame.effectiveLocals().size() + frameType - SAME_EXTENDED, frame.effectiveLocals().size()), frame.effectiveLocals().subList(0, frame.effectiveLocals().size() + frameType - SAME_EXTENDED), List.of()); } else if (frameType == SAME_EXTENDED) { - frame = new StackMapFrameImpl(frameType, FrameKind.SAME_FRAME_EXTENDED, + frame = new StackMapFrameSameImpl(frameType, FrameKind.SAME_FRAME_EXTENDED, offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - List.of(), List.of(), + true, frame.effectiveLocals(), List.of()); } else if (frameType < SAME_EXTENDED + 4) { int actSize = frame.effectiveLocals().size(); @@ -133,9 +135,9 @@ List entries() { for (int i = actSize; i < locals.length; i++) locals[i] = readVerificationTypeInfo(); var locList = List.of(locals); - frame = new StackMapFrameImpl(frameType, FrameKind.APPEND, + frame = new StackMapFrameAppendImpl(frameType, FrameKind.APPEND, offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - locList.subList(actSize, locList.size()), List.of(), + locList.subList(actSize, locList.size()), locList, List.of()); } else { var locals = new VerificationTypeInfo[u2()]; @@ -146,9 +148,8 @@ List entries() { stack[i] = readVerificationTypeInfo(); var locList = List.of(locals); var stackList = List.of(stack); - frame = new StackMapFrameImpl(frameType, FrameKind.FULL_FRAME, + frame = new StackMapFrameFullImpl(frameType, FrameKind.FULL_FRAME, offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - locList, stackList, locList, stackList); } } @@ -219,14 +220,53 @@ private int u2() { return v; } - public static record StackMapFrameImpl(int frameType, + public static record StackMapFrameSameImpl(int frameType, FrameKind frameKind, int offsetDelta, int absoluteOffset, - List declaredLocals, - List declaredStack, + boolean extended, List effectiveLocals, List effectiveStack) - implements StackMapFrame { + implements StackMapFrame.Same { + } + + public static record StackMapFrameSame1Impl(int frameType, + FrameKind frameKind, + int offsetDelta, + int absoluteOffset, + boolean extended, + VerificationTypeInfo declaredStack, + List effectiveLocals, + List effectiveStack) + implements StackMapFrame.Same1 { + } + + public static record StackMapFrameAppendImpl(int frameType, + FrameKind frameKind, + int offsetDelta, + int absoluteOffset, + List declaredLocals, + List effectiveLocals, + List effectiveStack) + implements StackMapFrame.Append { + } + + public static record StackMapFrameChopImpl(int frameType, + FrameKind frameKind, + int offsetDelta, + int absoluteOffset, + List choppedLocals, + List effectiveLocals, + List effectiveStack) + implements StackMapFrame.Chop { + } + + public static record StackMapFrameFullImpl(int frameType, + FrameKind frameKind, + int offsetDelta, + int absoluteOffset, + List effectiveLocals, + List effectiveStack) + implements StackMapFrame.Full { } } From 99a17b3294ac2d4cdc2a5258dfd22122ebda5019 Mon Sep 17 00:00:00 2001 From: Brian Goetz Date: Mon, 4 Jul 2022 10:14:07 +0200 Subject: [PATCH 015/190] Added some testing of local variable management, with BlockBuilder --- test/jdk/jdk/classfile/BuilderBlockTest.java | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/jdk/jdk/classfile/BuilderBlockTest.java b/test/jdk/jdk/classfile/BuilderBlockTest.java index ab0adab9fe327..5b1ae2914731d 100644 --- a/test/jdk/jdk/classfile/BuilderBlockTest.java +++ b/test/jdk/jdk/classfile/BuilderBlockTest.java @@ -153,4 +153,64 @@ public void testIfThenElse2() throws Exception { assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); } + + public 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); + })); + }); + } + + public 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); + })); + }); + } + + public 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_(); + })); + }); + } } From f2ac7705398aa8720d424cd69ad771665ebbfaf9 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 4 Jul 2022 12:03:20 +0200 Subject: [PATCH 016/190] test/jdk/jdk/classfile/examples fixed --- .../jdk/classfile/examples/ExampleGallery.java | 6 ++++-- .../jdk/classfile/examples/ModuleExamples.java | 15 +++++++++------ .../jdk/classfile/examples/TransformExamples.java | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/test/jdk/jdk/classfile/examples/ExampleGallery.java b/test/jdk/jdk/classfile/examples/ExampleGallery.java index 00a04d5ff91ee..eaae80fbd4974 100755 --- a/test/jdk/jdk/classfile/examples/ExampleGallery.java +++ b/test/jdk/jdk/classfile/examples/ExampleGallery.java @@ -272,8 +272,10 @@ public byte[] addInstrumentationBeforeInvoke(ClassModel cm) { public byte[] replaceIntegerConstant(ClassModel cm) { return cm.transform(ClassTransform.transformingMethodBodies((codeB, codeE) -> { switch (codeE) { - case ConstantInstruction ci - && ci.constantValue() instanceof Integer i -> codeB.constantInstruction(i + 1); + 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/ModuleExamples.java b/test/jdk/jdk/classfile/examples/ModuleExamples.java index 5d4616f2b807b..bda61f2e40460 100644 --- a/test/jdk/jdk/classfile/examples/ModuleExamples.java +++ b/test/jdk/jdk/classfile/examples/ModuleExamples.java @@ -28,10 +28,13 @@ import jdk.classfile.ClassModel; import jdk.classfile.Classfile; import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleAttribute.ModuleAttributeBuilder; import jdk.classfile.attribute.ModuleMainClassAttribute; import jdk.classfile.attribute.ModulePackagesAttribute; import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; import jdk.classfile.Attributes; +import jdk.classfile.jdktypes.PackageDesc; +import jdk.classfile.jdktypes.ModuleDesc; import org.testng.annotations.Test; import java.io.IOException; @@ -41,7 +44,6 @@ import java.nio.file.FileSystems; import java.util.List; import java.util.function.Consumer; -import jdk.classfile.ModuleAttributeBuilder; public class ModuleExamples { private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); @@ -52,7 +54,7 @@ public void examineModule() throws IOException { 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().asString()); + 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); @@ -64,16 +66,17 @@ public void examineModule() throws IOException { @Test(expectedExceptions = RuntimeException.class) public void buildModuleFromScratch() { - String moduleName = "the.very.best.module"; + var moduleName = ModuleDesc.of("the.very.best.module"); int moduleFlags = 0; Consumer handler = (mb -> {mb - .exports("export.some.pkg", 0) - .exports("qualified.export.to" , 0, "to.first.pkg", "to.another.pkg"); + .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, moduleFlags, handler), List.of(), clb -> { + 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;"), diff --git a/test/jdk/jdk/classfile/examples/TransformExamples.java b/test/jdk/jdk/classfile/examples/TransformExamples.java index 4876d54942e0c..f8143b69b4ae7 100755 --- a/test/jdk/jdk/classfile/examples/TransformExamples.java +++ b/test/jdk/jdk/classfile/examples/TransformExamples.java @@ -47,7 +47,7 @@ public byte[] deleteAllMethods(ClassModel cm) { public byte[] deleteFieldsWithDollarInName(ClassModel cm) { return cm.transform((b, e) -> { - if (!(e instanceof FieldModel fm && fm.fieldName().asString().contains("$"))) + if (!(e instanceof FieldModel fm && fm.fieldName().stringValue().contains("$"))) b.with(e); }); } From 848016318448c7d27b4b77f1ac72ccb63307e38a Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 4 Jul 2022 14:24:24 +0200 Subject: [PATCH 017/190] Fixed frame offset valid boundaries in StackMapGenerator + added test --- .../jdk/classfile/impl/StackMapGenerator.java | 4 ++-- test/jdk/jdk/classfile/StackMapsTest.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 0835075f27e51..25cb83b92920f 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -929,8 +929,8 @@ private BitSet detectFrameOffsets() { var offsets = new BitSet() { @Override public void set(int i) { - if (i < 0 || i > bytecode.capacity()) - generatorError("Branch offset out of bytecode range: " + i); + if (i < 0 || i >= bytecode.capacity()) + generatorError("Frame offset out of bytecode range"); super.set(i); } }; diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index 0315a404eec8f..5e14bb52c055c 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -44,6 +44,7 @@ import java.lang.constant.MethodTypeDesc; import java.util.List; import java.lang.reflect.AccessFlag; +import jdk.classfile.util.ClassPrinter; /** * StackMapsTest @@ -157,6 +158,17 @@ public void testPattern10() throws Exception { testTransformedStackMaps("/testdata/Pattern10.class"); } + @Test(expectedExceptions = VerifyError.class) + public void testFrameOutOfBytecodeRange() { + 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); + }))); + } + @Test(expectedExceptions = IllegalArgumentException.class) public void testMethodSwitchFromStatic() { Classfile.build(ClassDesc.of("TestClass"), clb -> From ace0f43573a28cb6186f0932f2d5def6e67adc01 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Thu, 7 Jul 2022 09:25:22 +0200 Subject: [PATCH 018/190] Adds toString methods to all classes that appear within a CodeModel. --- .../jdk/classfile/impl/AbstractAttributeMapper.java | 6 ++++++ .../jdk/classfile/impl/AbstractInstruction.java | 5 +++++ .../classes/jdk/classfile/impl/AccessFlagsImpl.java | 5 +++++ .../classes/jdk/classfile/impl/BoundAttribute.java | 5 +++++ .../jdk/classfile/impl/BoundCharacterRange.java | 6 ++++++ .../classes/jdk/classfile/impl/BoundLocalVariable.java | 5 +++++ .../jdk/classfile/impl/BoundLocalVariableType.java | 5 +++++ .../jdk/classfile/impl/BufferedCodeBuilder.java | 10 ++++++++++ .../jdk/classfile/impl/BufferedFieldBuilder.java | 5 +++++ .../jdk/classfile/impl/BufferedMethodBuilder.java | 6 ++++++ .../share/classes/jdk/classfile/impl/ClassImpl.java | 5 +++++ .../jdk/classfile/impl/ClassfileVersionImpl.java | 5 +++++ .../share/classes/jdk/classfile/impl/CodeImpl.java | 5 +++++ .../classes/jdk/classfile/impl/DirectCodeBuilder.java | 5 +++++ .../share/classes/jdk/classfile/impl/FieldImpl.java | 6 ++++++ .../classes/jdk/classfile/impl/InterfacesImpl.java | 8 ++++++++ .../classes/jdk/classfile/impl/LineNumberImpl.java | 5 +++++ .../share/classes/jdk/classfile/impl/MethodImpl.java | 6 ++++++ .../classes/jdk/classfile/impl/SuperclassImpl.java | 5 +++++ .../classes/jdk/classfile/impl/UnboundAttribute.java | 4 ++++ 20 files changed, 112 insertions(+) diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java index 7ba55f4c143a7..09a7275c4357e 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractAttributeMapper.java @@ -102,4 +102,10 @@ public boolean allowMultiple() { public int validSince() { return majorVersion; } + + @Override + public String toString() { + return String.format("AttributeMapper[name=%s, allowMultiple=%b, validSince=%d, whereApplicable=%s]", + name, allowMultiple, majorVersion, whereApplicable); + } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java index 195bf002d6e86..4e6654703965f 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java @@ -1392,6 +1392,11 @@ ClassEntry catchTypeEntry() { 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 diff --git a/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java b/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java index 68a51a49b009f..db3d4baed6ad3 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AccessFlagsImpl.java @@ -84,4 +84,9 @@ public AccessFlag.Location location() { 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/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java index ddc9895edabae..124d5586c2698 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java @@ -111,6 +111,11 @@ public ConstantPool constantPool() { return classReader; } + @Override + public String toString() { + return String.format("Attribute[name=%s]", mapper.name()); + } + List readEntryList(int p) { // @@@ Could use JavaUtilCollectionAccess.listFromTrustedArrayNullsAllowed to avoid copy int cnt = classReader.readU2(p); diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.java b/src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.java index 6617b737a442e..3f208d29a1ed4 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundCharacterRange.java @@ -94,4 +94,10 @@ public Kind codeKind() { public int sizeInBytes() { return 0; } + + @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/classfile/impl/BoundLocalVariable.java b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java index 63a85657a99c5..a21186257bb4f 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java @@ -56,4 +56,9 @@ public Utf8Entry type() { 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/classfile/impl/BoundLocalVariableType.java b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.java index cec66630be90f..ad240cf6f3482 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariableType.java @@ -55,4 +55,9 @@ public Utf8Entry signature() { 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/classfile/impl/BufferedCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java index 6593f465b42a3..1ca57c551e2d8 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java @@ -139,6 +139,11 @@ public CodeBuilder with(CodeElement 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; @@ -209,5 +214,10 @@ public void accept(CodeBuilder 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/classfile/impl/BufferedFieldBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.java index 5bfb35d3045a2..263cc2a477d3d 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedFieldBuilder.java @@ -116,5 +116,10 @@ public void writeTo(BufWriter buf) { 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/classfile/impl/BufferedMethodBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.java index afc638501b8ea..4077830eafb07 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedMethodBuilder.java @@ -196,5 +196,11 @@ public void writeTo(BufWriter buf) { 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/classfile/impl/ClassImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java index 151b0c625d30a..1d8c9ecaba26e 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassImpl.java @@ -214,6 +214,11 @@ && 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; diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.java index 52aab7b73bca4..00e1cb1b9b1e0 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassfileVersionImpl.java @@ -53,4 +53,9 @@ public int minorVersion() { 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/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java index b319f10995443..54933ff199f54 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java @@ -499,4 +499,9 @@ yield switch (bclow) { } }; } + + @Override + public String toString() { + return String.format("CodeModel[id=%d]", System.identityHashCode(this)); + } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java index 2ebb3d696f638..86af83d495bbf 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java @@ -691,6 +691,11 @@ 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 { diff --git a/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java b/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java index edaaf06903b6a..afa289e4d51e1 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/FieldImpl.java @@ -125,4 +125,10 @@ public void forEachElement(Consumer consumer) { 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/classfile/impl/InterfacesImpl.java b/src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.java index 16ad3d1389ebd..a9cb15b91c51c 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/InterfacesImpl.java @@ -25,6 +25,7 @@ package jdk.classfile.impl; import java.util.List; +import java.util.stream.Collectors; import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.Interfaces; @@ -50,4 +51,11 @@ public List interfaces() { 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/classfile/impl/LineNumberImpl.java b/src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java index aefd3e6039151..8872eb4c27ad0 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/LineNumberImpl.java @@ -76,5 +76,10 @@ public int sizeInBytes() { public void writeTo(DirectCodeBuilder writer) { writer.setLineNumber(line); } + + @Override + public String toString() { + return String.format("LineNumber[line=%d]", line); + } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/MethodImpl.java b/src/java.base/share/classes/jdk/classfile/impl/MethodImpl.java index fe048554a572c..c8cdde822729f 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/MethodImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/MethodImpl.java @@ -145,4 +145,10 @@ public void accept(MethodBuilder mb) { }); } } + + @Override + public String toString() { + return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]", + methodName().stringValue(), methodType().stringValue(), flags().flagsMask()); + } } \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java b/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java index 698cbb3301f76..319a2b472f49d 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/SuperclassImpl.java @@ -51,4 +51,9 @@ public ClassEntry superclassEntry() { 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/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java index df6f969b34f85..df6df587e8f45 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java @@ -140,6 +140,10 @@ 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 { From a98f860de0aac271b6901921b24a7f1885048c8e Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 7 Jul 2022 17:18:17 +0200 Subject: [PATCH 019/190] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54e554af99c78..3e2c330be5f03 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Classfile Processing API benchmarks are a part of JDK Microbenchmark Suite: Benchmarks can be selectively executed as: - make test TEST=micro:org.openjdk.bench.jdk.bytecode.+ + make test TEST=micro:org.openjdk.bench.jdk.classfile.+ See [JEP 230: Microbenchmark Suite](https://bugs.openjdk.java.net/browse/JDK-8050952) for more information about JDK benchmarks. From 4b69c9c698aa538c75780b340b91e2feabe07aae Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 8 Jul 2022 14:31:35 +0200 Subject: [PATCH 020/190] new JMH micro benchmark focused on clean StackMapGenerator throughput --- make/RunTests.gmk | 4 +- make/test/BuildMicrobenchmark.gmk | 2 + .../jdk/classfile/GenerateStackMaps.java | 119 ++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java diff --git a/make/RunTests.gmk b/make/RunTests.gmk index dc93a5d58861c..15866c440b146 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -598,8 +598,10 @@ define SetupRunMicroTestBody --add-exports java.base/jdk.classfile=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.attribute=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.constantpool=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.instruction=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.jdktypes=ALL-UNNAMED \ - --add-exports java.base/jdk.classfile.transforms=ALL-UNNAMED + --add-exports java.base/jdk.classfile.transforms=ALL-UNNAMED \ + --add-exports java.base/jdk.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/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index e03cb754ecb2c..1a3836bbb72f7 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -99,8 +99,10 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.classfile=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.attribute=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.constantpool=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.instruction=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.jdktypes=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.transforms=ALL-UNNAMED \ + --add-exports java.base/jdk.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 \ 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..adf10a3787d9b --- /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.classfile.Classfile; +import jdk.classfile.ClassReader; +import jdk.classfile.constantpool.ConstantPoolBuilder; +import jdk.classfile.impl.AbstractInstruction; +import jdk.classfile.impl.CodeImpl; +import jdk.classfile.impl.LabelContext; +import jdk.classfile.impl.SplitConstantPool; +import jdk.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 -> (AbstractInstruction.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(), + d.constantPool(), + d.handlers()); + } +} From 0631349a2ef77f32aac513d0521de85d1fc46cb9 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 11 Jul 2022 12:16:56 +0200 Subject: [PATCH 021/190] PerformancePatch - avoid ArrayList in StackMapGenerator::processInvokeInstructions --- .../jdk/classfile/impl/StackMapGenerator.java | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 25cb83b92920f..76d315271d070 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -829,27 +829,14 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl var cpe = cp.entryByIndex(index); var nameAndType = opcode == Classfile.INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); String invokeMethodName = nameAndType.name().stringValue(); - var methodDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); + var mDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); Type[] sig_type = new Type[2]; int nargs = 0; - ArrayList sigVerifTypes = new ArrayList<>(); - for (int i = 0; i < methodDesc.parameterCount(); i++) { - int n = classDescToType(methodDesc.parameterType(i), sig_type, 0); - for (int x = 0; x < n; x++) { - sigVerifTypes.add(sig_type[x]); - } - nargs += n; - } - if (!methodDesc.returnType().equals(CD_void)) { - int n = classDescToType(methodDesc.returnType(), sig_type, 0); - for (int y = 0; y < n; y++) { - sigVerifTypes.add(sig_type[y]); - } + for (int i = 0; i < mDesc.parameterCount(); i++) { + nargs += classDescToType(mDesc.parameterType(i), sig_type, 0); } int bci = bcs.bci; - for (int i = nargs - 1; i >= 0; i--) { - currentFrame.popStack(); - } + currentFrame.decStack(nargs); if (opcode != Classfile.INVOKESTATIC && opcode != Classfile.INVOKEDYNAMIC) { if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { Type type = currentFrame.popStack(); @@ -874,13 +861,13 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl currentFrame.popStack(); } } - int sig_verif_types_len = sigVerifTypes.size(); - if (sig_verif_types_len > nargs) { + if (!mDesc.returnType().equals(CD_void)) { if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { generatorError("Return type must be void in method"); } - for (int i = nargs; i < sig_verif_types_len; i++) { - currentFrame.pushStack(sigVerifTypes.get(i)); + int n = classDescToType(mDesc.returnType(), sig_type, 0); + for (int y = 0; y < n; y++) { + currentFrame.pushStack(sig_type[y]); } } return thisUninit; From 1950b1fc9ce4e11ac51f6080f61031041cf76dac Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 11 Jul 2022 19:21:21 +0200 Subject: [PATCH 022/190] StackMapGenerator code cleanup --- .../jdk/classfile/impl/StackMapGenerator.java | 563 +++++------------- 1 file changed, 159 insertions(+), 404 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 76d315271d070..f6e4492d3d2df 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -27,8 +27,6 @@ import java.lang.constant.ConstantDescs; import static java.lang.constant.ConstantDescs.*; import java.lang.constant.MethodTypeDesc; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import jdk.classfile.Classfile; import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.constantpool.ConstantDynamicEntry; @@ -40,11 +38,9 @@ import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.BiConsumer; import jdk.classfile.Attribute; import static jdk.classfile.Classfile.*; @@ -176,7 +172,6 @@ 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 BITS_PER_BYTE = 8; private static final int T_BOOLEAN = 4, T_LONG = 11; private static final int ITEM_TOP = 0, @@ -398,7 +393,6 @@ public Attribute stackMapTableAttribute() { return frames.isEmpty() ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.STACK_MAP_TABLE) { @Override public void writeBody(BufWriter b) { - int start = b.size(); b.writeU2(frames.size()); Frame prevFrame = new Frame(classHierarchy); prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); @@ -416,41 +410,8 @@ private static Type cpIndexToType(int index, ConstantPoolBuilder cp) { return Type.referenceType(((ClassEntry)cp.entryByIndex(index)).asSymbol()); } - private static int classDescToType(ClassDesc desc, Type inference_types[], int inference_type_index) { - return classDescToType(desc, new BiConsumer<>() { - @Override - public void accept(Integer i, Type vt) { - inference_types[i] = vt; - } - }, inference_type_index); - } - - private static int classDescToType(ClassDesc desc, BiConsumer inference_types_consumer, int inference_type_index) { - if (desc.isClassOrInterface() || desc.isArray()) { - inference_types_consumer.accept(inference_type_index, Type.referenceType(desc)); - return 1; - } - switch (desc.descriptorString()) { - case "J" -> { - inference_types_consumer.accept(inference_type_index, Type.LONG_TYPE); - inference_types_consumer.accept(++inference_type_index, Type.LONG2_TYPE); - return 2; - } - case "D" -> { - inference_types_consumer.accept(inference_type_index, Type.DOUBLE_TYPE); - inference_types_consumer.accept(++inference_type_index, Type.DOUBLE2_TYPE); - return 2; - } - case "I", "Z", "B", "C", "S" -> { - inference_types_consumer.accept(inference_type_index, Type.INTEGER_TYPE); - return 1; - } - case "F" -> { - inference_types_consumer.accept(inference_type_index, Type.FLOAT_TYPE); - return 1; - } - default -> throw new AssertionError("Should not reach here"); - } + private static boolean isDoubleSlot(ClassDesc desc) { + return CD_double.equals(desc) || CD_long.equals(desc); } private void processMethod() { @@ -513,11 +474,11 @@ private void processBlock(RawBytecodeHelper bcs) { 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.pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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.pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case Classfile.LDC -> processLdc(bcs.getIndexU1()); case Classfile.LDC_W, Classfile.LDC2_W -> @@ -527,17 +488,17 @@ private void processBlock(RawBytecodeHelper bcs) { 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).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + 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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + 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 -> @@ -545,13 +506,13 @@ private void processBlock(RawBytecodeHelper bcs) { case Classfile.IALOAD, Classfile.BALOAD, Classfile.CALOAD, Classfile.SALOAD -> currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); case Classfile.LALOAD -> - currentFrame.decStack(2).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case Classfile.AALOAD -> - currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()).isNull() ? Type.NULL_TYPE : type1.getComponent()); + 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 -> @@ -622,41 +583,41 @@ private void processBlock(RawBytecodeHelper bcs) { 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).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + currentFrame.decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case Classfile.LNEG -> - currentFrame.decStack(2).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case Classfile.LSHL, Classfile.LSHR, Classfile.LUSHR -> - currentFrame.decStack(3).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + currentFrame.decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case Classfile.DNEG -> - currentFrame.decStack(2).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case Classfile.IINC -> currentFrame.checkLocal(bcs.getIndex()); case Classfile.I2L -> - currentFrame.decStack(1).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + 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).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + 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).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case Classfile.F2D -> - currentFrame.decStack(1).pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case Classfile.D2L -> - currentFrame.decStack(2).pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + 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 -> @@ -733,20 +694,15 @@ private void processLdc(int index) { case TAG_FLOAT -> currentFrame.pushStack(Type.FLOAT_TYPE); case TAG_DOUBLE -> - currentFrame.pushStack2(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); case TAG_LONG -> - currentFrame.pushStack2(Type.LONG_TYPE, Type.LONG2_TYPE); + currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case TAG_METHODHANDLE -> currentFrame.pushStack(Type.referenceType(CD_MethodHandle)); case TAG_METHODTYPE -> currentFrame.pushStack(Type.referenceType(CD_MethodType)); - case TAG_CONSTANTDYNAMIC -> { - Type[] vConstantType = new Type[2]; - int n = classDescToType(((ConstantDynamicEntry)cp.entryByIndex(index)).asSymbol().constantType(), vConstantType, 0); - for (int i = 0; i < n; i++) { - currentFrame.pushStack(vConstantType[i]); - } - } + case TAG_CONSTANTDYNAMIC -> + currentFrame.pushStack(((ConstantDynamicEntry)cp.entryByIndex(index)).asSymbol().constantType()); default -> generatorError("Invalid index in ldc"); } @@ -793,31 +749,22 @@ private void processSwitch(RawBytecodeHelper bcs) { } private void processFieldInstructions(RawBytecodeHelper bcs) { - int index = bcs.getIndexU2(); - Type[] fieldType = new Type[2]; - int n = classDescToType(ClassDesc.ofDescriptor(((MemberRefEntry)cp.entryByIndex(index)).nameAndType().type().stringValue()), fieldType, 0); + var desc = ClassDesc.ofDescriptor(((MemberRefEntry)cp.entryByIndex(bcs.getIndexU2())).nameAndType().type().stringValue()); switch (bcs.rawCode) { - case Classfile.GETSTATIC -> { - for (int i = 0; i < n; i++) { - currentFrame.pushStack(fieldType[i]); - } - } + case Classfile.GETSTATIC -> + currentFrame.pushStack(desc); case Classfile.PUTSTATIC -> { - for (int i = n - 1; i >= 0; i--) { - currentFrame.popStack(); - } + currentFrame.popStack(); + if (isDoubleSlot(desc)) currentFrame.popStack(); } case Classfile.GETFIELD -> { currentFrame.popStack(); - for (int i = 0; i < n; i++) { - currentFrame.pushStack(fieldType[i]); - } + currentFrame.pushStack(desc); } case Classfile.PUTFIELD -> { - for (int i = n - 1; i >= 0; i--) { - currentFrame.popStack(); - } currentFrame.popStack(); + currentFrame.popStack(); + if (isDoubleSlot(desc)) currentFrame.popStack(); } default -> throw new AssertionError("Should not reach here"); } @@ -830,23 +777,21 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl var nameAndType = opcode == Classfile.INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); String invokeMethodName = nameAndType.name().stringValue(); var mDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); - Type[] sig_type = new Type[2]; int nargs = 0; - for (int i = 0; i < mDesc.parameterCount(); i++) { - nargs += classDescToType(mDesc.parameterType(i), sig_type, 0); - } + for (int i = 0; i < mDesc.parameterCount(); i++) + nargs += isDoubleSlot(mDesc.parameterType(i)) ? 2 : 1; 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.isUninitializedThis()) { + if (type == Type.UNITIALIZED_THIS_TYPE) { if (inTryBlock) { processExceptionHandlerTargets(bci, true); } currentFrame.initializeObject(type, thisType); thisUninit = true; - } else if (type.isUninitialized()) { + } 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); @@ -861,15 +806,7 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl currentFrame.popStack(); } } - if (!mDesc.returnType().equals(CD_void)) { - if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { - generatorError("Return type must be void in method"); - } - int n = classDescToType(mDesc.returnType(), sig_type, 0); - for (int y = 0; y < n; y++) { - currentFrame.pushStack(sig_type[y]); - } - } + currentFrame.pushStack(mDesc.returnType()); return thisUninit; } @@ -975,27 +912,21 @@ private static final class Frame { private Type[] locals, stack; Frame(ClassHierarchyImpl classHierarchy) { - this.offset = -1; - this.localsSize = 0; - this.stackSize = 0; - this.flags = 0; - this.classHierarchy = classHierarchy; - this.locals = null; - this.stack = null; + this(-1, 0, 0, 0, null, null, classHierarchy); } - Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl context) { + 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 = context; - } - - public Frame(int offset, ClassHierarchyImpl context) { - this(offset, -1, 0, 0, null, null, context); + this.classHierarchy = classHierarchy; } @Override @@ -1003,16 +934,34 @@ 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; } - void pushStack2(Type type1, Type type2) { + Frame pushStack(Type type1, Type type2) { checkStack(stackSize + 1); stack[stackSize++] = type1; stack[stackSize++] = type2; + return this; } Type popStack() { @@ -1042,7 +991,7 @@ void initializeObject(Type old_object, Type new_object) { stack[i] = new_object; } } - if (old_object.isUninitializedThis()) { + if (old_object == Type.UNITIALIZED_THIS_TYPE) { flags = 0; } } @@ -1077,56 +1026,48 @@ private void setLocalRawInternal(int index, Type type) { locals[index] = type; } - Type setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { + void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { localsSize = 0; if (!isStatic) { localsSize++; if (OBJECT_INITIALIZER_NAME.equals(name) && !ConstantDescs.CD_Object.equals(thisKlass.sym)) { - setLocal(0, Type.uninitialized_this_type); + setLocal(0, Type.UNITIALIZED_THIS_TYPE); flags |= FLAG_THIS_UNINIT; } else { setLocalRawInternal(0, thisKlass); } } for (int i = 0; i < methodDesc.parameterCount(); i++) { - localsSize += StackMapGenerator.classDescToType(methodDesc.parameterType(i), new BiConsumer<>() { - @Override - public void accept(Integer i, Type vt) { - setLocalRawInternal(i, vt); + 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); } - }, localsSize); - } - var ret = methodDesc.returnType(); - if (ret.isClassOrInterface() || ret.isArray()) { - return Type.referenceType(ret); + 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"); + } } - return switch (ret.descriptorString()) { - case "I" -> Type.INTEGER_TYPE; - case "B" -> Type.BYTE_TYPE; - case "C" -> Type.CHAR_TYPE; - case "S" -> Type.SHORT_TYPE; - case "Z" -> Type.BOOLEAN_TYPE; - case "F"-> Type.FLOAT_TYPE; - case "D" -> Type.DOUBLE_TYPE; - case "J" -> Type.LONG_TYPE; - case "V" -> Type.TOP_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); - for (int i = 0; i < src.localsSize; i++) { - locals[i] = src.locals[i]; - } + if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize); if (stack != null) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE); stackSize = src.stackSize; checkStack(src.stackSize - 1); - for (int i = 0; i < src.stackSize; i++) { - stack[i] = src.stack[i]; - } + if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize); flags = src.flags; } @@ -1165,17 +1106,12 @@ Type getLocal(int index) { return ret; } - void getLocal2(int index) { - getLocalRawInternal(index); - getLocalRawInternal(index + 1); - } - void setLocal(int index, Type type) { Type old = getLocalRawInternal(index); - if (old.isDouble() || old.isLong()) { + if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { setLocalRawInternal(index + 1, Type.TOP_TYPE); } - if (old.isDouble2() || old.isLong2()) { + if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { setLocalRawInternal(index - 1, Type.TOP_TYPE); } setLocalRawInternal(index, type); @@ -1186,11 +1122,11 @@ void setLocal(int index, Type type) { void setLocal2(int index, Type type1, Type type2) { Type old = getLocalRawInternal(index + 1); - if (old.isDouble() || old.isLong()) { + if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { setLocalRawInternal(index + 2, Type.TOP_TYPE); } old = getLocalRawInternal(index); - if (old.isDouble2() || old.isLong2()) { + if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { setLocalRawInternal(index - 1, Type.TOP_TYPE); } setLocalRawInternal(index, type1); @@ -1210,7 +1146,7 @@ private void merge(Type me, Type[] toTypes, int i, Frame target) { } private static int trimAndCompress(Type[] types, int count) { - while (count > 0 && types[count - 1].isTop()) 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()) { @@ -1265,94 +1201,37 @@ void writeTo(BufWriter out, Frame prevFrame, ConstantPoolBuilder cp) { } } - private static final class Type { + private static record Type(int tag, ClassDesc sym, int bci) { - private Type(ClassDesc sym) { - this(0x100, sym); + @Override //mandatory overrride to avoid use of method reference during JDK bootstrap + public boolean equals(Object o) { + return (o instanceof Type t) && t.tag == tag && t.bci == bci && Objects.equals(t.sym, sym); } - private final int data; - final ClassDesc sym; - @Override - public int hashCode() { - return sym == null ? data : sym.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Type ? (data == ((Type) obj).data) && Objects.equals(sym, ((Type) obj).sym) : false; + private Type(int tag) { + this(tag, null, 0); } - private static final Map CONSTANTS_MAP = new IdentityHashMap<>(18); - - @Override - public String toString() { - if (CONSTANTS_MAP.isEmpty()) { - for (Field f : Type.class.getDeclaredFields()) { - var name = f.getName(); - if (Modifier.isStatic(f.getModifiers()) && f.getType() == Type.class && name.endsWith("_TYPE")) try { - CONSTANTS_MAP.put((Type) f.get(null), name.substring(0, name.length() - 5)); - } catch (IllegalAccessException ignore) { - } - } - } - if (sym != null) { - return sym.displayName(); - } - if ((data & 0xff) == UNINITIALIZED) { - return "uninit@" + (data >> 8); - } - return CONSTANTS_MAP.getOrDefault(this, Integer.toHexString(data)); - } - - private static final int TYPE_MASK = 0x00000003, - REFERENCE = 0x0, - PRIMITIVE = 0x1, - UNINITIALIZED = 0x2, - TYPE_QUERY = 0x3, - CATEGORY1_FLAG = 0x01, - CATEGORY2_FLAG = 0x02, - CATEGORY2_2ND_FLAG = 0x04, - NULL = 0x00000000, - CATEGORY1 = (CATEGORY1_FLAG << 1 * BITS_PER_BYTE) | PRIMITIVE, - CATEGORY2 = (CATEGORY2_FLAG << 1 * BITS_PER_BYTE) | PRIMITIVE, - CATEGORY2_2ND = (CATEGORY2_2ND_FLAG << 1 * BITS_PER_BYTE) | PRIMITIVE, - TOP = PRIMITIVE, - BOOLEAN = (ITEM_BOOLEAN << 2 * BITS_PER_BYTE) | CATEGORY1, - BYTE = (ITEM_BYTE << 2 * BITS_PER_BYTE) | CATEGORY1, - SHORT = (ITEM_SHORT << 2 * BITS_PER_BYTE) | CATEGORY1, - CHAR = (ITEM_CHAR << 2 * BITS_PER_BYTE) | CATEGORY1, - INTEGER = (ITEM_INTEGER << 2 * BITS_PER_BYTE) | CATEGORY1, - FLOAT = (ITEM_FLOAT << 2 * BITS_PER_BYTE) | CATEGORY1, - LONG = (ITEM_LONG << 2 * BITS_PER_BYTE) | CATEGORY2, - DOUBLE = (ITEM_DOUBLE << 2 * BITS_PER_BYTE) | CATEGORY2, - LONG_2ND = (ITEM_LONG_2ND << 2 * BITS_PER_BYTE) | CATEGORY2_2ND, - DOUBLE_2ND = (ITEM_DOUBLE_2ND << 2 * BITS_PER_BYTE) | CATEGORY2_2ND, - BCI_MASK = 0xffff << 1 * BITS_PER_BYTE, - BCI_FOR_THIS = 0xffff; - - private Type(int raw_data) { - this(raw_data, null); - } - - private Type(int raw_data, ClassDesc sym) { - this.data = raw_data; - this.sym = sym; - } - - static final Type TOP_TYPE = new Type(TOP), - NULL_TYPE = new Type(NULL), - INTEGER_TYPE = new Type(INTEGER), - FLOAT_TYPE = new Type(FLOAT), - LONG_TYPE = new Type(LONG), - LONG2_TYPE = new Type(LONG_2ND), - DOUBLE_TYPE = new Type(DOUBLE), - BOOLEAN_TYPE = new Type(BOOLEAN), - BYTE_TYPE = new Type(BYTE), - CHAR_TYPE = new Type(CHAR), - SHORT_TYPE = new Type(SHORT), - DOUBLE2_TYPE = new Type(DOUBLE_2ND); - + private Type(ClassDesc sym) { + this(ITEM_OBJECT, sym, 0); + } + + //singleton types + static final Type TOP_TYPE = new Type(ITEM_TOP), + NULL_TYPE = new Type(ITEM_NULL), + INTEGER_TYPE = new Type(ITEM_INTEGER), + FLOAT_TYPE = new Type(ITEM_FLOAT), + LONG_TYPE = new Type(ITEM_LONG), + LONG2_TYPE = new Type(ITEM_LONG_2ND), + DOUBLE_TYPE = new Type(ITEM_DOUBLE), + BOOLEAN_TYPE = new Type(ITEM_BOOLEAN), + BYTE_TYPE = new Type(ITEM_BYTE), + CHAR_TYPE = new Type(ITEM_CHAR), + SHORT_TYPE = new Type(ITEM_SHORT), + DOUBLE2_TYPE = new Type(ITEM_DOUBLE_2ND), + UNITIALIZED_THIS_TYPE = new Type(ITEM_UNINITIALIZED_THIS); + + //frequently used types to reduce footprint static final Type OBJECT_TYPE = new Type(CD_Object), THROWABLE_TYPE = new Type(CD_Throwable), INT_ARRAY_TYPE = new Type(CD_int.arrayType()), @@ -1369,158 +1248,58 @@ static Type referenceType(ClassDesc sh) { } static Type uninitializedType(int bci) { - return new Type(bci << 1 * BITS_PER_BYTE | UNINITIALIZED); - } - static final Type uninitialized_this_type = uninitializedType(BCI_FOR_THIS); - - boolean isTop() { - return (data == TOP); - } - - boolean isNull() { - return (data == NULL); - } - - boolean isNullOrArray() { - return isNull() || isArray();// || (sub1 != null && sub1.isNullOrArray() && sub2.isNullOrArray()); - } - - boolean isBoolean() { - return (data == BOOLEAN); - } - - boolean isByte() { - return (data == BYTE); - } - - boolean isChar() { - return (data == CHAR); - } - - boolean isShort() { - return (data == SHORT); - } - - boolean isInteger() { - return (data == INTEGER); - } - - boolean isLong() { - return (data == LONG); - } - - boolean isFloat() { - return (data == FLOAT); - } - - boolean isDouble() { - return (data == DOUBLE); - } - - boolean isLong2() { - return (data == LONG_2ND); - } - - boolean isDouble2() { - return (data == DOUBLE_2ND); - } - - boolean isReference() { - return ((data & TYPE_MASK) == REFERENCE); - } - - boolean isCategory1() { - return ((data & CATEGORY1) != PRIMITIVE); - } - - boolean isCategory2() { - return ((data & CATEGORY2) == CATEGORY2); + return new Type(ITEM_UNINITIALIZED, null, bci); } boolean isCategory2_2nd() { - return ((data & CATEGORY2_2ND) == CATEGORY2_2ND); + return this == DOUBLE2_TYPE || this == LONG2_TYPE; } - boolean isCheck() { - return (data & TYPE_QUERY) == TYPE_QUERY; + boolean isReference() { + return tag == ITEM_OBJECT || this == NULL_TYPE; } boolean isObject() { - return (isReference() && !isNull() && sym.isClassOrInterface()); + return tag == ITEM_OBJECT && sym.isClassOrInterface(); } boolean isArray() { - return (isReference() && !isNull() && sym.isArray()); - } - - boolean isUninitialized() { - return ((data & UNINITIALIZED) == UNINITIALIZED); - } - - boolean isUninitializedThis() { - return isUninitialized() && bci() == BCI_FOR_THIS; - } - - int bci() { - return ((data & BCI_MASK) >> 1 * BITS_PER_BYTE); + return tag == ITEM_OBJECT && sym.isArray(); } Type mergeFrom(Type from, ClassHierarchyImpl context) { - if (isTop() || this == from || equals(from)) { + if (this == TOP_TYPE || this == from || equals(from)) { return this; } else { - switch (data) { - case BOOLEAN: - case BYTE: - case CHAR: - case SHORT: - return from.isInteger() ? this : Type.TOP_TYPE; - default: - if (isReference() && from.isReference()) { - return mergeReferenceFrom(from, context); - } else { - return Type.TOP_TYPE; - } - } + return switch (tag) { + case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT -> + from == INTEGER_TYPE ? this : TOP_TYPE; + default -> + isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; + }; } } Type mergeComponentFrom(Type from, ClassHierarchyImpl context) { - if (isTop() || this == from || equals(from)) { + if (this == TOP_TYPE || this == from || equals(from)) { return this; } else { - switch (data) { - case BOOLEAN: - case BYTE: - case CHAR: - case SHORT: - return Type.TOP_TYPE; - default: - if (isReference() && from.isReference()) { - return mergeReferenceFrom(from, context); - } else { - return Type.TOP_TYPE; - } - } - } - } - - int dimensions() { - int index = 0; - String desc = sym.descriptorString(); - while (desc.charAt(index) == '[') { - index++; + return switch (tag) { + case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT -> + TOP_TYPE; + default -> + isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; + }; } - return index; } 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.isNull()) { + if (from == NULL_TYPE) { return this; - } else if (isNull()) { + } else if (this == NULL_TYPE) { return from; } else if (sym.equals(from.sym)) { return this; @@ -1536,11 +1315,10 @@ private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { var anc = context.commonAncestor(sym, from.sym); return anc == null ? this : Type.referenceType(anc); } - return Type.OBJECT_TYPE; } else if (isArray() && from.isArray()) { Type compThis = getComponent(); Type compFrom = from.getComponent(); - if (!compThis.isTop() && !compFrom.isTop()) { + if (compThis != TOP_TYPE && compFrom != TOP_TYPE) { return compThis.mergeComponentFrom(compFrom, context).toArray(); } } @@ -1548,27 +1326,18 @@ private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { } Type toArray() { - if (isBoolean()) { - return BOOLEAN_ARRAY_TYPE; - } else if (isByte()) { - return BYTE_ARRAY_TYPE; - } else if (isChar()) { - return CHAR_ARRAY_TYPE; - } else if (isShort()) { - return SHORT_ARRAY_TYPE; - } else if (isInteger()) { - return INT_ARRAY_TYPE; - } else if (isLong()) { - return LONG_ARRAY_TYPE; - } else if (isFloat()) { - return FLOAT_ARRAY_TYPE; - } else if (isDouble()) { - return DOUBLE_ARRAY_TYPE; - } else if (isArray() || isObject()) { - return Type.referenceType(sym.arrayType()); - } else { - return OBJECT_TYPE; - } + 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() { @@ -1593,26 +1362,12 @@ Type getComponent() { } void writeTo(BufWriter bw, ConstantPoolBuilder cp) { - if (isTop()) { - bw.writeU1(ITEM_TOP); - } else if (isInteger()) { - bw.writeU1(ITEM_INTEGER); - } else if (isFloat()) { - bw.writeU1(ITEM_FLOAT); - } else if (isLong()) { - bw.writeU1(ITEM_LONG); - } else if (isDouble()) { - bw.writeU1(ITEM_DOUBLE); - } else if (isNull()) { - bw.writeU1(ITEM_NULL); - } else if (isUninitializedThis()) { - bw.writeU1(ITEM_UNINITIALIZED_THIS); - } else if (isReference()) { - bw.writeU1(ITEM_OBJECT); - bw.writeU2(cp.classEntry(cp.utf8Entry(Util.toInternalName(sym))).index()); - } else if (isUninitialized()) { - bw.writeU1(ITEM_UNINITIALIZED); - bw.writeU2(bci()); + bw.writeU1(tag); + switch (tag) { + case ITEM_OBJECT -> + bw.writeU2(cp.classEntry(cp.utf8Entry(Util.toInternalName(sym))).index()); + case ITEM_UNINITIALIZED -> + bw.writeU2(bci()); } } } From e14f3a5d321a4f4a8b74eaad5d54b09a876e9657 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 12 Jul 2022 23:25:56 +0200 Subject: [PATCH 023/190] PerformancePatch - StackMapGenerator faster counting of method descriptor argument slots --- .../jdk/classfile/impl/StackMapGenerator.java | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index f6e4492d3d2df..8fd009cf06c05 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -190,9 +190,9 @@ public final class StackMapGenerator { ITEM_LONG_2ND = 13, ITEM_DOUBLE_2ND = 14; - private static final ClassDesc[] ARRAY_FROM_BASIC_TYPE = new ClassDesc[]{null, null, null, null, - CD_boolean.arrayType(), CD_char.arrayType(), CD_float.arrayType(), CD_double.arrayType(), - CD_byte.arrayType(), CD_short.arrayType(), CD_int.arrayType(), CD_long.arrayType()}; + 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; @@ -776,10 +776,34 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl var cpe = cp.entryByIndex(index); var nameAndType = opcode == Classfile.INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); String invokeMethodName = nameAndType.name().stringValue(); - var mDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); - int nargs = 0; - for (int i = 0; i < mDesc.parameterCount(); i++) - nargs += isDoubleSlot(mDesc.parameterType(i)) ? 2 : 1; + + 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) { @@ -806,13 +830,13 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl currentFrame.popStack(); } } - currentFrame.pushStack(mDesc.returnType()); + 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"); - return Type.referenceType(ARRAY_FROM_BASIC_TYPE[index]); + return ARRAY_FROM_BASIC_TYPE[index]; } private void processAnewarray(int index) { From 5cbe44286997cc17b9c39131d5bc722a219a690b Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 12 Jul 2022 23:25:56 +0200 Subject: [PATCH 024/190] PerformancePatch - StackMapGenerator more effective loops and other fine-tunning --- .../constantpool/ConstantPoolBuilder.java | 1 - .../jdk/classfile/impl/StackMapGenerator.java | 211 +++++++++--------- 2 files changed, 111 insertions(+), 101 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java index dab950cac8f28..1363f1e8c8373 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java @@ -39,7 +39,6 @@ import jdk.classfile.ClassModel; import jdk.classfile.ClassReader; import jdk.classfile.Classfile; -import jdk.classfile.impl.ClassImpl; import jdk.classfile.impl.ClassReaderImpl; import jdk.classfile.impl.Options; import jdk.classfile.jdktypes.ModuleDesc; diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 8fd009cf06c05..4e8541a0b0322 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -24,7 +24,6 @@ package jdk.classfile.impl; import java.lang.constant.ClassDesc; -import java.lang.constant.ConstantDescs; import static java.lang.constant.ConstantDescs.*; import java.lang.constant.MethodTypeDesc; import jdk.classfile.Classfile; @@ -450,14 +449,14 @@ private void processMethod() { } else if (ncf) { generatorError("Expecting a stack map frame"); } - processBlock(bcs); - ncf = noControlFlow(bcs.rawCode); + ncf = processBlock(bcs); } if (trace) System.out.println(" " + currentFrame); } - private void processBlock(RawBytecodeHelper 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; @@ -468,7 +467,10 @@ private void processBlock(RawBytecodeHelper bcs) { verified_exc_handlers = true; } switch (opcode) { - case Classfile.NOP, Classfile.RETURN -> {} + 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 -> @@ -630,16 +632,26 @@ private void processBlock(RawBytecodeHelper bcs) { 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 -> + case Classfile.GOTO -> { checkJumpTarget(currentFrame, bcs.dest()); - case Classfile.GOTO_W -> + ncf = true; + } + case Classfile.GOTO_W -> { checkJumpTarget(currentFrame, bcs.destW()); - case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> + ncf = true; + } + case Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH -> { processSwitch(bcs); - case Classfile.LRETURN, Classfile.DRETURN -> + ncf = true; + } + case Classfile.LRETURN, Classfile.DRETURN -> { currentFrame.decStack(2); - case Classfile.IRETURN, Classfile.FRETURN, Classfile.ARETURN, Classfile.ATHROW -> + 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 -> @@ -666,6 +678,7 @@ private void processBlock(RawBytecodeHelper bcs) { if (!verified_exc_handlers && bci >= exMin && bci < exMax) { processExceptionHandlerTargets(bci, this_uninit); } + return ncf; } private void processExceptionHandlerTargets(int bci, boolean this_uninit) { @@ -686,9 +699,9 @@ private void processLdc(int index) { case TAG_UTF8 -> currentFrame.pushStack(Type.OBJECT_TYPE); case TAG_STRING -> - currentFrame.pushStack(Type.referenceType(CD_String)); + currentFrame.pushStack(Type.STRING_TYPE); case TAG_CLASS -> - currentFrame.pushStack(Type.referenceType(CD_Class)); + currentFrame.pushStack(Type.CLASS_TYPE); case TAG_INTEGER -> currentFrame.pushStack(Type.INTEGER_TYPE); case TAG_FLOAT -> @@ -698,9 +711,9 @@ private void processLdc(int index) { case TAG_LONG -> currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); case TAG_METHODHANDLE -> - currentFrame.pushStack(Type.referenceType(CD_MethodHandle)); + currentFrame.pushStack(Type.METHOD_HANDLE_TYPE); case TAG_METHODTYPE -> - currentFrame.pushStack(Type.referenceType(CD_MethodType)); + currentFrame.pushStack(Type.METHOD_TYPE); case TAG_CONSTANTDYNAMIC -> currentFrame.pushStack(((ConstantDynamicEntry)cp.entryByIndex(index)).asSymbol().constantType()); default -> @@ -816,7 +829,7 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl currentFrame.initializeObject(type, thisType); thisUninit = true; } else if (type.tag == ITEM_UNINITIALIZED) { - int new_offset = type.bci(); + 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) { @@ -841,7 +854,7 @@ private Type getNewarrayType(int index) { private void processAnewarray(int index) { currentFrame.popStack(); - currentFrame.pushStack(Type.referenceType(cpIndexToType(index, cp).sym.arrayType())); + currentFrame.pushStack(cpIndexToType(index, cp).toArray()); } /** @@ -852,22 +865,6 @@ private void generatorError(String msg) { throw new VerifyError(String.format("%s at %s", msg, methodName)); } - /** - * Simple check if the given opcode falls into "no control flow" instructions group - * (GOTO, GOTO_W, TABLESWITCH, - * LOOKUPSWITCH, ATHROW, all xRETURN instructions), - * for which current stack map frame is not propagated to the next instruction - * and a new frame must be provided for the following instruction (if any) - * @param opcode bytecode instruction opcode - * @return boolean true if the opcode is in the "no control flow" group - */ - private static boolean noControlFlow(int opcode) { - return switch(opcode) { - case Classfile.GOTO, Classfile.GOTO_W, Classfile.TABLESWITCH, Classfile.LOOKUPSWITCH, Classfile.IRETURN, Classfile.LRETURN, Classfile.FRETURN, Classfile.DRETURN, Classfile.ARETURN, Classfile.RETURN, Classfile.ATHROW -> true; - default -> false; - }; - } - /** * Performs detection of mandatory stack map frames offsets * in a single bytecode traversing pass @@ -891,30 +888,44 @@ public void set(int i) { if (no_control_flow) { offsets.set(bci); } - no_control_flow = noControlFlow(opcode); - switch (opcode) { - 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, Classfile.GOTO -> - offsets.set(bcs.dest()); - case Classfile.GOTO_W -> - offsets.set(bcs.destW()); + 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)); - } - } + 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; }; } for (var exhandler : exceptionTable) { @@ -1000,7 +1011,7 @@ Frame decStack(int size) { } Frame frameInExceptionHandler(int flags) { - return new Frame(offset, flags, localsSize, 0, locals, new Type[] {Type.TOP_TYPE}, classHierarchy); + return new Frame(offset, flags, localsSize, 0, locals, new Type[] {Type.TOP_TYPE}, classHierarchy); } void initializeObject(Type old_object, Type new_object) { @@ -1054,7 +1065,7 @@ void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, localsSize = 0; if (!isStatic) { localsSize++; - if (OBJECT_INITIALIZER_NAME.equals(name) && !ConstantDescs.CD_Object.equals(thisKlass.sym)) { + if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) { setLocal(0, Type.UNITIALIZED_THIS_TYPE); flags |= FLAG_THIS_UNINIT; } else { @@ -1088,7 +1099,7 @@ void copyFrom(Frame src) { localsSize = src.localsSize; checkLocal(src.localsSize - 1); if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize); - if (stack != null) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE); + 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); @@ -1227,54 +1238,54 @@ void writeTo(BufWriter out, Frame prevFrame, ConstantPoolBuilder cp) { private static record Type(int tag, ClassDesc sym, int bci) { - @Override //mandatory overrride to avoid use of method reference during JDK bootstrap - public boolean equals(Object o) { - return (o instanceof Type t) && t.tag == tag && t.bci == bci && Objects.equals(t.sym, sym); - } - - private Type(int tag) { - this(tag, null, 0); - } - - private Type(ClassDesc sym) { - this(ITEM_OBJECT, sym, 0); - } - //singleton types - static final Type TOP_TYPE = new Type(ITEM_TOP), - NULL_TYPE = new Type(ITEM_NULL), - INTEGER_TYPE = new Type(ITEM_INTEGER), - FLOAT_TYPE = new Type(ITEM_FLOAT), - LONG_TYPE = new Type(ITEM_LONG), - LONG2_TYPE = new Type(ITEM_LONG_2ND), - DOUBLE_TYPE = new Type(ITEM_DOUBLE), - BOOLEAN_TYPE = new Type(ITEM_BOOLEAN), - BYTE_TYPE = new Type(ITEM_BYTE), - CHAR_TYPE = new Type(ITEM_CHAR), - SHORT_TYPE = new Type(ITEM_SHORT), - DOUBLE2_TYPE = new Type(ITEM_DOUBLE_2ND), - UNITIALIZED_THIS_TYPE = new Type(ITEM_UNINITIALIZED_THIS); + static final Type TOP_TYPE = simpleType(ITEM_TOP), + NULL_TYPE = simpleType(ITEM_NULL), + INTEGER_TYPE = simpleType(ITEM_INTEGER), + FLOAT_TYPE = simpleType(ITEM_FLOAT), + LONG_TYPE = simpleType(ITEM_LONG), + LONG2_TYPE = simpleType(ITEM_LONG_2ND), + DOUBLE_TYPE = simpleType(ITEM_DOUBLE), + BOOLEAN_TYPE = simpleType(ITEM_BOOLEAN), + BYTE_TYPE = simpleType(ITEM_BYTE), + CHAR_TYPE = simpleType(ITEM_CHAR), + SHORT_TYPE = simpleType(ITEM_SHORT), + DOUBLE2_TYPE = simpleType(ITEM_DOUBLE_2ND), + UNITIALIZED_THIS_TYPE = simpleType(ITEM_UNINITIALIZED_THIS); //frequently used types to reduce footprint - static final Type OBJECT_TYPE = new Type(CD_Object), - THROWABLE_TYPE = new Type(CD_Throwable), - INT_ARRAY_TYPE = new Type(CD_int.arrayType()), - BOOLEAN_ARRAY_TYPE = new Type(CD_boolean.arrayType()), - BYTE_ARRAY_TYPE = new Type(CD_byte.arrayType()), - CHAR_ARRAY_TYPE = new Type(CD_char.arrayType()), - SHORT_ARRAY_TYPE = new Type(CD_short.arrayType()), - LONG_ARRAY_TYPE = new Type(CD_long.arrayType()), - DOUBLE_ARRAY_TYPE = new Type(CD_double.arrayType()), - FLOAT_ARRAY_TYPE = new Type(CD_float.arrayType()); - - static Type referenceType(ClassDesc sh) { - return new Type(sh); + static final Type OBJECT_TYPE = referenceType(CD_Object), + THROWABLE_TYPE = referenceType(CD_Throwable), + INT_ARRAY_TYPE = referenceType(CD_int.arrayType()), + BOOLEAN_ARRAY_TYPE = referenceType(CD_boolean.arrayType()), + BYTE_ARRAY_TYPE = referenceType(CD_byte.arrayType()), + CHAR_ARRAY_TYPE = referenceType(CD_char.arrayType()), + SHORT_ARRAY_TYPE = referenceType(CD_short.arrayType()), + LONG_ARRAY_TYPE = referenceType(CD_long.arrayType()), + DOUBLE_ARRAY_TYPE = referenceType(CD_double.arrayType()), + FLOAT_ARRAY_TYPE = referenceType(CD_float.arrayType()), + STRING_TYPE = referenceType(CD_String), + CLASS_TYPE = referenceType(CD_Class), + METHOD_HANDLE_TYPE = referenceType(CD_MethodHandle), + METHOD_TYPE = referenceType(CD_MethodType); + + private static Type simpleType(int tag) { + return new Type(tag, null, 0); + } + + static Type referenceType(ClassDesc desc) { + return new Type(ITEM_OBJECT, desc, 0); } static Type uninitializedType(int bci) { return new Type(ITEM_UNINITIALIZED, null, bci); } + @Override //mandatory overrride to avoid use of method reference during JDK bootstrap + public boolean equals(Object o) { + return (o instanceof Type t) && t.tag == tag && t.bci == bci && Objects.equals(sym, t.sym); + } + boolean isCategory2_2nd() { return this == DOUBLE2_TYPE || this == LONG2_TYPE; } @@ -1328,7 +1339,7 @@ private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { } else if (sym.equals(from.sym)) { return this; } else if (isObject()) { - if (ConstantDescs.CD_Object.equals(sym)) { + if (CD_Object.equals(sym)) { return this; } if (context.isInterface(sym)) { @@ -1389,9 +1400,9 @@ void writeTo(BufWriter bw, ConstantPoolBuilder cp) { bw.writeU1(tag); switch (tag) { case ITEM_OBJECT -> - bw.writeU2(cp.classEntry(cp.utf8Entry(Util.toInternalName(sym))).index()); + bw.writeU2(cp.classEntry(sym).index()); case ITEM_UNINITIALIZED -> - bw.writeU2(bci()); + bw.writeU2(bci); } } } From a1502e3ef09b93cdcb502e1da0f009767d2090f5 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 15 Jul 2022 11:11:02 +0200 Subject: [PATCH 025/190] CodeBuilder and ClassPrinterImpl minor fixes --- src/java.base/share/classes/jdk/classfile/CodeBuilder.java | 2 +- .../share/classes/jdk/classfile/impl/ClassPrinterImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index 3cd1e287022e1..154050ae8b488 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -906,7 +906,7 @@ default CodeBuilder if_icmplt(Label target) { } default CodeBuilder if_icmpne(Label target) { - return branchInstruction(Opcode.IF_ACMPNE, target); + return branchInstruction(Opcode.IF_ICMPNE, target); } default CodeBuilder if_nonnull(Label target) { diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java index fe1d926d45bcf..f90499fe9a3c7 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java @@ -686,7 +686,7 @@ public void printMethod(MethodModel m) { case StoreInstruction lv -> out.accept(format.localVariableInstruction.formatted(bci, ins.opcode().name(), lv.slot(), findLocal(locals, lv.slot(), bci))); case FieldInstruction fa -> - out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), fa.owner(), fa.name().stringValue(), fa.type())); + out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), fa.owner().asInternalName(), escape(fa.name().stringValue()), fa.type().stringValue())); case InvokeInstruction inv -> out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), inv.owner().asInternalName(), escape(inv.name().stringValue()), inv.type().stringValue())); case InvokeDynamicInstruction invd -> { From 4b5620e5ad480fc77f5e789ec887ac5eff6eee9c Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 15 Jul 2022 12:49:03 +0200 Subject: [PATCH 026/190] update of AdvancedTransformationsTest::instrument --- .../classfile/AdvancedTransformationsTest.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index 181fb75a00f8e..eed5f88582cea 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -200,8 +200,7 @@ private static byte[] instrument(ClassModel target, ClassModel instrumentor, Pre target.forEachElement(cle -> { CodeModel instrumentorCodeModel; if (cle instanceof MethodModel mm && ((instrumentorCodeModel = instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue())) != null)) { - clb.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), - mb -> mm.forEachElement(me -> { + clb.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), mb -> mm.forEachElement(me -> { if (me instanceof CodeModel targetCodeModel) { //instrumented methods are merged var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); @@ -221,9 +220,9 @@ record Arg(TypeKind tk, int slot) {} if (!mm.flags().has(AccessFlag.STATIC)) { storeStack.add(new Arg(TypeKind.ReferenceType, slot++)); } - var it = Util.parameterTypes(mm.methodType().stringValue()); - while (it.hasNext()) { - var tk = TypeKind.fromDescriptor(it.next()); + var mType = mm.methodTypeSymbol(); + for (int i = 0; i < mType.parameterCount(); i++) { + var tk = TypeKind.fromDescriptor(mType.parameterType(i).descriptorString()); storeStack.add(new Arg(tk, slot)); slot += tk.slotSize(); } @@ -250,7 +249,7 @@ record Arg(TypeKind tk, int slot) {} codeBuilder.with(instrumentorCodeElement); }; mb.transformCode(instrumentorCodeModel, - invokeInterceptor.andThen(instrumentorCodeRemapperAndShifter)); + invokeInterceptor.andThen(instrumentorCodeRemapperAndShifter)); } else { mb.with(me); @@ -265,10 +264,10 @@ record Arg(TypeKind tk, int slot) {} instrumentor.forEachElement(cle -> { //remaining instrumentor fields and methods are remapped and moved if (cle instanceof FieldModel fm && !targetFieldNames.contains(fm.fieldName().stringValue())) { - remapperConsumer.accept(cle); + remapperConsumer.accept(cle); } else if (cle instanceof MethodModel mm && !"".equals(mm.methodName().stringValue()) && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue())) { - remapperConsumer.accept(cle); + remapperConsumer.accept(cle); } }); }); From 0c80bffc75b7602ed5835f4a7a387b63ad035ab2 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 18 Jul 2022 16:48:32 +0200 Subject: [PATCH 027/190] CodeBuilder fixes and increased test coverage --- .../classes/jdk/classfile/CodeBuilder.java | 56 +-- .../helpers/RebuildingTransformation.java | 414 ++++++++++++++++-- 2 files changed, 395 insertions(+), 75 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index 154050ae8b488..bd76ff8d29bae 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -41,6 +41,7 @@ import jdk.classfile.constantpool.InvokeDynamicEntry; import jdk.classfile.constantpool.LoadableConstantEntry; import jdk.classfile.constantpool.MemberRefEntry; +import jdk.classfile.constantpool.MethodRefEntry; import jdk.classfile.constantpool.MethodHandleEntry; import jdk.classfile.constantpool.NameAndTypeEntry; import jdk.classfile.constantpool.Utf8Entry; @@ -265,15 +266,9 @@ default CodeBuilder invokeInstruction(Opcode opcode, MemberRefEntry ref) { } default CodeBuilder invokeInstruction(Opcode opcode, ClassDesc owner, String name, MethodTypeDesc desc, boolean isInterface) { - if (opcode == Opcode.INVOKEINTERFACE) { - with(InvokeInstruction.of(opcode, constantPool().interfaceMethodRefEntry(owner, name, desc))); - } else { - MemberRefEntry ref = isInterface - ? constantPool().interfaceMethodRefEntry(owner, name, desc) - : constantPool().methodRefEntry(owner, name, desc); - with(InvokeInstruction.of(opcode, ref)); - } - return this; + return invokeInstruction(opcode, + isInterface ? constantPool().interfaceMethodRefEntry(owner, name, desc) + : constantPool().methodRefEntry(owner, name, desc)); } default CodeBuilder invokeDynamicInstruction(InvokeDynamicEntry ref) { @@ -468,6 +463,10 @@ default CodeBuilder nopInstruction() { } + default CodeBuilder nop() { + return nopInstruction(); + } + // Base pseudo-instruction builder methods default Label newBoundLabel() { @@ -979,56 +978,47 @@ default CodeBuilder invokeinterface(InterfaceMethodRefEntry ref) { } default CodeBuilder invokeinterface(ClassDesc owner, String name, MethodTypeDesc type) { - with(InvokeInstruction.of(Opcode.INVOKEINTERFACE, constantPool().interfaceMethodRefEntry(owner, name, type))); - return this; + 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 invokespecial(owner, name, type, false); + return invokeInstruction(Opcode.INVOKESPECIAL, owner, name, type, false); } default CodeBuilder invokespecial(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { - MemberRefEntry ref = isInterface - ? constantPool().interfaceMethodRefEntry(owner, name, type) - : constantPool().methodRefEntry(owner, name, type); - with(InvokeInstruction.of(Opcode.INVOKESPECIAL, ref)); - return this; + 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 invokestatic(owner, name, type, false); + return invokeInstruction(Opcode.INVOKESTATIC, owner, name, type, false); } default CodeBuilder invokestatic(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { - MemberRefEntry ref = isInterface - ? constantPool().interfaceMethodRefEntry(owner, name, type) - : constantPool().methodRefEntry(owner, name, type); - with(InvokeInstruction.of(Opcode.INVOKESTATIC, ref)); - return this; + return invokeInstruction(Opcode.INVOKESTATIC, owner, name, type, isInterface); } - default CodeBuilder invokevirtual(InterfaceMethodRefEntry ref) { + default CodeBuilder invokevirtual(MethodRefEntry ref) { return invokeInstruction(Opcode.INVOKEVIRTUAL, ref); } default CodeBuilder invokevirtual(ClassDesc owner, String name, MethodTypeDesc type) { - return invokevirtual(owner, name, type, false); - } - - default CodeBuilder invokevirtual(ClassDesc owner, String name, MethodTypeDesc type, boolean isInterface) { - MemberRefEntry ref = isInterface - ? constantPool().interfaceMethodRefEntry(owner, name, type) - : constantPool().methodRefEntry(owner, name, type); - with(InvokeInstruction.of(Opcode.INVOKEVIRTUAL, ref)); - return this; + return invokeInstruction(Opcode.INVOKEVIRTUAL, owner, name, type, false); } default CodeBuilder ior() { diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 6c6a852e5ddbc..744cfa8ee1942 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -27,6 +27,7 @@ import java.lang.constant.ClassDesc; import java.util.HashMap; import java.util.List; +import java.util.Random; import jdk.classfile.*; import jdk.classfile.attribute.*; import jdk.classfile.constantpool.*; @@ -36,6 +37,8 @@ class RebuildingTransformation { + static private Random pathSwitch = new Random(1234); + static byte[] transform(ClassModel clm) { return Classfile.build(clm.thisClass().asSymbol(), clb -> { for (var cle : clm) { @@ -71,48 +74,375 @@ static byte[] transform(ClassModel clm) { var labels = new HashMap(); for (var coe : com) { switch (coe) { - case ArrayLoadInstruction i -> cob.arrayLoadInstruction(i.typeKind()); - case ArrayStoreInstruction i -> cob.arrayStoreInstruction(i.typeKind()); - case BranchInstruction i -> cob.branchInstruction(i.opcode(), labels.computeIfAbsent(i.target(), l -> cob.newLabel())); - case ConstantInstruction i -> cob.constantInstruction(i.constantValue()); - case ConvertInstruction i -> cob.convertInstruction(i.fromType(), i.toType()); - case FieldInstruction i -> cob.fieldInstruction(i.opcode(), i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol()); - case InvokeDynamicInstruction i -> cob.invokeDynamicInstruction(i.invokedynamic().asSymbol()); - case InvokeInstruction i -> cob.invokeInstruction(i.opcode(), i.owner().asSymbol(), i.name().stringValue(), i.typeSymbol(), i.isInterface()); - case LoadInstruction i -> cob.loadInstruction(i.typeKind(), i.slot()); - case StoreInstruction i -> cob.storeInstruction(i.typeKind(), i.slot()); - case IncrementInstruction i -> cob.incrementInstruction(i.slot(), i.constant()); - case LookupSwitchInstruction i -> cob.lookupSwitchInstruction(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 -> cob.monitorInstruction(i.opcode()); - case NewMultiArrayInstruction i -> cob.newMultidimensionalArrayInstruction(i.dimensions(), i.arrayType().asSymbol()); - case NewObjectInstruction i -> cob.newObjectInstruction(i.className().asSymbol()); - case NewPrimitiveArrayInstruction i -> cob.newPrimitiveArrayInstruction(i.typeKind()); - case NewReferenceArrayInstruction i -> cob.newReferenceArrayInstruction(i.componentType().asSymbol()); - case NopInstruction i -> cob.nopInstruction(); - case OperatorInstruction i -> cob.operatorInstruction(i.opcode()); - case ReturnInstruction i -> cob.returnInstruction(i.typeKind()); - case StackInstruction i -> cob.stackInstruction(i.opcode()); - case TableSwitchInstruction i -> cob.tableSwitchInstruction(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.throwInstruction(); - case TypeCheckInstruction i -> cob.typeCheckInstruction(i.opcode(), i.type().asSymbol()); - 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 CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); + 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(); + 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 CustomAttribute a -> + throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); } } }); From 4c730ede1783f41341ce92e083813a44e0ec4b76 Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Tue, 19 Jul 2022 01:10:55 -0700 Subject: [PATCH 028/190] Classfile try catch --- .../classes/jdk/classfile/CodeBuilder.java | 72 +++++ .../jdk/classfile/impl/BlockCodeBuilder.java | 10 + .../jdk/classfile/impl/CatchBuilderImpl.java | 76 +++++ .../jdk/classfile/BuilderTryCatchTest.java | 287 ++++++++++++++++++ 4 files changed, 445 insertions(+) create mode 100644 src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java create mode 100644 test/jdk/jdk/classfile/BuilderTryCatchTest.java diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index bd76ff8d29bae..dbcc678dccf92 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -31,8 +31,11 @@ import java.lang.constant.DynamicCallSiteDesc; import java.lang.constant.MethodTypeDesc; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import jdk.classfile.constantpool.ClassEntry; @@ -48,6 +51,7 @@ import jdk.classfile.impl.AbstractInstruction; import jdk.classfile.impl.BlockCodeBuilder; import jdk.classfile.impl.BytecodeHelpers; +import jdk.classfile.impl.CatchBuilderImpl; import jdk.classfile.impl.ChainedCodeBuilder; import jdk.classfile.impl.LabelImpl; import jdk.classfile.impl.LineNumberImpl; @@ -210,6 +214,74 @@ default CodeBuilder ifThenElse(Consumer thenHandler, 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 #catchingAll + */ + CatchBuilder catching(ClassDesc exceptionType, 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 + */ + 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(); + + // @@@ the tryHandler does not have access to tryCatchEnd + BlockCodeBuilder tryBlock = new BlockCodeBuilder(this); + 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) { diff --git a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java index cfdeae57093f2..5d7151206ced6 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java @@ -26,8 +26,10 @@ import jdk.classfile.CodeBuilder; import jdk.classfile.CodeElement; +import jdk.classfile.Instruction; import jdk.classfile.Label; import jdk.classfile.Opcode; +import jdk.classfile.PseudoInstruction; import jdk.classfile.TypeKind; import jdk.classfile.instruction.LabelTarget; @@ -40,6 +42,7 @@ public final class BlockCodeBuilder private final CodeBuilder parent; private final Label startLabel, endLabel; private boolean reachable = true; + private boolean hasInstructions = false; private int topLocal; private int terminalMaxLocals; @@ -66,6 +69,10 @@ public boolean reachable() { return reachable; } + public boolean isEmpty() { + return !hasInstructions; + } + private int topLocal(CodeBuilder parent) { return switch (parent) { case BlockCodeBuilder b -> b.topLocal; @@ -79,6 +86,9 @@ private int topLocal(CodeBuilder parent) { public CodeBuilder with(CodeElement element) { Opcode op = element.opcode(); parent.with(element); + + hasInstructions |= !op.isPseudo(); + if (reachable) { if (op.isUnconditionalBranch()) reachable = false; diff --git a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java new file mode 100644 index 0000000000000..823ff2e69f091 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java @@ -0,0 +1,76 @@ +package jdk.classfile.impl; + +import jdk.classfile.CodeBuilder; +import jdk.classfile.Label; +import jdk.classfile.Opcode; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { + final CodeBuilder b; + final BlockCodeBuilder tryBlock; + final Label tryCatchEnd; + final Set catchTypes; + BlockCodeBuilder catchBlock; + + public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilder 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) { + Objects.requireNonNull(catchHandler); + + if (catchBlock == null) { + if (tryBlock.reachable()) { + b.branchInstruction(Opcode.GOTO, tryCatchEnd); + } + } + + 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 BlockCodeBuilder(b); + Label tryStart = tryBlock.startLabel(); + Label tryEnd = tryBlock.endLabel(); + catchBlock.start(); + if (exceptionType == null) { + catchBlock.exceptionCatchAll(tryStart, tryEnd, catchBlock.startLabel()); + } + else { + catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), exceptionType); + } + catchHandler.accept(catchBlock); + + return this; + } + + @Override + public void catchingAll(Consumer catchAllHandler) { + catching(null, catchAllHandler); + } + + public void finish() { + if (catchBlock != null) { + catchBlock.end(); + b.labelBinding(tryCatchEnd); + } + } +} diff --git a/test/jdk/jdk/classfile/BuilderTryCatchTest.java b/test/jdk/jdk/classfile/BuilderTryCatchTest.java new file mode 100644 index 0000000000000..4797c8bc3e216 --- /dev/null +++ b/test/jdk/jdk/classfile/BuilderTryCatchTest.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute 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 testng BuilderTryCatchTest + */ + +import jdk.classfile.AccessFlags; +import jdk.classfile.Classfile; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CompoundElement; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.ExceptionCatch; +import org.testng.Assert; +import org.testng.annotations.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.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_String; + +@Test +public class BuilderTryCatchTest { + + static final ClassDesc CD_IOOBE = IndexOutOfBoundsException.class.describeConstable().get(); + static final MethodTypeDesc MTD_String = MethodType.methodType(String.class).describeConstable().get(); + + @Test + public 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)); + + Assert.assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + Assert.assertEquals(main.invoke(new String[]{}), "IndexOutOfBoundsException"); + Assert.assertEquals(main.invoke(null), "any"); + } + + @Test + public 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)); + + Assert.assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + Assert.assertEquals(main.invoke(new String[]{}), "IndexOutOfBoundsException"); + Assert.assertEquals(main.invoke(null), "any"); + } + + @Test + public 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)); + + Assert.assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + Assert.assertEquals(main.invoke(new String[]{}), "IndexOutOfBoundsException"); + Assert.assertThrows(NullPointerException.class, + () -> main.invoke(null)); + } + + @Test + public 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)); + + Assert.assertEquals(main.invoke(new String[]{"BODY"}), "BODY"); + Assert.assertEquals(main.invoke(new String[]{}), "any"); + Assert.assertEquals(main.invoke(null), "any"); + } + + @Test + public 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)); + Assert.assertFalse(anyGotos); + } + + @Test + public 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); + + Assert.assertThrows(IllegalStateException.class, () -> { + xb.trying(tb -> { + }, catchBuilder -> { + Assert.fail(); + + catchBuilder.catchingAll(tb -> { + tb.pop(); + + tb.constantInstruction(Opcode.LDC, "any"); + tb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + + xb.aload(stringSlot); + xb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + } + + @Test + public 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)); + + Assert.assertEquals(main.invoke(new String[]{"BODY"}), Integer.toString(4)); + Assert.assertEquals(main.invoke(new String[]{}), Double.toString(Math.PI)); + Assert.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, false); + tb.astore(stringSlot); + }, c); + + xb.aload(stringSlot); + xb.returnInstruction(TypeKind.ReferenceType); + }); + }); + }); + + return bytes; + } +} \ No newline at end of file From 28cb754252c76edf9d2ba77cd5a25948422fdd50 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 19 Jul 2022 10:14:41 +0200 Subject: [PATCH 029/190] added missing copyright header --- .../jdk/classfile/impl/CatchBuilderImpl.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java index 823ff2e69f091..3613213d1f577 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact 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.classfile.impl; import jdk.classfile.CodeBuilder; From 3369fbb7ad9298008580d2f8f6749e50da6bf1b9 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 19 Jul 2022 15:16:39 +0200 Subject: [PATCH 030/190] patched test calling obsolete CodeBuilder method invokevirtual(..., boolean isInterface) --- test/jdk/jdk/classfile/BuilderTryCatchTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/jdk/classfile/BuilderTryCatchTest.java b/test/jdk/jdk/classfile/BuilderTryCatchTest.java index 4797c8bc3e216..5f8239c90e9c4 100644 --- a/test/jdk/jdk/classfile/BuilderTryCatchTest.java +++ b/test/jdk/jdk/classfile/BuilderTryCatchTest.java @@ -272,7 +272,7 @@ static byte[] generateTryCatchMethod(Consumer c) { // IndexOutOfBoundsException tb.aaload(); // NullPointerException - tb.invokevirtual(CD_String, "toString", MTD_String, false); + tb.invokevirtual(CD_String, "toString", MTD_String); tb.astore(stringSlot); }, c); From e76ed5f58de4f710092bd2a623fa2c8fed827ff4 Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Wed, 20 Jul 2022 01:24:14 -0700 Subject: [PATCH 031/190] Fix and test CodeBuilder::swap (#26) --- .../classes/jdk/classfile/CodeBuilder.java | 2 +- test/jdk/jdk/classfile/SwapTest.java | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 test/jdk/jdk/classfile/SwapTest.java diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index dbcc678dccf92..99163a7c84216 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -1294,7 +1294,7 @@ default CodeBuilder sipush(int s) { } default CodeBuilder swap() { - return operatorInstruction(Opcode.SWAP); + return stackInstruction(Opcode.SWAP); } default CodeBuilder tableswitch(int low, int high, Label defaultTarget, List cases) { diff --git a/test/jdk/jdk/classfile/SwapTest.java b/test/jdk/jdk/classfile/SwapTest.java new file mode 100644 index 0000000000000..fa5c092f4bc56 --- /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 testng SwapTest + */ + +import jdk.classfile.AccessFlags; +import jdk.classfile.Classfile; +import org.testng.Assert; +import org.testng.annotations.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; + +public class SwapTest { + @Test + public 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); + Assert.assertEquals(m.invoke("A", "B"), "B"); + } +} \ No newline at end of file From b5e9ac3ac85c004b81ba19e0ecd593f74ecd4dc7 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 20 Jul 2022 10:25:54 +0200 Subject: [PATCH 032/190] added missing case for SWAP into RebuildingTransformation test helper --- test/jdk/jdk/classfile/helpers/RebuildingTransformation.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 744cfa8ee1942..c85deed4be41d 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -387,6 +387,7 @@ else switch (i.constantValue()) { 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"); } } From 482add7fd77dd60a925328485eef05d5bd5768bb Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Thu, 21 Jul 2022 14:47:43 -0700 Subject: [PATCH 033/190] Ifthenelse (#28) * Enhance ifThenElse. * Expose block builder with break label. * Docs and tests. * Doc. * Review feedback. * Unused import. --- .../classes/jdk/classfile/CodeBuilder.java | 131 ++++++++++++++---- ...Builder.java => BlockCodeBuilderImpl.java} | 27 ++-- .../jdk/classfile/impl/BytecodeHelpers.java | 2 +- .../jdk/classfile/impl/CatchBuilderImpl.java | 14 +- .../impl/NonterminalCodeBuilder.java | 4 +- test/jdk/jdk/classfile/BuilderBlockTest.java | 101 ++++++++++++-- 6 files changed, 224 insertions(+), 55 deletions(-) rename src/java.base/share/classes/jdk/classfile/impl/{BlockCodeBuilder.java => BlockCodeBuilderImpl.java} (84%) diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index 99163a7c84216..bf84fd95e352f 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -31,11 +31,8 @@ import java.lang.constant.DynamicCallSiteDesc; import java.lang.constant.MethodTypeDesc; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Consumer; import jdk.classfile.constantpool.ClassEntry; @@ -49,7 +46,7 @@ import jdk.classfile.constantpool.NameAndTypeEntry; import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.AbstractInstruction; -import jdk.classfile.impl.BlockCodeBuilder; +import jdk.classfile.impl.BlockCodeBuilderImpl; import jdk.classfile.impl.BytecodeHelpers; import jdk.classfile.impl.CatchBuilderImpl; import jdk.classfile.impl.ChainedCodeBuilder; @@ -99,7 +96,7 @@ */ public sealed interface CodeBuilder extends ClassfileBuilder - permits BlockCodeBuilder, ChainedCodeBuilder, TerminalCodeBuilder, NonterminalCodeBuilder { + permits CodeBuilder.BlockCodeBuilder, ChainedCodeBuilder, TerminalCodeBuilder, NonterminalCodeBuilder { /** * {@return the {@link CodeModel} representing the method body being transformed, @@ -159,58 +156,140 @@ public sealed interface CodeBuilder int allocateLocal(TypeKind typeKind); /** - * 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. + * A builder for blocks of code. */ - default CodeBuilder block(Consumer handler) { - BlockCodeBuilder child = new BlockCodeBuilder(this); + 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 CodeBuilder} to + * @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to * generate the body of the {@code if} * @return this builder */ - default CodeBuilder ifThen(Consumer thenHandler) { - BlockCodeBuilder thenBlock = new BlockCodeBuilder(this); - branchInstruction(Opcode.IFEQ, thenBlock.endLabel()); + 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() != CodeElement.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 CodeBuilder} to + * @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to * generate the body of the {@code if} - * @param elseHandler handler that receives a {@linkplain CodeBuilder} to + * @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) { - BlockCodeBuilder thenBlock = new BlockCodeBuilder(this); - BlockCodeBuilder elseBlock = new BlockCodeBuilder(this); - branchInstruction(Opcode.IFEQ, elseBlock.startLabel()); + 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() != CodeElement.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, elseBlock.endLabel()); + thenBlock.branchInstruction(Opcode.GOTO, thenBlock.breakLabel()); thenBlock.end(); elseBlock.start(); elseHandler.accept(elseBlock); elseBlock.end(); + labelBinding(breakLabel); return this; } @@ -234,7 +313,7 @@ sealed interface CatchBuilder permits CatchBuilderImpl { * @throws java.lang.IllegalArgumentException if an existing catch block catches an exception of the given type. * @see #catchingAll */ - CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler); + CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler); /** * Adds a "catch" block that catches all exceptions. @@ -246,7 +325,7 @@ sealed interface CatchBuilder permits CatchBuilderImpl { * @throws java.lang.IllegalArgumentException if an existing catch block catches all exceptions. * @see #catching */ - void catchingAll(Consumer catchAllHandler); + void catchingAll(Consumer catchAllHandler); } /** @@ -260,12 +339,12 @@ sealed interface CatchBuilder permits CatchBuilderImpl { * @return this builder * @see CatchBuilder */ - default CodeBuilder trying(Consumer tryHandler, + default CodeBuilder trying(Consumer tryHandler, Consumer catchesHandler) { Label tryCatchEnd = newLabel(); // @@@ the tryHandler does not have access to tryCatchEnd - BlockCodeBuilder tryBlock = new BlockCodeBuilder(this); + BlockCodeBuilderImpl tryBlock = new BlockCodeBuilderImpl(this, tryCatchEnd); tryBlock.start(); tryHandler.accept(tryBlock); tryBlock.end(); diff --git a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java similarity index 84% rename from src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java rename to src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java index 5d7151206ced6..ad6a10fae763f 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java @@ -26,31 +26,32 @@ import jdk.classfile.CodeBuilder; import jdk.classfile.CodeElement; -import jdk.classfile.Instruction; import jdk.classfile.Label; import jdk.classfile.Opcode; -import jdk.classfile.PseudoInstruction; import jdk.classfile.TypeKind; import jdk.classfile.instruction.LabelTarget; +import java.util.Objects; + /** * BlockCodeBuilder */ -public final class BlockCodeBuilder +public final class BlockCodeBuilderImpl extends NonterminalCodeBuilder - implements CodeBuilder { + implements CodeBuilder.BlockCodeBuilder { private final CodeBuilder parent; - private final Label startLabel, endLabel; + private final Label startLabel, endLabel, breakLabel; private boolean reachable = true; private boolean hasInstructions = false; private int topLocal; private int terminalMaxLocals; - public BlockCodeBuilder(CodeBuilder parent) { + public BlockCodeBuilderImpl(CodeBuilder parent, Label breakLabel) { super(parent); this.parent = parent; - startLabel = terminal.newLabel(); - endLabel = terminal.newLabel(); + this.startLabel = parent.newLabel(); + this.endLabel = parent.newLabel(); + this.breakLabel = Objects.requireNonNull(breakLabel); } public void start() { @@ -61,8 +62,9 @@ public void start() { public void end() { terminal.with((LabelTarget) endLabel); - if (terminalMaxLocals != topLocal(terminal)) + if (terminalMaxLocals != topLocal(terminal)) { throw new IllegalStateException("Interference in local variable slot management"); + } } public boolean reachable() { @@ -75,7 +77,7 @@ public boolean isEmpty() { private int topLocal(CodeBuilder parent) { return switch (parent) { - case BlockCodeBuilder b -> b.topLocal; + case BlockCodeBuilderImpl b -> b.topLocal; case ChainedCodeBuilder b -> topLocal(b.terminal); case DirectCodeBuilder b -> b.curTopLocal(); case BufferedCodeBuilder b -> b.curTopLocal(); @@ -115,4 +117,9 @@ public int allocateLocal(TypeKind typeKind) { topLocal += typeKind.slotSize(); return retVal; } + + @Override + public Label breakLabel() { + return breakLabel; + } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java index a0c9de2060fbf..cd1f2de6e082e 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java @@ -193,7 +193,7 @@ public static Opcode arrayStoreOpcode(TypeKind tk) { }; } - static Opcode reverseBranchOpcode(Opcode op) { + public static Opcode reverseBranchOpcode(Opcode op) { return switch (op) { case IFEQ -> IFNE; case IFNE -> IFEQ; diff --git a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java index 3613213d1f577..30d7284aa87c7 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java @@ -37,12 +37,12 @@ public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { final CodeBuilder b; - final BlockCodeBuilder tryBlock; + final BlockCodeBuilderImpl tryBlock; final Label tryCatchEnd; final Set catchTypes; - BlockCodeBuilder catchBlock; + BlockCodeBuilderImpl catchBlock; - public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilder tryBlock, Label tryCatchEnd) { + public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilderImpl tryBlock, Label tryCatchEnd) { this.b = b; this.tryBlock = tryBlock; this.tryCatchEnd = tryCatchEnd; @@ -50,7 +50,7 @@ public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilder tryBlock, Label tryCatch } @Override - public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler) { + public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler) { Objects.requireNonNull(catchHandler); if (catchBlock == null) { @@ -71,7 +71,7 @@ public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer catchAllHandler) { + public void catchingAll(Consumer catchAllHandler) { catching(null, catchAllHandler); } public void finish() { if (catchBlock != null) { catchBlock.end(); - b.labelBinding(tryCatchEnd); } + b.labelBinding(tryCatchEnd); } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java index 790cee50c8684..f6932667f1a63 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java @@ -35,13 +35,13 @@ * NonterminalCodeBuilder */ public abstract sealed class NonterminalCodeBuilder implements CodeBuilder - permits ChainedCodeBuilder, BlockCodeBuilder { + permits ChainedCodeBuilder, BlockCodeBuilderImpl { protected final TerminalCodeBuilder terminal; public NonterminalCodeBuilder(CodeBuilder downstream) { this.terminal = switch (downstream) { case ChainedCodeBuilder cb -> cb.terminal; - case BlockCodeBuilder cb -> cb.terminal; + case BlockCodeBuilderImpl cb -> cb.terminal; case TerminalCodeBuilder cb -> cb; }; } diff --git a/test/jdk/jdk/classfile/BuilderBlockTest.java b/test/jdk/jdk/classfile/BuilderBlockTest.java index 5b1ae2914731d..85f3d0d828e57 100644 --- a/test/jdk/jdk/classfile/BuilderBlockTest.java +++ b/test/jdk/jdk/classfile/BuilderBlockTest.java @@ -42,8 +42,10 @@ import java.lang.reflect.AccessFlag; import jdk.classfile.Classfile; import jdk.classfile.Label; +import jdk.classfile.Opcode; import jdk.classfile.TypeKind; import jdk.classfile.impl.LabelImpl; +import org.testng.Assert; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -100,7 +102,7 @@ public void testStartEndBlock() throws Exception { assertEquals(((LabelImpl) startEnd[3]).getContextInfo(), 2); } - public void testIfThen() throws Exception { + public 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), @@ -113,12 +115,12 @@ public void testIfThen() throws Exception { Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) .getMethod("Foo", "foo"); - assertEquals((Integer) fooMethod.invoke(null, 3), (Integer) 1); - assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); } - public void testIfThenElse() throws Exception { + public 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), @@ -130,12 +132,31 @@ public void testIfThenElse() throws Exception { Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) .getMethod("Foo", "foo"); - assertEquals((Integer) fooMethod.invoke(null, 3), (Integer) 1); - assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); } - public void testIfThenElse2() throws Exception { + public 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); + Assert.assertThrows(IllegalArgumentException.class, () -> { + xb.ifThen( + Opcode.GOTO, + xxb -> xxb.iconst_1().istore(2)); + }); + xb.iload(2); + xb.ireturn(); + })); + }); + } + + public 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), @@ -149,9 +170,71 @@ public void testIfThenElse2() throws Exception { Method fooMethod = new ByteArrayClassLoader(BuilderBlockTest.class.getClassLoader(), "Foo", bytes) .getMethod("Foo", "foo"); - assertEquals((Integer) fooMethod.invoke(null, 3), (Integer) 1); - assertEquals((Integer) fooMethod.invoke(null, 0), (Integer) 2); + assertEquals(fooMethod.invoke(null, 3), 1); + assertEquals(fooMethod.invoke(null, 0), 2); + + } + public 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); + } + + public 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); + } + + public 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); + Assert.assertThrows(IllegalArgumentException.class, () -> { + xb.ifThenElse( + Opcode.GOTO, + xxb -> xxb.iconst_1().istore(2), + xxb -> xxb.iconst_2().istore(2)); + }); + xb.iload(2); + xb.ireturn(); + })); + }); } public void testAllocateLocal() { From 34e72c5807bd17136e3d73f82b9bc334afe60178 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 20 Jul 2022 13:59:01 +0200 Subject: [PATCH 034/190] ClassRemapper implemenation handling RecordAttribute, InnerClassesAttribute, EnclosingMethodAttribute, Annotations, TypeAnnotations, ParameterAnnotations and LoadableConstants --- .../jdk/classfile/impl/ConcreteEntry.java | 2 +- .../classfile/transforms/ClassRemapper.java | 156 +++++++++++++++++- .../AdvancedTransformationsTest.java | 97 +++++++++++ 3 files changed, 248 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java b/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java index bead58d7618b5..f17727e0dd964 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java @@ -840,7 +840,7 @@ public ConcreteEntry.MemberRefEntry reference() { public DirectMethodHandleDesc asSymbol() { return MethodHandleDesc.of( DirectMethodHandleDesc.Kind.valueOf(kind(), reference().isInterface()), - ClassDesc.of(Util.toClassString(((jdk.classfile.constantpool.MemberRefEntry) reference()).owner().asInternalName())), + ((jdk.classfile.constantpool.MemberRefEntry) reference()).owner().asSymbol(), ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().name().stringValue(), ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().type().stringValue()); } diff --git a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java b/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java index a620fc84af2b5..8558ece4c799e 100644 --- a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java +++ b/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java @@ -24,10 +24,18 @@ package jdk.classfile.transforms; 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.classfile.Annotation; +import jdk.classfile.AnnotationElement; +import jdk.classfile.AnnotationValue; import jdk.classfile.ClassBuilder; import jdk.classfile.ClassElement; import jdk.classfile.ClassModel; @@ -51,7 +59,6 @@ import jdk.classfile.instruction.InvokeInstruction; import jdk.classfile.instruction.NewMultiArrayInstruction; import jdk.classfile.instruction.NewObjectInstruction; -import jdk.classfile.instruction.NewPrimitiveArrayInstruction; import jdk.classfile.instruction.NewReferenceArrayInstruction; import jdk.classfile.instruction.TypeCheckInstruction; import jdk.classfile.MethodModel; @@ -59,12 +66,28 @@ import jdk.classfile.MethodTransform; import jdk.classfile.Signature; import jdk.classfile.Superclass; +import jdk.classfile.TypeAnnotation; +import jdk.classfile.attribute.EnclosingMethodAttribute; import jdk.classfile.attribute.ExceptionsAttribute; +import jdk.classfile.attribute.InnerClassInfo; +import jdk.classfile.attribute.InnerClassesAttribute; +import jdk.classfile.attribute.ModuleAttribute; +import jdk.classfile.attribute.ModuleProvideInfo; +import jdk.classfile.attribute.RecordAttribute; +import jdk.classfile.attribute.RecordComponentInfo; +import jdk.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; import jdk.classfile.attribute.SignatureAttribute; +import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.instruction.ExceptionCatch; import jdk.classfile.instruction.LocalVariable; import jdk.classfile.instruction.LocalVariableType; import jdk.classfile.impl.Util; +import jdk.classfile.instruction.ConstantInstruction.LoadConstantInstruction; /** * @@ -116,6 +139,32 @@ public ClassTransform classTransform() { 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 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); } @@ -128,6 +177,14 @@ public FieldTransform fieldTransform() { 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); } @@ -144,6 +201,18 @@ public MethodTransform methodTransform() { 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); } @@ -162,20 +231,24 @@ public CodeTransform codeTransform() { cob.invokeDynamicInstruction(DynamicCallSiteDesc.of(idi.bootstrapMethod(), idi.name().stringValue(), mapMethodDesc(idi.typeSymbol()))); case NewObjectInstruction c -> cob.newObjectInstruction(map(c.className().asSymbol())); - case NewPrimitiveArrayInstruction c -> - cob.newPrimitiveArrayInstruction(c.typeKind()); case NewReferenceArrayInstruction c -> - cob.anewarray(c.componentType().asSymbol()); + cob.anewarray(map(c.componentType().asSymbol())); case NewMultiArrayInstruction c -> - cob.multianewarray(c.arrayType().asSymbol(), c.dimensions()); + 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(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(map(d.asSymbol())))))); + 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); } @@ -207,6 +280,54 @@ MethodSignature mapMethodSignature(MethodSignature signature) { signature.throwableSignatures().stream().map(this::mapSignature).toList()); } + 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) { @@ -218,5 +339,28 @@ S mapSignature(S signature) { 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(); + } } } diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index eed5f88582cea..10d11540fb678 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -53,12 +53,19 @@ import jdk.classfile.CodeElement; import jdk.classfile.FieldModel; import jdk.classfile.Signature; +import jdk.classfile.attribute.ModuleAttribute; import jdk.classfile.impl.AbstractInstruction; import jdk.classfile.impl.RawBytecodeHelper; import jdk.classfile.impl.Util; import jdk.classfile.instruction.InvokeInstruction; import java.lang.reflect.AccessFlag; import jdk.classfile.transforms.LabelsRemapper; +import jdk.classfile.jdktypes.ModuleDesc; +import jdk.classfile.util.ClassPrinter; +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; public class AdvancedTransformationsTest { @@ -118,6 +125,96 @@ public void testRemapClass() throws Exception { } } + @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE}) + @Retention(RetentionPolicy.RUNTIME) + @interface FooAnno { + } + + @interface BarAnno { + } + + public static class Foo { + public static Foo fooField; + public static Foo fooMethod(Foo arg) { + return null; + } + + }; + public static class Bar {}; + + @FooAnno + public static record Rec(@FooAnno Foo foo) { + @FooAnno + public Rec(Foo foo) { + this.foo = new @FooAnno Foo(); + Foo local @FooAnno [] = new Foo @FooAnno [0]; + Foo.fooField = foo; + Foo.fooMethod(foo); + } + } + + @Test + public void testRemapModule() throws Exception { + var foo = ClassDesc.ofDescriptor(Foo.class.descriptorString()); + var bar = ClassDesc.ofDescriptor(Bar.class.descriptorString()); + + var ma = Classfile.parse( + ClassRemapper.of(Map.of(foo, bar)).remapClass( + Classfile.parse( + Classfile.buildModule( + ModuleAttribute.of(ModuleDesc.of("MyModule"), mab -> + 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 + public 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.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, sb::append).printClass(remapped); + String out = sb.toString(); + assertContains(out, + "type: 'LAdvancedTransformationsTest$Bar;'", + "['AdvancedTransformationsTest$Bar', 'AdvancedTransformationsTest', 'Foo', [PUBLIC, STATIC]]", + "descriptor: 'LAdvancedTransformationsTest$Bar;'", + "descriptor: '(LAdvancedTransformationsTest$Bar;)V'", + "[NEW, {type: 'AdvancedTransformationsTest$Bar'}]", + "[INVOKESPECIAL, {owner: 'AdvancedTransformationsTest$Bar', name: '', descriptor: '()V'}]", + "[PUTFIELD, {owner: 'AdvancedTransformationsTest$Rec', name: 'foo', descriptor: 'LAdvancedTransformationsTest$Bar;'}]", + "[ANEWARRAY, {dimensions: 1, descriptor: 'AdvancedTransformationsTest$Bar'}]", + "[PUTSTATIC, {owner: 'AdvancedTransformationsTest$Bar', name: 'fooField', descriptor: 'LAdvancedTransformationsTest$Bar;'}]", + "[INVOKESTATIC, {owner: 'AdvancedTransformationsTest$Bar', name: 'fooMethod', descriptor: '(LAdvancedTransformationsTest$Bar;)LAdvancedTransformationsTest$Bar;'}]", + "descriptor: '()LAdvancedTransformationsTest$Bar;'", + "[GETFIELD, {owner: 'AdvancedTransformationsTest$Rec', name: 'foo', descriptor: 'LAdvancedTransformationsTest$Bar;'}]", + "{class: 'LAdvancedTransformationsTest$BarAnno;'}", + "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'FIELD'}", + "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'METHOD_RETURN'}", + "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'NEW'}", + "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'LOCAL_VARIABLE'}", + "#visible type annotation: {class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'NEW'}", + "#visible type annotation: {class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'LOCAL_VARIABLE'}"); + } + + + + 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 -> From 269e52933db08e34fb5f0b4ba6c7b3809d270e37 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 3 Aug 2022 13:01:51 +0200 Subject: [PATCH 035/190] added more context to StackMapGenerator error messages --- .../jdk/classfile/impl/StackMapGenerator.java | 35 ++++++++++++++----- test/jdk/jdk/classfile/StackMapsTest.java | 4 +-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 4e8541a0b0322..911ce122e5d6a 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import jdk.classfile.Attribute; import static jdk.classfile.Classfile.*; @@ -328,7 +329,7 @@ private void generate() { var frame = frames.get(i); if (DEBUG) System.out.println(" " + frame); if (frame.flags == -1) { - if (!patchDeadCode) generatorError("Unable to generate stack map frame for dead code"); + 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; @@ -717,7 +718,7 @@ private void processLdc(int index) { case TAG_CONSTANTDYNAMIC -> currentFrame.pushStack(((ConstantDynamicEntry)cp.entryByIndex(index)).asSymbol().constantType()); default -> - generatorError("Invalid index in ldc"); + generatorError("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag())); } } @@ -848,7 +849,7 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl } private Type getNewarrayType(int index) { - if (index < T_BOOLEAN || index > T_LONG) generatorError("Illegal newarray instruction"); + if (index < T_BOOLEAN || index > T_LONG) generatorError("Illegal newarray instruction type %d".formatted(index)); return ARRAY_FROM_BASIC_TYPE[index]; } @@ -862,7 +863,20 @@ private void processAnewarray(int index) { * @param msg error message */ private void generatorError(String msg) { - throw new VerifyError(String.format("%s at %s", msg, methodName)); + 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) { + throw new VerifyError(String.format("%s at bytecode offset %d of method %s(%s)", + msg, + offset, + methodName, + methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")))); } /** @@ -874,15 +888,14 @@ private BitSet detectFrameOffsets() { var offsets = new BitSet() { @Override public void set(int i) { - if (i < 0 || i >= bytecode.capacity()) - generatorError("Frame offset out of bytecode range"); + 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; - while (!bcs.isLastBytecode()) { + int opcode, bci = 0; + while (!bcs.isLastBytecode()) try { opcode = bcs.rawNext(); bci = bcs.bci; if (no_control_flow) { @@ -927,9 +940,13 @@ public void set(int i) { 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) { + for (var exhandler : exceptionTable) try { offsets.set(labelContext.labelToBci(exhandler.handler())); + } catch (IllegalArgumentException iae) { + generatorError("Detected exception handler out of bytecode range"); } return offsets; } diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index 5e14bb52c055c..e05853cc2f959 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -93,7 +93,7 @@ public void testDeadCodePatternPatch() throws Exception { testTransformedStackMaps(buildDeadCode()); } - @Test(expectedExceptions = VerifyError.class) + @Test(expectedExceptions = VerifyError.class, expectedExceptionsMessageRegExp = "Unable to generate stack map frame for dead code at bytecode offset 1 of method twoReturns\\(\\)") public void testDeadCodePatternFail() throws Exception { testTransformedStackMaps(buildDeadCode(), Classfile.Option.patchDeadCode(false)); } @@ -158,7 +158,7 @@ public void testPattern10() throws Exception { testTransformedStackMaps("/testdata/Pattern10.class"); } - @Test(expectedExceptions = VerifyError.class) + @Test(expectedExceptions = VerifyError.class, expectedExceptionsMessageRegExp = "Detected branch target out of bytecode range at bytecode offset 0 of method frameOutOfRangeMethod\\(\\)") public void testFrameOutOfBytecodeRange() { Classfile.parse( Classfile.build(ClassDesc.of("TestClass"), clb -> From 2b471fce5d58ca719980feeb296d36a738f42f2e Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 3 Aug 2022 14:36:55 +0200 Subject: [PATCH 036/190] new ClassPrinter API and implementation --- .../classes/jdk/classfile/ClassPrinter.java | 90 ++ .../attribute/LocalVariableInfo.java | 8 + .../classfile/impl/BoundLocalVariable.java | 6 + .../jdk/classfile/impl/ClassPrinterImpl.java | 1424 ++++++++++------- .../classfile/impl/verifier/VerifierImpl.java | 4 +- .../jdk/classfile/util/ClassPrinter.java | 55 - .../AdvancedTransformationsTest.java | 39 +- test/jdk/jdk/classfile/ClassPrinterTest.java | 909 ++++++++--- test/jdk/jdk/classfile/StackMapsTest.java | 1 - .../TempConstantPoolBuilderTest.java | 2 +- .../examples/AnnotationsExamples.java | 4 +- .../classfile/helpers/CorpusTestHelper.java | 2 +- 12 files changed, 1645 insertions(+), 899 deletions(-) create mode 100644 src/java.base/share/classes/jdk/classfile/ClassPrinter.java delete mode 100644 src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java diff --git a/src/java.base/share/classes/jdk/classfile/ClassPrinter.java b/src/java.base/share/classes/jdk/classfile/ClassPrinter.java new file mode 100644 index 0000000000000..33dbca41732b3 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/ClassPrinter.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.classfile; + +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.classfile.impl.ClassPrinterImpl; + +/** + * + */ +public final class ClassPrinter { + + public enum Verbosity { MEMBERS_ONLY, CRITICAL_ATTRIBUTES, TRACE_ALL } + + public sealed interface Node { + + public ConstantDesc name(); + + public Stream walk(); + + default public void toJson(Consumer out) { + ClassPrinterImpl.toJson(this, out); + } + + default public void toXml(Consumer out) { + ClassPrinterImpl.toXml(this, out); + } + + default public void toYaml(Consumer out) { + ClassPrinterImpl.toYaml(this, out); + } + } + + public sealed interface LeafNode extends Node + permits ClassPrinterImpl.LeafNodeImpl { + + public ConstantDesc value(); + } + + public sealed interface ListNode extends Node, List + permits ClassPrinterImpl.ListNodeImpl { + } + + public sealed interface MapNode extends Node, Map + permits ClassPrinterImpl.MapNodeImpl { + } + + public static MapNode toTree(CompoundElement model, Verbosity verbosity) { + return ClassPrinterImpl.modelToTree(model, verbosity); + } + + public static void toJson(CompoundElement model, Verbosity verbosity, Consumer out) { + toTree(model, verbosity).toJson(out); + } + + public static void toXml(CompoundElement model, Verbosity verbosity, Consumer out) { + toTree(model, verbosity).toXml(out); + } + + public static void toYaml(CompoundElement model, Verbosity verbosity, Consumer out) { + toTree(model, verbosity).toYaml(out); + } +} diff --git a/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.java b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.java index 680650b89aaea..2a72120c34bc4 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/LocalVariableInfo.java @@ -24,6 +24,7 @@ */ package jdk.classfile.attribute; +import java.lang.constant.ClassDesc; import jdk.classfile.constantpool.Utf8Entry; import jdk.classfile.impl.BoundLocalVariable; import jdk.classfile.impl.UnboundAttribute; @@ -56,6 +57,13 @@ sealed public interface LocalVariableInfo */ 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} diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java index a21186257bb4f..857dc74a5c072 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundLocalVariable.java @@ -25,6 +25,7 @@ package jdk.classfile.impl; +import java.lang.constant.ClassDesc; import jdk.classfile.Opcode; import jdk.classfile.attribute.LocalVariableInfo; import jdk.classfile.constantpool.Utf8Entry; @@ -52,6 +53,11 @@ public Utf8Entry type() { return secondaryEntry(); } + @Override + public ClassDesc typeSymbol() { + return ClassDesc.ofDescriptor(type().stringValue()); + } + @Override public void writeTo(DirectCodeBuilder writer) { writer.addLocalVariable(this); diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java index f90499fe9a3c7..e139f3fcb6edc 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java @@ -24,341 +24,218 @@ */ package jdk.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.Optional; import java.util.function.BiConsumer; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; +import jdk.classfile.Annotation; import jdk.classfile.AnnotationElement; import jdk.classfile.AnnotationValue; import jdk.classfile.AnnotationValue.*; import jdk.classfile.Attribute; import jdk.classfile.ClassModel; -import jdk.classfile.constantpool.AnnotationConstantValueEntry; -import jdk.classfile.constantpool.ClassEntry; -import jdk.classfile.constantpool.ConstantDynamicEntry; -import jdk.classfile.constantpool.DoubleEntry; -import jdk.classfile.constantpool.DynamicConstantPoolEntry; -import jdk.classfile.constantpool.FloatEntry; -import jdk.classfile.constantpool.IntegerEntry; -import jdk.classfile.constantpool.InvokeDynamicEntry; -import jdk.classfile.constantpool.LongEntry; -import jdk.classfile.constantpool.MemberRefEntry; -import jdk.classfile.constantpool.MethodHandleEntry; -import jdk.classfile.constantpool.MethodTypeEntry; -import jdk.classfile.constantpool.ModuleEntry; -import jdk.classfile.constantpool.NameAndTypeEntry; -import jdk.classfile.constantpool.PackageEntry; -import jdk.classfile.constantpool.PoolEntry; -import jdk.classfile.constantpool.StringEntry; -import jdk.classfile.constantpool.Utf8Entry; -import jdk.classfile.util.ClassPrinter; +import jdk.classfile.CodeModel; +import jdk.classfile.ClassPrinter.*; import jdk.classfile.Instruction; -import jdk.classfile.instruction.*; import jdk.classfile.MethodModel; import jdk.classfile.TypeAnnotation; -import jdk.classfile.TypeKind; import jdk.classfile.attribute.*; import jdk.classfile.attribute.StackMapTableAttribute.*; +import jdk.classfile.constantpool.*; +import jdk.classfile.instruction.*; -import static jdk.classfile.Classfile.TAG_CLASS; -import static jdk.classfile.Classfile.TAG_CONSTANTDYNAMIC; -import static jdk.classfile.Classfile.TAG_DOUBLE; -import static jdk.classfile.Classfile.TAG_FIELDREF; -import static jdk.classfile.Classfile.TAG_FLOAT; -import static jdk.classfile.Classfile.TAG_INTEGER; -import static jdk.classfile.Classfile.TAG_INTERFACEMETHODREF; -import static jdk.classfile.Classfile.TAG_INVOKEDYNAMIC; -import static jdk.classfile.Classfile.TAG_LONG; -import static jdk.classfile.Classfile.TAG_METHODHANDLE; -import static jdk.classfile.Classfile.TAG_METHODREF; -import static jdk.classfile.Classfile.TAG_METHODTYPE; -import static jdk.classfile.Classfile.TAG_MODULE; -import static jdk.classfile.Classfile.TAG_NAMEANDTYPE; -import static jdk.classfile.Classfile.TAG_PACKAGE; -import static jdk.classfile.Classfile.TAG_STRING; -import static jdk.classfile.Classfile.TAG_UTF8; +import static jdk.classfile.Classfile.*; +import jdk.classfile.CompoundElement; +import jdk.classfile.FieldModel; +import static jdk.classfile.impl.ClassPrinterImpl.Style.*; /** * ClassPrinterImpl */ -public final class ClassPrinterImpl implements ClassPrinter { - - public record Format(char quotes, boolean quoteFlagsAndAttrs, boolean quoteTypes, String mandatoryDelimiter, String inlineDelimiter, - Block classForm, Block constantPool, String valueEntry, String stringEntry, String namedEntry, String memberEntry, - String nameAndTypeEntry, String methodHandleEntry, String methodTypeEntry, String dynamicEntry, - String fieldsHeader, Block field, String methodsHeader, Block method, String simpleAttr, String simpleQuotedAttr, - String annotationDefault, Table annotations, Table typeAnnotations, String typeAnnotationInline, Table parameterAnnotations, - Table annotationValuePair, Table bootstrapMethods, String enclosingMethod, Table innerClasses, Table methodParameters, - Table recordComponents, String recordComponentTail, Block module, Table requires, Table exports, Table opens, Table provides, - String modulePackages, String moduleMain, String nestHost, String nestMembers, - Block code, String plainInstruction, String localVariableInstruction, String incInstruction, String memberInstruction, String invokeDynamicInstruction, - String branchInstruction, String switchInstruction, String newArrayInstruction, String typeInstruction, String constantInstruction, - Table exceptionHandlers, String tryStartInline, String tryEndInline, String handlerInline, Table localVariableTable, String localVariableInline, - String frameInline, Table stackMapTable, Table lineNumberTable, Table characterRangeTable, Table localVariableTypeTable, - Function escapeFunction) {} - - public record Block(String header, String footer){} - - public record Table(String header, String footer, String element){} - - private record ExceptionHandler(int start, int end, int handler, String catchType) {} +public final class ClassPrinterImpl { - public static final Format JSON = new Format('"', true, true, ",", ", ", - new Block(" { \"class name\": \"%s\",%n \"version\": \"%d.%d\",%n \"flags\": %s,%n \"superclass\": \"%s\",%n \"interfaces\": %s,%n \"attributes\": %s", "]%n }"), - new Block(",%n \"constant pool\": {", " }"), - "%n \"%d\": [\"%s\", \"%s\"]", - "%n \"%d\": [\"%s\", { \"value index:\": %d, \"value:\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"name index:\": %d, \"name:\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"owner index:\": %d, \"name and type index:\": %d, \"owner:\": \"%s\", \"name:\": \"%s\", \"type:\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"name index:\": %d, \"type index:\": %d, \"name:\": \"%s\", \"type:\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"reference kind:\": \"%s\", \"reference index:\": %d, \"owner:\": \"%s\", \"name:\": \"%s\", \"type:\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"descriptor index:\": %d, \"descriptor:\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"bootstrap method handle index:\": %d, \"bootstrap method arguments indexes:\": %s, \"name and type index:\": %d, \"name:\": \"%s\", \"type:\": \"%s\" }]", - ",%n \"fields\": [", - new Block("%n { \"field name\": \"%s\",%n \"flags\": %s,%n \"descriptor\": \"%s\",%n \"attributes\": %s", " }"), - "],%n \"methods\": [", - new Block("%n { \"method name\": \"%s\",%n \"flags\": %s,%n \"descriptor\": \"%s\",%n \"attributes\": %s", " }"), - ",%n%s\"%s\": %s", - ",%n%s\"%s\": \"%s\"", - ",%n%s\"annotation default\": \"%s\"", - new Table(",%n%s\"%s annotations\": [", "]", "%n%s {\"class\": \"%s\"%s}"), - new Table(",%n%s\"%s type annotations\": [", "]", "%n%s {\"class\": \"%s\", \"target type\": \"%s\"%s}"), - "",//JSON does not allow comments - new Table(",%n%s\"%s parameter %d annotations\": [", "]", "%n%s {\"class\": \"%s\"%s}"), - new Table(", \"values\": {", "}", "\"%s\": \"%s\""), - new Table(",%n \"bootstrap methods\": [", "]", "%n { \"bootstrap method kind\": \"%s\", \"owner\": \"%s\", \"method name\": \"%s\", \"invocation type\": \"%s\", \"is interface\": %b, \"is methods\": %b }"), - ",%n%s\"enclosing method\": { \"class\": \"%s\", \"method name\": \"%s\", \"type\": \"s\" }", - new Table(",%n \"inner classes\": [", "]", "%n { \"inner class\": \"%s\", \"outer class\": \"%s\", \"inner class entry\": \"%s\", \"flags\": %s }"), - new Table(",%n \"method parameters\": [", "]", "%n { \"parameter name\": \"%s\", \"flags\": %s }"), - new Table(",%n \"record components\": [", "]", "%n { \"name\": \"%s\", \"type\": \"%s\", \"attributes\": %s"), - " }", - new Block(",%n \"module\": {%n \"name\": \"%s\",%n \"flags\": %s,%n \"version\": \"%s\",%n \"uses\": %s", " }"), - new Table(",%n \"requires\": [", "]", "%n { \"name\": \"%s\", \"flags\": %s, \"version\": \"%s\" }"), - new Table(",%n \"exports\": [", "]", "%n { \"package\": \"%s\", \"flags\": %s, \"to\": %s }"), - new Table(",%n \"opens\": [", "]", "%n { \"package\": \"%s\", \"flags\": %s, \"to\": %s }"), - new Table(",%n \"provides\": [", "]", "%n { \"class\": \"%s\", \"with\": %s }"), - ",%n \"module packages\": %s", - ",%n \"module main class\": \"%s\"", - ",%n \"nest host\": \"%s\"", - ",%n \"nest members\": %s", - new Block(",%n \"code\": {%n \"max stack\": %d,%n \"max locals\": %d,%n \"attributes\": %s", " }"), - "%n \"%d\": [\"%s\"]", - "%n \"%d\": [\"%s\", { \"slot\": %d%s }]", - "%n \"%d\": [\"%s\" , { \"slot\": %d, \"const\": \"%+d\"%s }]", - "%n \"%d\": [\"%s\", { \"owner\": \"%s\", \"name\": \"%s\", \"descriptor\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"name\": \"%s\", \"descriptor\": \"%s\", \"bootstrap method kind\": \"%s\", \"owner\": \"%s\", \"method name\": \"%s\", \"invocation type\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"target\": %d }]", - "%n \"%d\": [\"%s\", { \"targets\": %s }]", - "%n \"%d\": [\"%s\", { \"dimensions\": %d, \"descriptor\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"type\": \"%s\" }]", - "%n \"%d\": [\"%s\", { \"constant value\": \"%s\" }]", - new Table(",%n \"exception handlers\": [", "]", "%n [%d, %d, %d, \"%s\"]"), - "", //JSON does not allow comments - "", - "", - new Table(",%n \"local variables%s\": [", "]", "%n [%d, %d, %d, \"%s\", \"%s\"]"), - ", \"type\": \"%s\", \"name\": \"%s\"", - "", - new Table(",%n \"stack map frames\": {", " }", "%n \"%d\": { \"locals\": %s, \"stack\": %s }"), - new Table(",%n \"line numbers%s\": [", "]", "%n [%d, %d]"), - new Table(",%n \"character ranges\": [", "]", "%n [%d, %d, %d, %d, %d]"), - new Table(",%n \"local variable types%s\": [", "]", "%n [%d, %d, %d, \"%s\", \"%s\"]"), - ClassPrinterImpl::escapeJson); - - public static final Format XML = new Format('\'', false, false, "", "", - new Block("%n ", "%n"), - new Block("%n ", ""), - "%n <:>%d<%s>%s", - "%n <:>%d<%s value_index='%d' value='%s'/>", - "%n <:>%d<%s name_index='%d' name='%s'/>", - "%n <:>%d<%s owner_index='%d' name_and_type_index='%d' owner='%s' name='%s' type='%s'/>", - "%n <:>%d<%s name_index='%d' type_index='%d' name='%s' type='%s'/>", - "%n <:>%d<%s reference_kind='%s' reference_index='%d' owner='%s' name='%s' type='%s'/>", - "%n <:>%d<%s descriptor_index='%d' descriptor='%s'/>", - "%n <:>%d<%s bootstrap_method_handle_index='%d' bootstrap_method_arguments_indexes:='%s' name_and_type_index='%d' name='%s' type='%s'/>", - "%n ", - new Block("%n ", ""), - "%n ", - new Block("%n ", ""), - "%n%s<%s>%s", - "%n%s<%s>%s", - "%n%s'%s'", - new Table(",%n%s<%s_annotations>", "", "%n%s %s"), - new Table(",%n%s<%s_type_annotations>", "", "%n%s %s"), - "%n ", - new Table(",%n%s<%s_parameter_%d_annotations>", "", "%n%s %s"), - new Table("", "", "%s"), - new Table(",%n ", "", "%n "), - "%n%s", - new Table(",%n ", "", "%n "), - new Table(",%n ", "", "%n "), - new Table(",%n ", "", "%n "), - "", - new Block(",%n ", ""), - new Table(",%n ", "", "%n "), - new Table(",%n ", "", "%n "), - new Table(",%n ", "", "%n "), - new Table(",%n ", "", "%n "), - ",%n %s", - ",%n %s", - ",%n %s", - ",%n %s", - new Block("%n ", ""), - "%n <:>%d<%s/>", - "%n <:>%d<%s slot='%d'%s/>", - "%n <:>%d<%s slot='%d' const='%+d'%s/>", - "%n <:>%d<%s owner='%s' name='%s' descriptor='%s'/>", - "%n <:>%d<%s name='%s' descriptor='%s' bootstrap_method_kind='%s' owner='%s' method_name='%s' invocation_type='%s'/>", - "%n <:>%d<%s target='%d'/>", - "%n <:>%d<%s targets='%s'/>", - "%n <:>%d<%s dimensions='%d' descriptor:='%s'/>", - "%n <:>%d<%s type='%s'/>", - "%n <:>%d<%s constant_value='%s'/>", - new Table("%n ", "", "%n "), - "%n ", - "%n ", - "%n ", - new Table("%n ", "", "%n "), - " type='%s' variable_name='%s'", - "%n ", - new Table("%n ", "", "%n <:>%d"), - new Table("%n ", "", "%n "), - new Table("%n ", "", "%n "), - new Table("%n ", "", "%n "), - ClassPrinterImpl::escapeXml); - - public static final Format YAML = new Format('\'', false, true, "", ", ", - new Block(" - class name: '%s'%n version: '%d.%d'%n flags: %s%n superclass: '%s'%n interfaces: %s%n attributes: %s", "%n"), - new Block("%n constant pool:", ""), - "%n %d: [%s, '%s']", - "%n %d: [%s, {value index: %d, value: '%s'}]", - "%n %d: [%s, {name index: %d, name: '%s'}]", - "%n %d: [%s, {owner index: %d, name and type index: %d, owner: '%s', name: '%s', type: '%s'}]", - "%n %d: [%s, {name index: %d, type index: %d, name: '%s', type: '%s'}]", - "%n %d: [%s, {reference kind: '%s', reference index: %d, owner: '%s', name: '%s', type: '%s'}]", - "%n %d: [%s, {descriptor index: %d, descriptor: '%s'}]", - "%n %d: [%s, {bootstrap method handle index: %d, bootstrap method arguments indexes: %s, name and type index: '%d', name: '%s', type: '%s'}]", - "%n fields:", - new Block("%n - field name: '%s'%n flags: %s%n descriptor: '%s'%n attributes: %s", ""), - "%n methods:", - new Block("%n - method name: '%s'%n flags: %s%n descriptor: '%s'%n attributes: %s", ""), - "%n%s%s: %s", - "%n%s%s: '%s'", - "%n%sannotation default: '%s'", - new Table("%n%s%s annotations:", "", "%n%s - {class: '%s'%s}"), - new Table("%n%s%s type annotations:", "", "%n%s - {class: '%s', target type: '%s'%s}"), - "%n #%s type annotation: {class: '%s', target type: '%s'%s}", - new Table("%n%s%s parameter %d annotations:", "", "%n%s - {class: '%s'%s}"), - new Table(", values: {", "}", "'%s': '%s'"), - new Table("%n bootstrap methods: #[, , , , , ]", "", "%n - ['%s', '%s', '%s', '%s', %b, %b]"), - "%n%senclosing method: {class: '%s', method name: '%s', type: 's'}", - new Table("%n inner classes: #[, , , ]", "", "%n - ['%s', '%s', '%s', %s]"), - new Table("%n method parameters: #[, ]", "", "%n - ['%s', %s]"), - new Table("%n record components:", "", "%n - name: '%s'%n type: '%s'%n attributes: %s"), - "", - new Block("%n module:%n name: '%s'%n flags: %s%n version: '%s'%n uses: %s", ""), - new Table("%n requires:", "", "%n - { name: '%s', flags: %s, version: '%s' }"), - new Table("%n exports:", "", "%n - { package: '%s', flags: %s, to: %s }"), - new Table("%n opens:", "", "%n - { package: '%s', flags: %s, to: %s }"), - new Table("%n provides:", "", "%n - { class: '%s', with: %s }"), - "%n module packages: %s", - "%n module main class: '%s'", - "%n nest host: '%s'", - "%n nest members: %s", - new Block("%n code:%n max stack: %d%n max locals: %d%n attributes: %s", ""), - "%n %d: [%s]", - "%n %d: [%s, {slot: %d%s}]", - "%n %d: [%s, {slot: %d, const: %+d%s}]", - "%n %d: [%s, {owner: '%s', name: '%s', descriptor: '%s'}]", - "%n %d: [%s, {name: '%s', descriptor: '%s', bootstrap method kind: %s, owner: '%s', method name: '%s', invocation type: '%s'}]", - "%n %d: [%s, {target: %d}]", - "%n %d: [%s, {targets: %s}]", - "%n %d: [%s, {dimensions: %d, descriptor: '%s'}]", - "%n %d: [%s, {type: '%s'}]", - "%n %d: [%s, {constant value: '%s'}]", - new Table("%n exception handlers: #[, , , ]", "", "%n - [%d, %d, %d, %s]"), - "%n #try block start: {start: %d, end: %d, handler: %d, catch type: %s}", - "%n #try block end: {start: %d, end: %d, handler: %d, catch type: %s}", - "%n #exception handler start: {start: %d, end: %d, handler: %d, catch type: %s}", - new Table("%n local variables: #[, , '', ]", "", "%n - [%d, %d, %d, '%s', '%s']"), - ", type: '%s', variable name: '%s'", - "%n #stack map frame locals: %s, stack: %s", - new Table("%n stack map frames:", "", "%n %d: {locals: %s, stack: %s}"), - new Table("%n line numbers: #[, ]", "", "%n - [%d, %d]"), - new Table("%n character ranges: #[, , , , ]", "", "%n - [%d, %d, %d, '%s', '%s']"), - new Table("%n local variable types: #[, , '', ]", "", "%n - [%d, %d, %d, '%s', '%s']"), - ClassPrinterImpl::escapeYaml); + public enum Style { BLOCK, FLOW } - private static final char[] DIGITS = "0123456789ABCDEF".toCharArray(); + public record LeafNodeImpl(ConstantDesc name, ConstantDesc value) implements LeafNode { - private List quoteFlags(Set> flags) { - return flags.stream().map(f -> format.quoteFlagsAndAttrs ? format.quotes + f.name() + format.quotes : f.name()).toList(); + @Override + public Stream walk() { + return Stream.of(this); + } } - private String escape(String s) { - return format.escapeFunction.apply(s); - } + public static final class ListNodeImpl extends AbstractList implements ListNode { - private String typesToString(Stream strings) { - return strings.map(s -> format.quoteTypes ? format.quotes + s + format.quotes : s).collect(Collectors.joining(", ", "[", "]")); - } + private final Style style; + private final ConstantDesc name; + private final Node[] nodes; - private String attributeNames(List> attributes) { - return attributes.stream().map(a -> format.quoteFlagsAndAttrs ? format.quotes + a.attributeName() + format.quotes : a.attributeName()).collect(Collectors.joining(", ", "[", "]")); + 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; + } } - private String elementValueToString(AnnotationValue v) { - return switch (v) { - case OfConstant cv -> format.escapeFunction().apply(v.tag() == 'Z' ? String.valueOf((int)cv.constantValue() != 0) : String.valueOf(cv.constantValue())); - case OfClass clv -> clv.className().stringValue(); - case OfEnum ev -> ev.className().stringValue() + "." + ev.constantName().stringValue(); - case OfAnnotation av -> v.tag() + av.annotation().className().stringValue(); - case OfArray av -> av.values().stream().map(ev -> elementValueToString(ev)).collect(Collectors.joining(", ", "[", "]")); - }; + 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 String elementValuePairsToString(List evps) { - return evps.isEmpty() ? "" : evps.stream().map(evp -> format.annotationValuePair.element.formatted(evp.name().stringValue(), elementValueToString(evp.value()))) - .collect(Collectors.joining(format.inlineDelimiter, format.annotationValuePair.header, format.annotationValuePair.footer)); + private static Node leaf(ConstantDesc name, ConstantDesc value) { + return new LeafNodeImpl(name, value); } - private static String escapeXml(String s) { - 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 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 String escapeYaml(String s) { - var sb = new StringBuilder(s.length() << 1); - s.chars().forEach(c -> { - switch (c) { - case '\'' -> sb.append("''"); - default -> escape(c, sb); - }}); - return sb.toString(); + private static Node list(ConstantDesc listName, ConstantDesc itemsName, Stream values) { + return new ListNodeImpl(FLOW, listName, values.map(v -> leaf(itemsName, v))); } - private static String escapeJson(String s) { - var sb = new StringBuilder(s.length() << 1); - s.chars().forEach(c -> escape(c, sb)); - return sb.toString(); + 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('\\'); @@ -378,234 +255,744 @@ private static void escape(int c, StringBuilder sb) { } } - private static String formatDescriptor(String desc) { - int i = desc.lastIndexOf('['); - if (i >= 0) desc = desc.substring(i + 1); - desc = switch (desc) { - case "I", "B", "Z", "F", "S", "C", "J", "D" -> TypeKind.fromDescriptor(desc).typeName(); - default -> desc = Util.descriptorToClass(desc); - }; - if (i >= 0) { - var ret = new StringBuilder(desc.length() + 2*i + 2).append(desc); - while (i-- >= 0) ret.append('[').append(']'); - return ret.toString(); + 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); + } + } + } + } } - return desc; + out.accept(""); } - private static Stream convertVTIs(List vtis) { + 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(int bci, List vtis) { return vtis.stream().mapMulti((vti, ret) -> { - var s = formatDescriptor(vti.toString()); - ret.accept(s); - if (vti.type() == StackMapTableAttribute.VerificationType.ITEM_DOUBLE || vti.type() == StackMapTableAttribute.VerificationType.ITEM_LONG) - ret.accept(s + "2"); + switch (vti) { + case SimpleVerificationTypeInfo s -> { + switch (s.type()) { + 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 @" + (bci + u.offset())); + } }); } - private final Format format; - private final VerbosityLevel verbosity; - private final Consumer out; + 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(com, verbosity); + }; + } - public ClassPrinterImpl(Format format, VerbosityLevel verbosity, Consumer out) { - this.format = format; - this.verbosity = verbosity; - this.out = out; + 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 -> + (Node)methodToTree(mm, verbosity)))); } - @Override - public void printClass(ClassModel clm) { - out.accept(format.classForm.header().formatted(clm.thisClass().asInternalName(), clm.majorVersion(), clm.minorVersion(), quoteFlags(clm.flags().flags()), clm.superclass().map(ClassEntry::asInternalName).orElse(null), typesToString(clm.interfaces().stream().map(ClassEntry::asInternalName)), attributeNames(clm.attributes()))); - if (verbosity == VerbosityLevel.TRACE_ALL) { - out.accept(format.constantPool.header.formatted()); - for (int i = 1; i < clm.constantPool().entryCount();) { - if (i > 1) out.accept(format.mandatoryDelimiter); - var e = clm.constantPool().entryByIndex(i); - printCPEntry(e); + 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.poolEntries(); } - out.accept(format.constantPool.footer.formatted()); - } - if (verbosity != VerbosityLevel.MEMBERS_ONLY) printAttributes(" ", clm.attributes()); - out.accept(format.fieldsHeader.formatted()); - boolean first = true; - for (var f : clm.fields()) { - if (first) first = false; else out.accept(format.mandatoryDelimiter); - out.accept(format.field.header().formatted(f.fieldName().stringValue(), quoteFlags(f.flags().flags()), f.fieldType().stringValue(), attributeNames(f.attributes()))); - if (verbosity != VerbosityLevel.MEMBERS_ONLY) printAttributes(" ", f.attributes()); - out.accept(format.field.footer().formatted()); - } - out.accept(format.methodsHeader.formatted()); - first = true; - for (var m : clm.methods()) { - if (first) first = false; else out.accept(format.mandatoryDelimiter); - printMethod(m); - } - out.accept(format.classForm.footer().formatted()); - } - - - public static String tagName(byte tag) { - return switch (tag) { - case TAG_UTF8 -> "CONSTANT_Utf8"; - case TAG_INTEGER -> "CONSTANT_Integer"; - case TAG_FLOAT -> "CONSTANT_Float"; - case TAG_LONG -> "CONSTANT_Long"; - case TAG_DOUBLE -> "CONSTANT_Double"; - case TAG_CLASS -> "CONSTANT_Class"; - case TAG_STRING -> "CONSTANT_String"; - case TAG_FIELDREF -> "CONSTANT_Fieldref"; - case TAG_METHODREF -> "CONSTANT_Methodref"; - case TAG_INTERFACEMETHODREF -> "CONSTANT_InterfaceMethodref"; - case TAG_NAMEANDTYPE -> "CONSTANT_NameAndType"; - case TAG_METHODHANDLE -> "CONSTANT_MethodHandle"; - case TAG_METHODTYPE -> "CONSTANT_MethodType"; - case TAG_CONSTANTDYNAMIC -> "CONSTANT_Dynamic"; - case TAG_INVOKEDYNAMIC -> "CONSTANT_InvokeDynamic"; - case TAG_MODULE -> "CONSTANT_Module"; - case TAG_PACKAGE -> "CONSTANT_Package"; - default -> null; - }; + return new Node[]{cpNode}; + } else { + return new Node[0]; + } } - private void printCPEntry(PoolEntry e) { - out.accept(switch (e) { - case Utf8Entry ve -> printValueEntry(ve); - case IntegerEntry ve -> printValueEntry(ve); - case FloatEntry ve -> printValueEntry(ve); - case LongEntry ve -> printValueEntry(ve); - case DoubleEntry ve -> printValueEntry(ve); - case ClassEntry ce -> printNamedEntry(e, ce.name()); - case StringEntry se -> format.stringEntry.formatted(e.index(), tagName(e.tag()), se.utf8().index(), escape(se.stringValue())); - case MemberRefEntry mre -> format.memberEntry.formatted(e.index(), tagName(e.tag()), mre.owner().index(), mre.nameAndType().index(), escape(mre.owner().asInternalName()), escape(mre.name().stringValue()), escape(mre.type().stringValue())); - case NameAndTypeEntry nte -> format.nameAndTypeEntry.formatted(e.index(), tagName(e.tag()), nte.name().index(), nte.type().index(), escape(nte.name().stringValue()), escape(nte.type().stringValue())); - case MethodHandleEntry mhe -> format.methodHandleEntry.formatted(e.index(), tagName(e.tag()), DirectMethodHandleDesc.Kind.valueOf(mhe.kind()), mhe.reference().index(), escape(mhe.reference().owner().asInternalName()), escape(mhe.reference().name().stringValue()), escape(mhe.reference().type().stringValue())); - case MethodTypeEntry mte -> format.methodTypeEntry.formatted(e.index(), tagName(e.tag()), mte.descriptor().index(), escape(mte.descriptor().stringValue())); - case ConstantDynamicEntry cde -> printDynamicEntry(cde); - case InvokeDynamicEntry ide -> printDynamicEntry(ide); - case ModuleEntry me -> printNamedEntry(e, me.name()); - case PackageEntry pe -> printNamedEntry(e, pe.name()); - }); + private static Node frameToTree(ConstantDesc name, StackMapFrame f) { + return new MapNodeImpl(FLOW, name).with( + list("locals", "item", convertVTIs(f.absoluteOffset(), f.effectiveLocals())), + list("stack", "item", convertVTIs(f.absoluteOffset(), f.effectiveStack()))); } - private String printValueEntry(AnnotationConstantValueEntry e) { - return format.valueEntry.formatted(e.index(), tagName(e.tag()), escape(String.valueOf(e.constantValue()))); + 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)); } - private String printNamedEntry(PoolEntry e, Utf8Entry name) { - return format.namedEntry.formatted(e.index(), tagName(e.tag()), name.index(), escape(name.stringValue())); + 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(m.code().orElse(null), verbosity)); } - private String printDynamicEntry(DynamicConstantPoolEntry dcpe) { - return format.dynamicEntry.formatted(dcpe.index(), tagName(dcpe.tag()), dcpe.bootstrap().bootstrapMethod().index(), - dcpe.bootstrap().arguments().stream().map(en -> en.index()).toList(), - dcpe.nameAndType().index(), escape(dcpe.name().stringValue()), escape(dcpe.type().stringValue())); + private static MapNode codeToTree(CodeModel com, Verbosity verbosity) { + if (verbosity != Verbosity.MEMBERS_ONLY && com != null) { + var codeNode = new MapNodeImpl(BLOCK, "code"); + codeNode.with(leaf("max stack", ((CodeAttribute)com).maxStack())); + codeNode.with(leaf("max locals", ((CodeAttribute)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(smf.absoluteOffset(), smf)); + } + } else if (verbosity == Verbosity.TRACE_ALL) 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(frameToTree("//stack map frame @0", StackMapDecoder.initFrame(com.parent().get()))); + } + 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 void printAttributes(String indentSpace, List> attributes) { - for (var attr : attributes) { + 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 -> - printTable(format.bootstrapMethods, bma.bootstrapMethods(), bm -> { + nodes.add(new ListNodeImpl(BLOCK, "bootstrap methods", bma.bootstrapMethods().stream().map( + bm -> { var mh = bm.bootstrapMethod(); var mref = mh.reference(); - return new Object[] {DirectMethodHandleDesc.Kind.valueOf(mh.kind(), mref.isInterface()), mref.owner().asInternalName(), mref.nameAndType().name().stringValue(), mref.nameAndType().type().stringValue(), mref.isInterface(), mref.isMethod()}; - }); + return map("bm", + "kind", DirectMethodHandleDesc.Kind.valueOf(mh.kind(), mref.isInterface()).name(), + "owner", mref.owner().name().stringValue(), + "name", mref.nameAndType().name().stringValue(), + "type", mref.nameAndType().type().stringValue(), + "is interface", String.valueOf(mref.isInterface()), + "is method", String.valueOf(mref.isMethod())); + }))); case ConstantValueAttribute cva -> - out.accept(format.simpleQuotedAttr.formatted(indentSpace, "value", escape(cva.constant().constantValue().toString()))); + nodes.add(leaf("constant value", cva.constant().constantValue())); case NestHostAttribute nha -> - out.accept(format.nestHost.formatted(nha.nestHost().asInternalName())); + nodes.add(leaf("nest host", nha.nestHost().name().stringValue())); case NestMembersAttribute nma -> - out.accept(format.nestMembers.formatted(typesToString(nma.nestMembers().stream().map(mp -> mp.asInternalName())))); + nodes.add(list("nest members", "member", nma.nestMembers().stream().map(mp -> mp.name().stringValue()))); case PermittedSubclassesAttribute psa -> - out.accept(format.simpleAttr.formatted(indentSpace, "subclasses", typesToString(psa.permittedSubclasses().stream().map(e -> e.asInternalName())))); + nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream().map(e -> e.name().stringValue()))); default -> {} } - if (verbosity == VerbosityLevel.TRACE_ALL) switch (attr) { + if (verbosity == Verbosity.TRACE_ALL) switch (attr) { case EnclosingMethodAttribute ema -> - out.accept(format.enclosingMethod.formatted(indentSpace, ema.enclosingClass().asInternalName(), ema.enclosingMethod().map(e -> escape(e.name().stringValue())).orElse(null), ema.enclosingMethod().map(e -> escape(e.type().stringValue())).orElse(null))); + 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 -> - out.accept(format.simpleAttr.formatted(indentSpace, "exceptions", typesToString(exa.exceptions().stream().map(e -> e.asInternalName())))); + nodes.add(list("excceptions", "exc", exa.exceptions().stream().map(e -> e.name().stringValue()))); case InnerClassesAttribute ica -> - printTable(format.innerClasses, ica.classes(), ic -> new Object[] {ic.innerClass().asInternalName(), ic.outerClass().map(e -> escape(e.asInternalName())).orElse(null), ic.innerName().map(e -> escape(e.stringValue())).orElse(null), quoteFlags(ic.flags())}); - case MethodParametersAttribute mpa -> - printTable(format.methodParameters, mpa.parameters(), mp -> new Object[]{mp.name().map(e -> escape(e.stringValue())).orElse(null), quoteFlags(mp.flags())}); - case ModuleAttribute ma -> { - out.accept(format.module.header.formatted(ma.moduleName().name().stringValue(), quoteFlags(ma.moduleFlags()), ma.moduleVersion().map(Utf8Entry::stringValue).orElse(""), typesToString(ma.uses().stream().map(ce -> ce.asInternalName())))); - printTable(format.requires, ma.requires(), req -> new Object[] {req.requires().name().stringValue(), quoteFlags(req.requiresFlags()), req.requiresVersion().map(Utf8Entry::stringValue).orElse(null)}); - printTable(format.exports, ma.exports(), exp -> new Object[] {exp.exportedPackage().name().stringValue(), quoteFlags(exp.exportsFlags()), typesToString(exp.exportsTo().stream().map(me -> me.name().stringValue()))}); - printTable(format.opens, ma.opens(), open -> new Object[] {open.openedPackage().name().stringValue(), quoteFlags(open.opensFlags()), typesToString(open.opensTo().stream().map(me -> me.name().stringValue()))}); - printTable(format.provides, ma.provides(), provide -> new Object[] {provide.provides().asInternalName(), typesToString(provide.providesWith().stream().map(me -> me.asInternalName()))}); - out.accept(format.module.footer.formatted()); + 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 -> - out.accept(format.modulePackages.formatted(typesToString(mopa.packages().stream().map(mp -> mp.name().stringValue())))); + nodes.add(list("module packages", "subclass", mopa.packages().stream().map(mp -> mp.asSymbol().packageName()))); case ModuleMainClassAttribute mmca -> - out.accept(format.moduleMain.formatted(mmca.mainClass().asInternalName())); + nodes.add(leaf("module main class", mmca.mainClass().name().stringValue())); case RecordAttribute ra -> - printTable(format.recordComponents, ra.components(), rc -> new Object[]{rc.name().stringValue(), rc.descriptor().stringValue(), attributeNames(rc.attributes())}, rc -> { - printAttributes(" ", rc.attributes()); - out.accept(format.recordComponentTail); - }); + 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 -> - out.accept(format.annotationDefault.formatted(indentSpace, elementValueToString(ada.defaultValue()))); - case RuntimeInvisibleAnnotationsAttribute riaa -> - printTable(format.annotations, riaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "invisible"); - case RuntimeVisibleAnnotationsAttribute rvaa -> - printTable(format.annotations, rvaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "visible"); - case RuntimeInvisibleParameterAnnotationsAttribute ripaa -> { - int i = 0; - for (var pa : ripaa.parameterAnnotations()) { - i++; - if (!pa.isEmpty()) printTable(format.parameterAnnotations, pa, a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "invisible", i); - } - } - case RuntimeVisibleParameterAnnotationsAttribute rvpaa -> { - int i = 0; - for (var pa : rvpaa.parameterAnnotations()) { - i++; - if (!pa.isEmpty()) printTable(format.parameterAnnotations, pa, a -> new Object[]{indentSpace, a.className().stringValue(), elementValuePairsToString(a.elements())}, indentSpace, "visible", i); - } - } - case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> - printTable(format.typeAnnotations, ritaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements())}, indentSpace, "invisible"); - case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> - printTable(format.typeAnnotations, rvtaa.annotations(), a -> new Object[]{indentSpace, a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements())}, indentSpace, "visible"); + 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 -> - out.accept(format.simpleQuotedAttr.formatted(indentSpace, "signature", escape(sa.signature().stringValue()))); + nodes.add(leaf("signature", sa.signature().stringValue())); case SourceFileAttribute sfa -> - out.accept(format.simpleQuotedAttr.formatted(indentSpace, "source", escape(sfa.sourceFile().stringValue()))); + nodes.add(leaf("source file", sfa.sourceFile().stringValue())); default -> {} } } + return nodes.toArray(Node[]::new); } - private String findLocal(List locals, int slot, int bci) { + 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 format.localVariableInline.formatted(formatDescriptor(l.type().stringValue()), l.name().stringValue()); + return leafs("type", l.type().stringValue(), + "variable name", l.name().stringValue()); } } } - return ""; - } - - private void printTable(Table tfmt, Collection elements, Function arguments, Object ... headerArgs) { - printTable(tfmt, elements, arguments, null, headerArgs); - } - - private void printTable(Table tfmt, Collection elements, Function arguments, Consumer afterElement, Object ... headerAndFooterArgs) { - out.accept(tfmt.header().formatted(headerAndFooterArgs)); - boolean first = true; - for (var e : elements) { - if (first) first = false; else out.accept(format.mandatoryDelimiter); - out.accept(tfmt.element().formatted(arguments.apply(e))); - if (afterElement != null) afterElement.accept(e); - } - out.accept(tfmt.footer().formatted(headerAndFooterArgs)); + return new Node[0]; } - private void forEachOffset(TypeAnnotation ta, LabelResolver lr, BiConsumer consumer) { + private static void forEachOffset(TypeAnnotation ta, LabelResolver 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); @@ -613,113 +1000,4 @@ private void forEachOffset(TypeAnnotation ta, LabelResolver lr, BiConsumer {} } } - - @Override - public void printMethod(MethodModel m) { - out.accept(format.method.header().formatted(escape(m.methodName().stringValue()), quoteFlags(m.flags().flags()), m.methodType().stringValue(), attributeNames(m.attributes()))); - if (verbosity != VerbosityLevel.MEMBERS_ONLY) { - printAttributes(" ", m.attributes()); - m.code().ifPresent(com -> { - out.accept(format.code.header().formatted(((CodeAttribute)com).maxStack(), ((CodeAttribute)com).maxLocals(), attributeNames(com.attributes()))); - var stackMap = new LinkedHashMap(); - var visibleTypeAnnos = new LinkedHashMap(); - var invisibleTypeAnnos = new LinkedHashMap(); - List locals = List.of(); - int lnc =0, lvc = 0, lvtc = 0; - for (var attr : com.attributes()) { - if (attr instanceof StackMapTableAttribute smta) { - for (var smf : smta.entries()) { - stackMap.put(smf.absoluteOffset(), smf); - } - printTable(format.stackMapTable, stackMap.values(), f -> new Object[]{f.absoluteOffset(), typesToString(convertVTIs(f.effectiveLocals())), typesToString(convertVTIs(f.effectiveStack()))}); - } else if (verbosity == VerbosityLevel.TRACE_ALL) switch (attr) { - case LocalVariableTableAttribute lvta -> - printTable(format.localVariableTable, locals = lvta.localVariables(), lv -> new Object[]{lv.startPc(), lv.startPc() + lv.length(), lv.slot(), lv.name().stringValue(), formatDescriptor(lv.type().stringValue())}, ++lvc < 2 ? "" : " #" + lvc); - case LineNumberTableAttribute lnta -> - printTable(format.lineNumberTable, lnta.lineNumbers(), lni -> new Object[] {lni.startPc(), lni.lineNumber()}, ++lnc < 2 ? "" : " #"+lnc); - case CharacterRangeTableAttribute crta -> - printTable(format.characterRangeTable, crta.characterRangeTable(), chr -> new Object[] {chr.startPc(), chr.endPc(), chr.characterRangeStart(), chr.characterRangeEnd(), chr.flags()}); - case LocalVariableTypeTableAttribute lvtta -> - printTable(format.localVariableTypeTable, lvtta.localVariableTypes(), lvt -> new Object[]{lvt.startPc(), lvt.startPc() + lvt.length(), lvt.slot(), lvt.name().stringValue(), formatDescriptor(lvt.signature().stringValue())}, ++lvtc < 2 ? "" : " #" + lvtc); - case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> - rvtaa.annotations().forEach(a -> forEachOffset(a, com, visibleTypeAnnos::put)); - case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> - ritaa.annotations().forEach(a -> forEachOffset(a, com, invisibleTypeAnnos::put)); - case Object o -> {} - } - } - printAttributes(" ", com.attributes()); - stackMap.putIfAbsent(0, StackMapDecoder.initFrame(m)); - 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.asInternalName()).orElse(null))).toList(); - int bci = 0; - for (var coe : com) { - if (coe instanceof Instruction ins) { - var frame = stackMap.get(bci); - if (frame != null) { - out.accept(format.frameInline.formatted(typesToString(convertVTIs(frame.effectiveLocals())), typesToString(convertVTIs(frame.effectiveStack())))); - } - var a = invisibleTypeAnnos.get(bci); - if (a != null) { - out.accept(format.typeAnnotationInline.formatted("invisible", a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements()))); - } - a = visibleTypeAnnos.get(bci); - if (a != null) { - out.accept(format.typeAnnotationInline.formatted("visible", a.className().stringValue(), a.targetInfo().targetType(), elementValuePairsToString(a.elements()))); - } - for (var exc : excHandlers) { - if (exc.start() == bci) { - out.accept(format.tryStartInline.formatted(exc.start(), exc.end(), exc.handler(), exc.catchType())); - } - if (exc.end() == bci) { - out.accept(format.tryEndInline.formatted(exc.start(), exc.end(), exc.handler(), exc.catchType())); - } - if (exc.handler() == bci) { - out.accept(format.handlerInline.formatted(exc.start(), exc.end(), exc.handler(), exc.catchType())); - } - } - out.accept(format.mandatoryDelimiter); - switch (coe) { - case IncrementInstruction inc -> - out.accept(format.incInstruction.formatted(bci, ins.opcode().name(), inc.slot(), inc.constant(), findLocal(locals, inc.slot(), bci))); - case LoadInstruction lv -> - out.accept(format.localVariableInstruction.formatted(bci, ins.opcode().name(), lv.slot(), findLocal(locals, lv.slot(), bci))); - case StoreInstruction lv -> - out.accept(format.localVariableInstruction.formatted(bci, ins.opcode().name(), lv.slot(), findLocal(locals, lv.slot(), bci))); - case FieldInstruction fa -> - out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), fa.owner().asInternalName(), escape(fa.name().stringValue()), fa.type().stringValue())); - case InvokeInstruction inv -> - out.accept(format.memberInstruction.formatted(bci, ins.opcode().name(), inv.owner().asInternalName(), escape(inv.name().stringValue()), inv.type().stringValue())); - case InvokeDynamicInstruction invd -> { - var bm = invd.bootstrapMethod(); - out.accept(format.invokeDynamicInstruction.formatted(bci, ins.opcode().name(), invd.name().stringValue(), invd.type().stringValue(), bm.kind(), bm.owner().descriptorString(), escape(bm.methodName()), bm.invocationType().descriptorString())); - } - case NewObjectInstruction newo -> - out.accept(format.typeInstruction.formatted(bci, ins.opcode().name(), newo.className().asInternalName())); - case NewPrimitiveArrayInstruction newa -> out.accept(format.newArrayInstruction.formatted(bci, ins.opcode().name(), 1, newa.typeKind().descriptor())); - case NewReferenceArrayInstruction newa -> out.accept(format.newArrayInstruction.formatted(bci, ins.opcode().name(), 1, newa.componentType().asInternalName())); - case NewMultiArrayInstruction newa -> out.accept(format.newArrayInstruction.formatted(bci, ins.opcode().name(), newa.dimensions(), newa.arrayType().asInternalName())); - case TypeCheckInstruction tch -> - out.accept(format.typeInstruction.formatted(bci, ins.opcode().name(), tch.type().asInternalName())); - case ConstantInstruction cons -> - out.accept(format.constantInstruction.formatted(bci, ins.opcode().name(), escape(String.valueOf(cons.constantValue())))); - case BranchInstruction br -> - out.accept(format.branchInstruction.formatted(bci, ins.opcode().name(), com.labelToBci(br.target()))); - case LookupSwitchInstruction ls -> - out.accept(format.switchInstruction.formatted(bci, ins.opcode().name(), Stream.concat(Stream.of(ls.defaultTarget()).map(com::labelToBci), ls.cases().stream().map(c -> com.labelToBci(c.target()))).toList())); - case TableSwitchInstruction ts -> - out.accept(format.switchInstruction.formatted(bci, ins.opcode().name(), Stream.concat(Stream.of(ts.defaultTarget()).map(com::labelToBci), ts.cases().stream().map(c -> com.labelToBci(c.target()))).toList())); - default -> - out.accept(format.plainInstruction.formatted(bci, ins.opcode().name())); - } - bci += ins.sizeInBytes(); - } - } - out.accept(format.code.footer().formatted()); - if (excHandlers.size() > 0) { - printTable(format.exceptionHandlers, excHandlers, exc -> new Object[]{exc.start(), exc.end(), exc.handler(), exc.catchType()}); - } - }); - } - out.accept(format.method.footer().formatted()); - } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java index da7687c6d464a..e46a1d2f9b565 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java @@ -30,7 +30,7 @@ import java.util.function.Consumer; import jdk.classfile.ClassHierarchyResolver; import jdk.classfile.ClassModel; -import jdk.classfile.util.ClassPrinter; +import jdk.classfile.ClassPrinter; import jdk.classfile.Classfile; import jdk.classfile.impl.ClassHierarchyImpl; import jdk.classfile.impl.RawBytecodeHelper; @@ -1826,7 +1826,7 @@ void verify_return_value(VerificationType return_type, VerificationType type, in } private void dumpMethod() { - if (_logger != null) ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, _logger).printMethod(_method.m); + if (_logger != null) ClassPrinter.toTree(_method.m, ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES).toYaml(_logger); } void verifyError(String msg) { diff --git a/src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java b/src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java deleted file mode 100644 index 6f1b2fe8310d2..0000000000000 --- a/src/java.base/share/classes/jdk/classfile/util/ClassPrinter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact 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.classfile.util; - -import java.util.function.Consumer; - -import jdk.classfile.ClassModel; -import jdk.classfile.MethodModel; -import jdk.classfile.impl.ClassPrinterImpl; - -/** - * - */ -public sealed interface ClassPrinter permits ClassPrinterImpl { - - public enum VerbosityLevel {MEMBERS_ONLY, CRITICAL_ATTRIBUTES, TRACE_ALL} - - void printClass(ClassModel classModel); - - void printMethod(MethodModel methodModel); - - static ClassPrinter jsonPrinter(VerbosityLevel verbosity, Consumer output) { - return new ClassPrinterImpl(ClassPrinterImpl.JSON, verbosity, output); - } - - static ClassPrinter xmlPrinter(VerbosityLevel verbosity, Consumer output) { - return new ClassPrinterImpl(ClassPrinterImpl.XML, verbosity, output); - } - - static ClassPrinter yamlPrinter(VerbosityLevel verbosity, Consumer output) { - return new ClassPrinterImpl(ClassPrinterImpl.YAML, verbosity, output); - } -} diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index 10d11540fb678..fca98d4660a4d 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -61,7 +61,7 @@ import java.lang.reflect.AccessFlag; import jdk.classfile.transforms.LabelsRemapper; import jdk.classfile.jdktypes.ModuleDesc; -import jdk.classfile.util.ClassPrinter; +import jdk.classfile.ClassPrinter; import static java.lang.annotation.ElementType.*; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -184,32 +184,25 @@ public void testRemapDetails() throws Exception { Rec.class.getResourceAsStream(Rec.class.getName() + ".class") .readAllBytes()))); var sb = new StringBuilder(); - ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, sb::append).printClass(remapped); + ClassPrinter.toYaml(remapped, ClassPrinter.Verbosity.TRACE_ALL, sb::append); String out = sb.toString(); assertContains(out, - "type: 'LAdvancedTransformationsTest$Bar;'", - "['AdvancedTransformationsTest$Bar', 'AdvancedTransformationsTest', 'Foo', [PUBLIC, STATIC]]", - "descriptor: 'LAdvancedTransformationsTest$Bar;'", - "descriptor: '(LAdvancedTransformationsTest$Bar;)V'", - "[NEW, {type: 'AdvancedTransformationsTest$Bar'}]", - "[INVOKESPECIAL, {owner: 'AdvancedTransformationsTest$Bar', name: '', descriptor: '()V'}]", - "[PUTFIELD, {owner: 'AdvancedTransformationsTest$Rec', name: 'foo', descriptor: 'LAdvancedTransformationsTest$Bar;'}]", - "[ANEWARRAY, {dimensions: 1, descriptor: 'AdvancedTransformationsTest$Bar'}]", - "[PUTSTATIC, {owner: 'AdvancedTransformationsTest$Bar', name: 'fooField', descriptor: 'LAdvancedTransformationsTest$Bar;'}]", - "[INVOKESTATIC, {owner: 'AdvancedTransformationsTest$Bar', name: 'fooMethod', descriptor: '(LAdvancedTransformationsTest$Bar;)LAdvancedTransformationsTest$Bar;'}]", - "descriptor: '()LAdvancedTransformationsTest$Bar;'", - "[GETFIELD, {owner: 'AdvancedTransformationsTest$Rec', name: 'foo', descriptor: 'LAdvancedTransformationsTest$Bar;'}]", - "{class: 'LAdvancedTransformationsTest$BarAnno;'}", - "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'FIELD'}", - "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'METHOD_RETURN'}", - "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'NEW'}", - "{class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'LOCAL_VARIABLE'}", - "#visible type annotation: {class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'NEW'}", - "#visible type annotation: {class: 'LAdvancedTransformationsTest$BarAnno;', target type: 'LOCAL_VARIABLE'}"); + "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); diff --git a/test/jdk/jdk/classfile/ClassPrinterTest.java b/test/jdk/jdk/classfile/ClassPrinterTest.java index b75cfe1cadbce..e7975b4adc234 100644 --- a/test/jdk/jdk/classfile/ClassPrinterTest.java +++ b/test/jdk/jdk/classfile/ClassPrinterTest.java @@ -32,10 +32,10 @@ import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; -import jdk.classfile.ClassModel; -import jdk.classfile.Classfile; -import jdk.classfile.attribute.SourceFileAttribute; -import jdk.classfile.util.ClassPrinter; +import java.util.List; +import java.util.Optional; +import jdk.classfile.*; +import jdk.classfile.attribute.*; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -48,193 +48,462 @@ ClassModel getClassModel() { .with(SourceFileAttribute.of("Foo.java")) .withSuperclass(ClassDesc.of("Boo")) .withInterfaceSymbols(ClassDesc.of("Phee"), ClassDesc.of("Phoo")) - .withField("f", ConstantDescs.CD_String, Classfile.ACC_PRIVATE) - .withMethod("m", MethodTypeDesc.of(ConstantDescs.CD_Void, ConstantDescs.CD_boolean, ConstantDescs.CD_Throwable), Classfile.ACC_PROTECTED, mb -> mb.withCode(cob -> { - cob.iload(1); - cob.ifThen(thb -> thb.aload(2).athrow()); - cob.return_(); - })) - )); + .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 public void testPrintYamlTraceAll() throws IOException { var out = new StringBuilder(); - ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); + ClassPrinter.toYaml(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL, out::append); assertOut(out, """ - - class name: 'Foo' - version: '61.0' + - class name: Foo + version: 61.0 flags: [PUBLIC] - superclass: 'Boo' - interfaces: ['Phee', 'Phoo'] - attributes: [SourceFile] + superclass: Boo + interfaces: [Phee, Phoo] + attributes: [SourceFile, InnerClasses, EnclosingMethod, Synthetic, Signature, Deprecated, NestHost, NestMembers, Record, RuntimeInvisibleAnnotations, PermittedSubclasses] constant pool: - 1: [CONSTANT_Utf8, 'Foo'] - 2: [CONSTANT_Class, {name index: 1, name: 'Foo'}] - 3: [CONSTANT_Utf8, 'Boo'] - 4: [CONSTANT_Class, {name index: 3, name: 'Boo'}] - 5: [CONSTANT_Utf8, 'f'] - 6: [CONSTANT_Utf8, 'Ljava/lang/String;'] - 7: [CONSTANT_Utf8, 'm'] - 8: [CONSTANT_Utf8, '(ZLjava/lang/Throwable;)Ljava/lang/Void;'] - 9: [CONSTANT_Utf8, 'Phee'] - 10: [CONSTANT_Class, {name index: 9, name: 'Phee'}] - 11: [CONSTANT_Utf8, 'Phoo'] - 12: [CONSTANT_Class, {name index: 11, name: 'Phoo'}] - 13: [CONSTANT_Utf8, 'Code'] - 14: [CONSTANT_Utf8, 'StackMapTable'] - 15: [CONSTANT_Utf8, 'SourceFile'] - 16: [CONSTANT_Utf8, 'Foo.java'] - source: 'Foo.java' + 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' + - field name: f flags: [PRIVATE] - descriptor: 'Ljava/lang/String;' - attributes: [] + 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' + - method name: m flags: [PROTECTED] - descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' - attributes: [Code] + 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: [StackMapTable] + 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: []} - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 0: [ILOAD_1, {slot: 1}] - 1: [IFEQ, {target: 6}] - 4: [ALOAD_2, {slot: 2}] - 5: [ATHROW] - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 6: [RETURN] + 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 public void testPrintYamlCriticalAttributes() throws IOException { var out = new StringBuilder(); - ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); + ClassPrinter.toYaml(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES, out::append); assertOut(out, """ - - class name: 'Foo' - version: '61.0' + - class name: Foo + version: 61.0 flags: [PUBLIC] - superclass: 'Boo' - interfaces: ['Phee', 'Phoo'] - attributes: [SourceFile] + 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' + - field name: f flags: [PRIVATE] - descriptor: 'Ljava/lang/String;' - attributes: [] + field type: Ljava/lang/String; + attributes: [RuntimeVisibleAnnotations] methods: - - method name: 'm' + - method name: m flags: [PROTECTED] - descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' - attributes: [Code] + method type: (ZLjava/lang/Throwable;)Ljava/lang/Void; + attributes: [AnnotationDefault, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, Exceptions, Code] code: max stack: 1 max locals: 3 - attributes: [StackMapTable] + attributes: [RuntimeInvisibleTypeAnnotations, RuntimeVisibleTypeAnnotations, LocalVariableTable, LocalVariableTypeTable, LineNumberTable, StackMapTable] stack map frames: - 6: {locals: ['Foo', 'int', 'java/lang/Throwable'], stack: []} - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 0: [ILOAD_1, {slot: 1}] - 1: [IFEQ, {target: 6}] - 4: [ALOAD_2, {slot: 2}] - 5: [ATHROW] - #stack map frame locals: ['Foo', 'int', 'java/lang/Throwable'], stack: [] - 6: [RETURN] + 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 public void testPrintYamlMembersOnly() throws IOException { var out = new StringBuilder(); - ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); + ClassPrinter.toYaml(getClassModel(), ClassPrinter.Verbosity.MEMBERS_ONLY, out::append); assertOut(out, """ - - class name: 'Foo' - version: '61.0' + - class name: Foo + version: 61.0 flags: [PUBLIC] - superclass: 'Boo' - interfaces: ['Phee', 'Phoo'] - attributes: [SourceFile] + superclass: Boo + interfaces: [Phee, Phoo] + attributes: [SourceFile, InnerClasses, EnclosingMethod, Synthetic, Signature, Deprecated, NestHost, NestMembers, Record, RuntimeInvisibleAnnotations, PermittedSubclasses] fields: - - field name: 'f' + - field name: f flags: [PRIVATE] - descriptor: 'Ljava/lang/String;' - attributes: [] + field type: Ljava/lang/String; + attributes: [RuntimeVisibleAnnotations] methods: - - method name: 'm' + - method name: m flags: [PROTECTED] - descriptor: '(ZLjava/lang/Throwable;)Ljava/lang/Void;' - attributes: [Code] + method type: (ZLjava/lang/Throwable;)Ljava/lang/Void; + attributes: [AnnotationDefault, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, Exceptions, Code] """); } @Test public void testPrintJsonTraceAll() throws IOException { var out = new StringBuilder(); - ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); + ClassPrinter.toJson(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL, out::append); assertOut(out, """ - { "class name": "Foo", + { "class name": "Foo", "version": "61.0", "flags": ["PUBLIC"], "superclass": "Boo", "interfaces": ["Phee", "Phoo"], - "attributes": ["SourceFile"], + "attributes": ["SourceFile", "InnerClasses", "EnclosingMethod", "Synthetic", "Signature", "Deprecated", "NestHost", "NestMembers", "Record", "RuntimeInvisibleAnnotations", "PermittedSubclasses"], "constant pool": { - "1": ["CONSTANT_Utf8", "Foo"], - "2": ["CONSTANT_Class", { "name index:": 1, "name:": "Foo" }], - "3": ["CONSTANT_Utf8", "Boo"], - "4": ["CONSTANT_Class", { "name index:": 3, "name:": "Boo" }], - "5": ["CONSTANT_Utf8", "f"], - "6": ["CONSTANT_Utf8", "Ljava/lang/String;"], - "7": ["CONSTANT_Utf8", "m"], - "8": ["CONSTANT_Utf8", "(ZLjava/lang/Throwable;)Ljava/lang/Void;"], - "9": ["CONSTANT_Utf8", "Phee"], - "10": ["CONSTANT_Class", { "name index:": 9, "name:": "Phee" }], - "11": ["CONSTANT_Utf8", "Phoo"], - "12": ["CONSTANT_Class", { "name index:": 11, "name:": "Phoo" }], - "13": ["CONSTANT_Utf8", "Code"], - "14": ["CONSTANT_Utf8", "StackMapTable"], - "15": ["CONSTANT_Utf8", "SourceFile"], - "16": ["CONSTANT_Utf8", "Foo.java"] }, - "source": "Foo.java", + "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"], - "descriptor": "Ljava/lang/String;", - "attributes": [] }], + { "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"], - "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", - "attributes": ["Code"], - "code": { - "max stack": 1, - "max locals": 3, - "attributes": ["StackMapTable"], - "stack map frames": { - "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, - "0": ["ILOAD_1", { "slot": 1 }], - "1": ["IFEQ", { "target": 6 }], - "4": ["ALOAD_2", { "slot": 2 }], - "5": ["ATHROW"], - "6": ["RETURN"] } }] - } + { "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 public void testPrintJsonCriticalAttributes() throws IOException { var out = new StringBuilder(); - ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); + ClassPrinter.toJson(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES, out::append); assertOut(out, """ { "class name": "Foo", @@ -242,36 +511,48 @@ public void testPrintJsonCriticalAttributes() throws IOException { "flags": ["PUBLIC"], "superclass": "Boo", "interfaces": ["Phee", "Phoo"], - "attributes": ["SourceFile"], + "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"], - "descriptor": "Ljava/lang/String;", - "attributes": [] }], + { "field name": "f", + "flags": ["PRIVATE"], + "field type": "Ljava/lang/String;", + "attributes": ["RuntimeVisibleAnnotations"]}], "methods": [ - { "method name": "m", - "flags": ["PROTECTED"], - "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", - "attributes": ["Code"], - "code": { - "max stack": 1, - "max locals": 3, - "attributes": ["StackMapTable"], - "stack map frames": { - "6": { "locals": ["Foo", "int", "java/lang/Throwable"], "stack": [] } }, - "0": ["ILOAD_1", { "slot": 1 }], - "1": ["IFEQ", { "target": 6 }], - "4": ["ALOAD_2", { "slot": 2 }], - "5": ["ATHROW"], - "6": ["RETURN"] } }] - } + { "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 public void testPrintJsonMembersOnly() throws IOException { var out = new StringBuilder(); - ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); + ClassPrinter.toJson(getClassModel(), ClassPrinter.Verbosity.MEMBERS_ONLY, out::append); assertOut(out, """ { "class name": "Foo", @@ -279,141 +560,287 @@ public void testPrintJsonMembersOnly() throws IOException { "flags": ["PUBLIC"], "superclass": "Boo", "interfaces": ["Phee", "Phoo"], - "attributes": ["SourceFile"], + "attributes": ["SourceFile", "InnerClasses", "EnclosingMethod", "Synthetic", "Signature", "Deprecated", "NestHost", "NestMembers", "Record", "RuntimeInvisibleAnnotations", "PermittedSubclasses"], "fields": [ - { "field name": "f", - "flags": ["PRIVATE"], - "descriptor": "Ljava/lang/String;", - "attributes": [] }], + { "field name": "f", + "flags": ["PRIVATE"], + "field type": "Ljava/lang/String;", + "attributes": ["RuntimeVisibleAnnotations"]}], "methods": [ - { "method name": "m", - "flags": ["PROTECTED"], - "descriptor": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", - "attributes": ["Code"] }] - } + { "method name": "m", + "flags": ["PROTECTED"], + "method type": "(ZLjava/lang/Throwable;)Ljava/lang/Void;", + "attributes": ["AnnotationDefault", "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations", "Exceptions", "Code"]}]} """); } @Test public void testPrintXmlTraceAll() throws IOException { var out = new StringBuilder(); - ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, out::append).printClass(getClassModel()); + ClassPrinter.toXml(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL, out::append); assertOut(out, """ - + + Foo + 61.0 + PUBLIC + Boo + PheePhoo + SourceFileInnerClassesEnclosingMethodSyntheticSignatureDeprecatedNestHostNestMembersRecordRuntimeInvisibleAnnotationsPermittedSubclasses - <:>1Foo - <:>2 - <:>3Boo - <:>4 - <:>5f - <:>6Ljava/lang/String; - <:>7m - <:>8(ZLjava/lang/Throwable;)Ljava/lang/Void; - <:>9Phee - <:>10 - <:>11Phoo - <:>12 - <:>13Code - <:>14StackMapTable - <:>15SourceFile - <:>16Foo.java - Foo.java + <_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 - - - - <:>6 - - <:>0 - <:>1 - <:>4 - <:>5 - - <:>6 - + + 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 public void testPrintXmlCriticalAttributes() throws IOException { var out = new StringBuilder(); - ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.CRITICAL_ATTRIBUTES, out::append).printClass(getClassModel()); + 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 - - - - <:>6 - - <:>0 - <:>1 - <:>4 - <:>5 - - <:>6 - + + 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 public void testPrintXmlMembersOnly() throws IOException { var out = new StringBuilder(); - ClassPrinter.xmlPrinter(ClassPrinter.VerbosityLevel.MEMBERS_ONLY, out::append).printClass(getClassModel()); + 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 + public void testWalkTraceAll() throws IOException { + var node = ClassPrinter.toTree(getClassModel(), ClassPrinter.Verbosity.TRACE_ALL); + assertEquals(node.walk().count(), 509); + } + + @Test + public void testWalkCriticalAttributes() throws IOException { + var node = ClassPrinter.toTree(getClassModel(), ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES); + assertEquals(node.walk().count(), 128); + } + + @Test + public 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) { - assertEquals(out.toString().replaceAll("\\\r", "").trim(), expected.trim()); +// System.out.println("-----------------"); +// System.out.println(out.toString()); +// System.out.println("-----------------"); + assertEquals(out.toString().trim().split(" *\r?\n"), expected.trim().split("\n")); } } diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index e05853cc2f959..a3d5254623aac 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -44,7 +44,6 @@ import java.lang.constant.MethodTypeDesc; import java.util.List; import java.lang.reflect.AccessFlag; -import jdk.classfile.util.ClassPrinter; /** * StackMapsTest diff --git a/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java b/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java index 6edb82b797fa1..61acd04d1677b 100644 --- a/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java +++ b/test/jdk/jdk/classfile/TempConstantPoolBuilderTest.java @@ -68,6 +68,6 @@ public void addAnno() { ); }); ClassModel m = Classfile.parse(bytes); - //ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, System.out::println).printClass(m); + //ClassPrinter.toJson(m, ClassPrinter.Verbosity.TRACE_ALL, System.out::println); } } diff --git a/test/jdk/jdk/classfile/examples/AnnotationsExamples.java b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java index 3cfd9001870bd..a9fc1fe45bc61 100644 --- a/test/jdk/jdk/classfile/examples/AnnotationsExamples.java +++ b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java @@ -39,7 +39,7 @@ import jdk.classfile.Classfile; import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; import jdk.classfile.constantpool.ConstantPoolBuilder; -import jdk.classfile.util.ClassPrinter; +import jdk.classfile.ClassPrinter; import org.testng.Assert; import org.testng.annotations.Factory; import org.testng.annotations.Test; @@ -154,7 +154,7 @@ public void addAnnotation() { int size = m2.findAttribute(Attributes.RUNTIME_VISIBLE_ANNOTATIONS).orElseThrow().annotations().size(); if (size !=2) { StringBuilder sb = new StringBuilder(); - ClassPrinter.jsonPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, sb::append).printClass(m2); + ClassPrinter.toJson(m2, ClassPrinter.Verbosity.TRACE_ALL, sb::append); System.err.println(sb.toString()); } } diff --git a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java index 73241e6aaa3f1..a03cdf68e11b9 100644 --- a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java +++ b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java @@ -85,7 +85,7 @@ public void writeBody(BufWriter b) { // ClassRecord.assertEqualsDeep( // ClassRecord.ofClassModel(ClassModel.of(Files.readAllBytes(root.resolve(targetClassFile)))), // ClassRecord.ofClassModel(ClassModel.of(Files.readAllBytes(root.resolve(sourceClassFile))))); -// ClassPrinter.yamlPrinter(ClassPrinter.VerbosityLevel.TRACE_ALL, System.out::print).printClass(ClassModel.of(Files.readAllBytes(root.resolve(targetClassFile)))); +// ClassPrinter.toYaml(ClassModel.of(Files.readAllBytes(root.resolve(targetClassFile))), ClassPrinter.Verbosity.TRACE_ALL, System.out::print); } @DataProvider(name = "corpus") From 2c988dfab7a491dd37e69bb288786b86a3d06d3e Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 3 Aug 2022 17:39:34 +0200 Subject: [PATCH 037/190] StackMapGenerator appends detailed debug info about corrupted bytecode in case of an error - previous debug logging and TRACE and DEBUG switches have been removed --- .../jdk/classfile/impl/SplitConstantPool.java | 2 +- .../jdk/classfile/impl/StackMapGenerator.java | 66 ++++++++----------- test/jdk/jdk/classfile/StackMapsTest.java | 4 +- 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java index ec156c90d5e79..ec91bddf91044 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java +++ b/src/java.base/share/classes/jdk/classfile/impl/SplitConstantPool.java @@ -84,7 +84,7 @@ public final class SplitConstantPool implements ConstantPoolBuilder { private final ClassReaderImpl parent; private final int parentSize, parentBsmSize; - private final Options options; + final Options options; private int size, bsmSize; private PoolEntry[] myEntries; diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 911ce122e5d6a..a49e07353f90a 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -36,9 +36,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import jdk.classfile.Attribute; @@ -46,9 +44,10 @@ import static jdk.classfile.Classfile.*; import jdk.classfile.BufWriter; import jdk.classfile.Label; -import jdk.classfile.Opcode; import jdk.classfile.attribute.StackMapTableAttribute; import jdk.classfile.Attributes; +import jdk.classfile.ClassPrinter; +import jdk.classfile.attribute.CodeAttribute; /** * StackMapGenerator is responsible for stack map frames generation. @@ -156,19 +155,6 @@ public final class StackMapGenerator { - private static final boolean TRACE, DEBUG; - private static final Map OPCODE_NAMES; - static { - TRACE = Boolean.getBoolean(StackMapGenerator.class.getName() + ".TRACE"); - DEBUG = TRACE || Boolean.getBoolean(StackMapGenerator.class.getName() + ".DEBUG"); - if (DEBUG) { - OPCODE_NAMES = new HashMap<>(); - for (var o : Opcode.values()) - OPCODE_NAMES.put(o.bytecode(), o.name()); - } else - OPCODE_NAMES = null; - } - private static final String OBJECT_INITIALIZER_NAME = ""; private static final int FLAG_THIS_UNINIT = 0x01; private static final int FRAME_DEFAULT_CAPACITY = 10; @@ -207,7 +193,6 @@ public final class StackMapGenerator { private List frames; private final Frame currentFrame; private int maxStack, maxLocals; - private boolean trace = TRACE; /** * Primary constructor of the Generator class. @@ -244,17 +229,7 @@ public StackMapGenerator(LabelContext labelContext, this.classHierarchy = new ClassHierarchyImpl(cp.optionValue(Classfile.Option.Key.HIERARCHY_RESOLVER)); this.patchDeadCode = cp.optionValue(Classfile.Option.Key.PATCH_DEAD_CODE); this.currentFrame = new Frame(classHierarchy); - if (DEBUG) System.out.println("Generating stack maps for class: " + thisClass.displayName() + " method: " + methodName + " with signature: " + methodDesc); - try { - generate(); - } catch (Error | Exception e) { - if (DEBUG && !trace) { - e.printStackTrace(System.out); - trace = true; - generate(); - } - throw e; - } + generate(); } /** @@ -307,7 +282,6 @@ private void generate() { if (end_pc > exMax) exMax = end_pc; } BitSet frameOffsets = detectFrameOffsets(); - if (trace) System.out.println(" Detected mandatory frame bytecode offsets: " + frameOffsets); int framesCount = frameOffsets.cardinality(); frames = new ArrayList<>(framesCount); int offset = -1; @@ -316,18 +290,14 @@ private void generate() { frames.add(new Frame(offset, classHierarchy)); } do { - if (trace) System.out.println(" Entering generator loop"); processMethod(); } while (isAnyFrameDirty()); maxLocals = currentFrame.frameMaxLocals; maxStack = currentFrame.frameMaxStack; - if (DEBUG) System.out.println(" Calculated maxLocals: " + maxLocals + " maxStack: " + maxStack); - if (framesCount > 0) if (DEBUG) System.out.println(" Generated stack map frames:"); //dead code patching for (int i = 0; i < framesCount; i++) { var frame = frames.get(i); - if (DEBUG) System.out.println(" " + frame); if (frame.flags == -1) { if (!patchDeadCode) generatorError("Unable to generate stack map frame for dead code", frame.offset); //patch frame @@ -335,7 +305,6 @@ private void generate() { if (maxStack < 1) maxStack = 1; int blockSize = (i < framesCount - 1 ? frames.get(i + 1).offset : bytecode.limit()) - frame.offset; //patch bytecode - if (trace) System.out.println(" Patching dead code range <" + frame.offset + ", " + (frame.offset + blockSize) + ")"); bytecode.position(frame.offset); for (int n=1; n= handlerEnd) { //complete removal @@ -441,7 +409,6 @@ private void processMethod() { } bcs.rawNext(nextFrame.offset); //skip code up-to the next frame currentFrame.offset = bcs.bci; - if (trace) System.out.println(" " + currentFrame); currentFrame.copyFrom(nextFrame); nextFrame.dirty = false; } else if (thisOffset < bcs.bci) { @@ -452,7 +419,6 @@ private void processMethod() { } ncf = processBlock(bcs); } - if (trace) System.out.println(" " + currentFrame); } private boolean processBlock(RawBytecodeHelper bcs) { @@ -461,7 +427,6 @@ private boolean processBlock(RawBytecodeHelper bcs) { boolean this_uninit = false; boolean verified_exc_handlers = false; int bci = bcs.bci; - if (trace) System.out.println(" " +currentFrame +"\n @" + bci + " " + OPCODE_NAMES.get(opcode)); Type type1, type2, type3, type4; if (RawBytecodeHelper.isStoreIntoLocal(opcode) && bci >= exMin && bci < exMax) { processExceptionHandlerTargets(bci, this_uninit); @@ -872,11 +837,34 @@ private void generatorError(String msg) { * @param offset bytecode offset where the error occured */ private void generatorError(String msg, int offset) { - throw new VerifyError(String.format("%s at bytecode offset %d of method %s(%s)", + 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 { + ((SplitConstantPool)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) { + var err = new VerifyError(sb.toString()); + err.addSuppressed(suppresed); + throw err; + } + throw new VerifyError(sb.toString()); } /** diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index a3d5254623aac..894aa789c2814 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -92,7 +92,7 @@ public void testDeadCodePatternPatch() throws Exception { testTransformedStackMaps(buildDeadCode()); } - @Test(expectedExceptions = VerifyError.class, expectedExceptionsMessageRegExp = "Unable to generate stack map frame for dead code at bytecode offset 1 of method twoReturns\\(\\)") + @Test(expectedExceptions = VerifyError.class, expectedExceptionsMessageRegExp = "Unable to generate stack map frame for dead code at bytecode offset 1 of method twoReturns.+opcode: RETURN.+") public void testDeadCodePatternFail() throws Exception { testTransformedStackMaps(buildDeadCode(), Classfile.Option.patchDeadCode(false)); } @@ -157,7 +157,7 @@ public void testPattern10() throws Exception { testTransformedStackMaps("/testdata/Pattern10.class"); } - @Test(expectedExceptions = VerifyError.class, expectedExceptionsMessageRegExp = "Detected branch target out of bytecode range at bytecode offset 0 of method frameOutOfRangeMethod\\(\\)") + @Test(expectedExceptions = VerifyError.class, expectedExceptionsMessageRegExp = "Detected branch target out of bytecode range at bytecode offset 0 of method frameOutOfRangeMethod.+opcode: GOTO.+") public void testFrameOutOfBytecodeRange() { Classfile.parse( Classfile.build(ClassDesc.of("TestClass"), clb -> From 3902fd25583d9385c6287680fd0d672259bf7e7e Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 3 Aug 2022 18:28:37 +0200 Subject: [PATCH 038/190] Opcode.ISHR and LSHR fix --- .../share/classes/jdk/classfile/Opcode.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/Opcode.java b/src/java.base/share/classes/jdk/classfile/Opcode.java index c8f73af924e03..aca34c96596b4 100755 --- a/src/java.base/share/classes/jdk/classfile/Opcode.java +++ b/src/java.base/share/classes/jdk/classfile/Opcode.java @@ -157,8 +157,8 @@ public enum Opcode { DNEG(Classfile.DNEG, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), ISHL(Classfile.ISHL, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), LSHL(Classfile.LSHL, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), - ISHR(Classfile.ISHR, 1, CodeElement.Kind.OPERATOR, TypeKind.FloatType), - LSHR(Classfile.LSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.DoubleType), + ISHR(Classfile.ISHR, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), + LSHR(Classfile.LSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), IUSHR(Classfile.IUSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), LUSHR(Classfile.LUSHR, 1, CodeElement.Kind.OPERATOR, TypeKind.LongType), IAND(Classfile.IAND, 1, CodeElement.Kind.OPERATOR, TypeKind.IntType), @@ -288,11 +288,11 @@ public enum Opcode { this(bytecode, sizeIfFixed, kind, primaryTypeKind, secondaryTypeKind, 0, null); } - Opcode(int bytecode, - int sizeIfFixed, - CodeElement.Kind kind, - TypeKind primaryTypeKind, - TypeKind secondaryTypeKind, + Opcode(int bytecode, + int sizeIfFixed, + CodeElement.Kind kind, + TypeKind primaryTypeKind, + TypeKind secondaryTypeKind, int slot, ConstantDesc constantValue) { this.bytecode = bytecode; @@ -303,7 +303,7 @@ public enum Opcode { this.slot = slot; this.constantValue = constantValue; } - + public int bytecode() { return bytecode; } public boolean isWide() { return bytecode > 255 && !isPseudo(); } From b83633c6f9f9380564e78fcd9914ec977ea9cf4e Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 4 Aug 2022 10:54:30 +0200 Subject: [PATCH 039/190] added StackMapGenerator error debug info print fallback to bytecode hex dump --- .../classes/jdk/classfile/impl/StackMapGenerator.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index a49e07353f90a..6e1a6c999ca55 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -860,6 +860,14 @@ public void writeBody(BufWriter b) { })); 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; From 6c8f57a3f4eb2237628602263c62cc17322064f3 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 4 Aug 2022 15:37:39 +0200 Subject: [PATCH 040/190] fixed StackMapGenerator operand stack underflow reporting --- .../share/classes/jdk/classfile/impl/StackMapGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index 6e1a6c999ca55..d2a204f4e4324 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -947,7 +947,7 @@ public void set(int i) { return offsets; } - private static final class Frame { + private final class Frame { int offset; int localsSize, stackSize; @@ -1013,13 +1013,13 @@ Frame pushStack(Type type1, Type type2) { } Type popStack() { - if (stackSize < 1) throw new VerifyError("Operand stack underflow"); + if (stackSize < 1) generatorError("Operand stack underflow"); return stack[--stackSize]; } Frame decStack(int size) { stackSize -= size; - if (stackSize < 0) throw new VerifyError("Operand stack underflow"); + if (stackSize < 0) generatorError("Operand stack underflow"); return this; } From 76b09c5e2459b99c30cd605ce8120fe45da1fcc3 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 18 Aug 2022 08:00:59 +0200 Subject: [PATCH 041/190] Classfile API stack map manual processing (#32) * removed FrameKind from StackMapTableAttribute and offsets replaced with labels in StackMapFrames * simplification of StackMapFrame, removal of compressed sub-types * Implemented UnboundStackMapTableAttribute and added related factory methods dropped VerificationType SimpleVerificationTypeInfo refactored to enum * implemented StackMapTableAttribute compression and writing * StackMapTableAttribute extends CodeElement * added Classfile.Option.processStackMaps enable stack maps processing in RebuildingTransformation test helper fixed UninitializedVerificationTypeInfo labels resolution * fixed Opcode * enable unordered StackMapTableAttribute entries * removal of PROCESS_STACK_MAPS option generated stack maps override user content adjusted labels inflation from stack maps * minor patch in RebuildingTransformation test helper --- .../classes/jdk/classfile/Attributes.java | 7 +- .../classes/jdk/classfile/CodeElement.java | 6 +- .../share/classes/jdk/classfile/Opcode.java | 3 +- .../attribute/StackMapTableAttribute.java | 153 ++++----- .../jdk/classfile/impl/AttributeHolder.java | 2 +- .../jdk/classfile/impl/BoundAttribute.java | 32 +- .../jdk/classfile/impl/ClassPrinterImpl.java | 24 +- .../classes/jdk/classfile/impl/CodeImpl.java | 19 +- .../jdk/classfile/impl/DirectCodeBuilder.java | 4 + .../jdk/classfile/impl/StackMapDecoder.java | 306 +++++++++--------- .../jdk/classfile/impl/UnboundAttribute.java | 32 ++ .../helpers/RebuildingTransformation.java | 17 + .../jdk/jdk/classfile/helpers/Transforms.java | 2 +- 13 files changed, 324 insertions(+), 283 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/Attributes.java b/src/java.base/share/classes/jdk/classfile/Attributes.java index b025624a62287..0ce8c33495e1f 100755 --- a/src/java.base/share/classes/jdk/classfile/Attributes.java +++ b/src/java.base/share/classes/jdk/classfile/Attributes.java @@ -87,6 +87,7 @@ import static jdk.classfile.AttributedElement.Kind.EVERYWHERE; import static jdk.classfile.AttributedElement.Kind.FIELD_ONLY; import static jdk.classfile.AttributedElement.Kind.METHOD_ONLY; +import jdk.classfile.impl.StackMapDecoder; /** * Attribute mappers for standard classfile attributes. @@ -740,12 +741,12 @@ protected void writeBody(BufWriter buf, SourceIDAttribute attr) { STACK_MAP_TABLE = new AbstractAttributeMapper<>(NAME_STACK_MAP_TABLE, CODE_ONLY, Classfile.JAVA_6_VERSION) { @Override public StackMapTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { - return new BoundAttribute.BoundStackMapTableAttribute((CodeModel)e, cf, this, p); + return new BoundAttribute.BoundStackMapTableAttribute((CodeImpl)e, cf, this, p); } @Override - protected void writeBody(BufWriter buf, StackMapTableAttribute attr) { - throw new AssertionError("should never reach here"); + protected void writeBody(BufWriter b, StackMapTableAttribute attr) { + StackMapDecoder.writeFrames(b, attr.entries()); } }; diff --git a/src/java.base/share/classes/jdk/classfile/CodeElement.java b/src/java.base/share/classes/jdk/classfile/CodeElement.java index 86e8d09301093..b4076130a6216 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeElement.java +++ b/src/java.base/share/classes/jdk/classfile/CodeElement.java @@ -26,6 +26,7 @@ import jdk.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; import jdk.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import jdk.classfile.attribute.StackMapTableAttribute; import jdk.classfile.impl.AbstractInstruction; /** @@ -38,7 +39,8 @@ */ public sealed interface CodeElement extends ClassfileElement permits Instruction, PseudoInstruction, AbstractInstruction, - CustomAttribute, RuntimeVisibleTypeAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute { + CustomAttribute, RuntimeVisibleTypeAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, + StackMapTableAttribute { /** * {@return the kind of this instruction} */ @@ -64,7 +66,7 @@ enum Kind { TYPE_CHECK, ARRAY_LOAD, ARRAY_STORE, STACK, CONVERT, OPERATOR, CONSTANT, MONITOR, NOP, UNSUPPORTED, LABEL_TARGET, EXCEPTION_CATCH, CHARACTER_RANGE, LOCAL_VARIABLE, LOCAL_VARIABLE_TYPE, LINE_NUMBER, - PARAMETER_ANNOTATION, TYPE_ANNOTATION, END; + PARAMETER_ANNOTATION, STACK_MAP, TYPE_ANNOTATION, END; } } diff --git a/src/java.base/share/classes/jdk/classfile/Opcode.java b/src/java.base/share/classes/jdk/classfile/Opcode.java index aca34c96596b4..cc816be21fa56 100755 --- a/src/java.base/share/classes/jdk/classfile/Opcode.java +++ b/src/java.base/share/classes/jdk/classfile/Opcode.java @@ -257,7 +257,8 @@ public enum Opcode { PARAMETER_ANNO(0xFF06, 0, CodeElement.Kind.PARAMETER_ANNOTATION), TYPE_ANNO(0xFF07, 0, CodeElement.Kind.TYPE_ANNOTATION), LOCAL_VARIABLE_TYPE(0xFF08, 0, CodeElement.Kind.LOCAL_VARIABLE_TYPE), - END(0xFF09, 0, CodeElement.Kind.END), + STACK_MAP(0xFF09, 0, CodeElement.Kind.STACK_MAP), + END(0xFF0A, 0, CodeElement.Kind.END), ; private final int bytecode; diff --git a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java index fac2f019e9d80..b778ff0bc537a 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java @@ -25,12 +25,17 @@ package jdk.classfile.attribute; +import java.lang.constant.ClassDesc; import java.util.List; import jdk.classfile.Attribute; +import jdk.classfile.CodeElement; +import jdk.classfile.Label; import jdk.classfile.constantpool.ClassEntry; import jdk.classfile.impl.BoundAttribute; import jdk.classfile.impl.StackMapDecoder; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.UnboundAttribute; import static jdk.classfile.Classfile.*; /** @@ -38,98 +43,84 @@ * on a {@code Code} attribute. */ public sealed interface StackMapTableAttribute - extends Attribute - permits BoundAttribute.BoundStackMapTableAttribute { + extends Attribute, CodeElement + permits BoundAttribute.BoundStackMapTableAttribute, UnboundAttribute.UnboundStackMapTableAttribute { /** * {@return the stack map frames} */ - List entries(); + List entries(); + + public static StackMapTableAttribute of(List entries) { + return new UnboundAttribute.UnboundStackMapTableAttribute(entries); + } /** - * {@return the initial frame} + * The type of a stack value. */ - StackMapFrame.Full initFrame(); + sealed interface VerificationTypeInfo { + int tag(); + } /** - * The possible types for a stack slot. + * A simple stack value. */ - enum VerificationType { + public enum SimpleVerificationTypeInfo implements VerificationTypeInfo { ITEM_TOP(VT_TOP), ITEM_INTEGER(VT_INTEGER), ITEM_FLOAT(VT_FLOAT), - ITEM_DOUBLE(VT_DOUBLE, 2), - ITEM_LONG(VT_LONG, 2), + ITEM_DOUBLE(VT_DOUBLE), + ITEM_LONG(VT_LONG), ITEM_NULL(VT_NULL), - ITEM_UNINITIALIZED_THIS(VT_UNINITIALIZED_THIS), - ITEM_OBJECT(VT_OBJECT), - ITEM_UNINITIALIZED(VT_UNINITIALIZED); + ITEM_UNINITIALIZED_THIS(VT_UNINITIALIZED_THIS); - private final int tag; - private final int width; - VerificationType(int tag) { - this(tag, 1); - } + private final int tag; - VerificationType(int tag, int width) { + SimpleVerificationTypeInfo(int tag) { this.tag = tag; - this.width = width; } public int tag() { return tag; } - } - /** - * Kinds of stack values. - */ - enum FrameKind { - 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 int start() { return start; } - public int end() { return end; } - - FrameKind(int start, int end) { - this.start = start; - this.end = end; + @Override + public String toString() { + return switch (this) { + case ITEM_DOUBLE -> "D"; + case ITEM_FLOAT -> "F"; + case ITEM_INTEGER -> "I"; + case ITEM_LONG -> "J"; + case ITEM_NULL -> "null"; + case ITEM_TOP -> "?"; + case ITEM_UNINITIALIZED_THIS -> "THIS"; + }; } } - /** - * The type of a stack value. - */ - sealed interface VerificationTypeInfo { - VerificationType type(); - } - - /** - * A simple stack value. - */ - sealed interface SimpleVerificationTypeInfo extends VerificationTypeInfo - permits StackMapDecoder.SimpleVerificationTypeInfoImpl { - } - /** * 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(); + } } /** @@ -137,51 +128,29 @@ sealed interface ObjectVerificationTypeInfo extends VerificationTypeInfo */ sealed interface UninitializedVerificationTypeInfo extends VerificationTypeInfo permits StackMapDecoder.UninitializedVerificationTypeInfoImpl { - int offset(); + Label newTarget(); + + public static UninitializedVerificationTypeInfo of(Label newTarget) { + return new StackMapDecoder.UninitializedVerificationTypeInfoImpl(newTarget); + } } /** * A stack map frame. */ - sealed interface StackMapFrame - permits StackMapFrame.Same, StackMapFrame.Same1, StackMapFrame.Append, StackMapFrame.Chop, StackMapFrame.Full { + sealed interface StackMapFrameInfo + permits StackMapDecoder.StackMapFrameImpl { int frameType(); - FrameKind frameKind(); - int offsetDelta(); - int absoluteOffset(); - List effectiveLocals(); - List effectiveStack(); - - sealed interface Same extends StackMapFrame permits StackMapDecoder.StackMapFrameSameImpl { - - boolean extended(); - } - sealed interface Same1 extends StackMapFrame permits StackMapDecoder.StackMapFrameSame1Impl { - - boolean extended(); - - VerificationTypeInfo declaredStack(); - } - - sealed interface Append extends StackMapFrame permits StackMapDecoder.StackMapFrameAppendImpl { - - List declaredLocals(); - } - - sealed interface Chop extends StackMapFrame permits StackMapDecoder.StackMapFrameChopImpl { - - List choppedLocals(); - } + Label target(); + List locals(); + List stack(); - sealed interface Full extends StackMapFrame permits StackMapDecoder.StackMapFrameFullImpl { + public static StackMapFrameInfo of(Label target, + List locals, + List stack) { - default List declaredStack() { - return effectiveStack(); - } - default List declaredLocals() { - return effectiveLocals(); - } + return new StackMapDecoder.StackMapFrameImpl(255, target, locals, stack); } } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.java b/src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.java index a2cfb3894ad39..f4b7db917f55e 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AttributeHolder.java @@ -60,7 +60,7 @@ public void writeTo(BufWriter buf) { a.writeTo(buf); } - private boolean isPresent(AttributeMapper am) { + boolean isPresent(AttributeMapper am) { for (Attribute a : attributes) if (a.attributeMapper() == am) return true; diff --git a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java index 124d5586c2698..5111adaeea4e2 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BoundAttribute.java @@ -234,29 +234,37 @@ public static final class BoundStackMapTableAttribute extends BoundAttribute implements StackMapTableAttribute { final MethodModel method; - List entries = null; - StackMapFrame.Full initFrame = null; + final LabelContext ctx; + List entries = null; - public BoundStackMapTableAttribute(CodeModel code, ClassReader cf, AttributeMapper mapper, int pos) { + public BoundStackMapTableAttribute(CodeImpl code, ClassReader cf, AttributeMapper mapper, int pos) { super(cf, mapper, pos); method = code.parent().orElseThrow(); + ctx = code; } @Override - public StackMapFrame.Full initFrame() { - if (initFrame == null) - initFrame = StackMapDecoder.initFrame(method); - return initFrame; - } - - @Override - public List entries() { + public List entries() { if (entries == null) { - entries = new StackMapDecoder(classReader, payloadStart, initFrame()).entries(); + entries = new StackMapDecoder(classReader, payloadStart, ctx, StackMapDecoder.initFrameLocals(method)).entries(); } return entries; } + @Override + public Kind codeKind() { + return Kind.STACK_MAP; + } + + @Override + public Opcode opcode() { + return Opcode.STACK_MAP; + } + + @Override + public int sizeInBytes() { + return 0; + } } public static final class BoundSyntheticAttribute extends BoundAttribute diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java index e139f3fcb6edc..54e40350c03c0 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java @@ -512,11 +512,11 @@ private static Node elementValuePairsToTree(List evps) { new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value()))))); } - private static Stream convertVTIs(int bci, List vtis) { + private static Stream convertVTIs(LabelResolver lr, List vtis) { return vtis.stream().mapMulti((vti, ret) -> { switch (vti) { case SimpleVerificationTypeInfo s -> { - switch (s.type()) { + switch (s) { case ITEM_DOUBLE -> { ret.accept("double"); ret.accept("double2"); @@ -537,7 +537,7 @@ private static Stream convertVTIs(int bci, List ret.accept(o.className().name().stringValue()); case UninitializedVerificationTypeInfo u -> - ret.accept("UNITIALIZED @" + (bci + u.offset())); + ret.accept("UNITIALIZED @" + lr.labelToBci(u.newTarget())); } }); } @@ -549,7 +549,7 @@ public static MapNode modelToTree(CompoundElement model, Verbosity verbosity) case ClassModel cm -> classToTree(cm, verbosity); case FieldModel fm -> fieldToTree(fm, verbosity); case MethodModel mm -> methodToTree(mm, verbosity); - case CodeModel com -> codeToTree(com, verbosity); + case CodeModel com -> codeToTree((CodeImpl)com, verbosity); }; } @@ -647,10 +647,10 @@ private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) { } } - private static Node frameToTree(ConstantDesc name, StackMapFrame f) { + private static Node frameToTree(ConstantDesc name, LabelResolver lr, StackMapFrameInfo f) { return new MapNodeImpl(FLOW, name).with( - list("locals", "item", convertVTIs(f.absoluteOffset(), f.effectiveLocals())), - list("stack", "item", convertVTIs(f.absoluteOffset(), f.effectiveStack()))); + list("locals", "item", convertVTIs(lr, f.locals())), + list("stack", "item", convertVTIs(lr, f.stack()))); } private static MapNode fieldToTree(FieldModel f, Verbosity verbosity) { @@ -669,10 +669,10 @@ public static MapNode methodToTree(MethodModel m, Verbosity verbosity) { leaf("method type", m.methodType().stringValue()), list("attributes", "attribute", m.attributes().stream().map(Attribute::attributeName))) .with(attributesToTree(m.attributes(), verbosity)) - .with(codeToTree(m.code().orElse(null), verbosity)); + .with(codeToTree((CodeImpl)m.code().orElse(null), verbosity)); } - private static MapNode codeToTree(CodeModel com, Verbosity verbosity) { + private static MapNode codeToTree(CodeImpl com, Verbosity verbosity) { if (verbosity != Verbosity.MEMBERS_ONLY && com != null) { var codeNode = new MapNodeImpl(BLOCK, "code"); codeNode.with(leaf("max stack", ((CodeAttribute)com).maxStack())); @@ -686,7 +686,7 @@ private static MapNode codeToTree(CodeModel com, Verbosity verbosity) { if (attr instanceof StackMapTableAttribute smta) { codeNode.with(stackMap); for (var smf : smta.entries()) { - stackMap.with(frameToTree(smf.absoluteOffset(), smf)); + stackMap.with(frameToTree(com.labelToBci(smf.target()), com, smf)); } } else if (verbosity == Verbosity.TRACE_ALL) switch (attr) { case LocalVariableTableAttribute lvta -> { @@ -740,7 +740,9 @@ private static MapNode codeToTree(CodeModel com, Verbosity verbosity) { } codeNode.with(attributesToTree(com.attributes(), verbosity)); if (!stackMap.containsKey(0)) { - codeNode.with(frameToTree("//stack map frame @0", StackMapDecoder.initFrame(com.parent().get()))); + 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; diff --git a/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java index 54933ff199f54..3cfae6765daed 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/CodeImpl.java @@ -248,8 +248,11 @@ public boolean compareCodeBytes(BufWriter buf, int offset, int len) { && classReader.compare(buf, offset, codeStart, codeLength); } - private static int adjustForLongDouble(int vt, int v) { - return (vt == 7 || vt == 8) ? v + 2 : v; + 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) { @@ -295,13 +298,13 @@ private void inflateJumpTargets() { } else if (frameType < 128) { offsetDelta = frameType & 0x3f; - p += adjustForLongDouble(classReader.readU1(p + 1), 2); + p = adjustForObjectOrUninitialized(p + 1); } else switch (frameType) { case 247 -> { offsetDelta = classReader.readU2(p + 1); - p += adjustForLongDouble(classReader.readU1(p + 3), 4); + p = adjustForObjectOrUninitialized(p + 3); } case 248, 249, 250, 251 -> { offsetDelta = classReader.readU2(p + 1); @@ -312,7 +315,7 @@ else if (frameType < 128) { int k = frameType - 251; p += 3; for (int c = 0; c < k; ++c) { - p += adjustForLongDouble(classReader.readU1(p), 1); + p = adjustForObjectOrUninitialized(p); } } case 255 -> { @@ -321,12 +324,12 @@ else if (frameType < 128) { int k = classReader.readU2(p); p += 2; for (int c = 0; c < k; ++c) { - p += adjustForLongDouble(classReader.readU1(p), 1); + p = adjustForObjectOrUninitialized(p); } k = classReader.readU2(p); p += 2; for (int c = 0; c < k; ++c) { - p += adjustForLongDouble(classReader.readU1(p), 1); + p = adjustForObjectOrUninitialized(p); } } default -> throw new IllegalArgumentException("Bad frame type: " + frameType); @@ -369,7 +372,7 @@ private void generateDebugElements(Consumer consumer) { consumer.accept(instruction); } } - if (a.attributeMapper() == Attributes.LOCAL_VARIABLE_TABLE) { + else if (a.attributeMapper() == Attributes.LOCAL_VARIABLE_TABLE) { var attr = (BoundLocalVariableTableAttribute) a; int cnt = classReader.readU2(attr.payloadStart); int p = attr.payloadStart + 2; diff --git a/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java index 86af83d495bbf..ed7a342e89df4 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java @@ -182,6 +182,10 @@ public int curPc() { return bytecodesBufWriter.size(); } + public MethodInfo methodInfo() { + return methodInfo; + } + private Attribute content = null; private void writeExceptionHandlers(BufWriter buf) { diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java index 0a0abf54db092..21555f86080b4 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java @@ -26,51 +26,54 @@ package jdk.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.classfile.BufWriter; import jdk.classfile.constantpool.ClassEntry; -import java.lang.reflect.AccessFlag; import jdk.classfile.attribute.StackMapTableAttribute.*; import jdk.classfile.ClassReader; import static jdk.classfile.Classfile.*; +import jdk.classfile.Label; import jdk.classfile.MethodModel; -import static jdk.classfile.attribute.StackMapTableAttribute.VerificationType.*; public class StackMapDecoder { - static final VerificationTypeInfo soleTopVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_TOP); - static final VerificationTypeInfo soleIntegerVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_INTEGER); - static final VerificationTypeInfo soleFloatVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_FLOAT); - static final VerificationTypeInfo soleDoubleVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_DOUBLE); - static final VerificationTypeInfo soleLongVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_LONG); - static final VerificationTypeInfo soleNullVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_NULL); - static final VerificationTypeInfo soleUninitializedThisVerificationTypeInfo = new SimpleVerificationTypeInfoImpl(ITEM_UNINITIALIZED_THIS); private static final int SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, - SAME_EXTENDED = 251, - FULL = 255; + SAME_EXTENDED = 251; private final ClassReader classReader; private final int pos; - private final StackMapFrame.Full initFrame; + private final LabelContext ctx; + private final List initFrameLocals; private int p; - StackMapDecoder(ClassReader classReader, int pos, StackMapFrame.Full initFrame) { + StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List initFrameLocals) { this.classReader = classReader; this.pos = pos; - this.initFrame = initFrame; + this.ctx = ctx; + this.initFrameLocals = initFrameLocals; } - static StackMapFrame.Full initFrame(MethodModel method) { + 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[]; - var mdesc = method.methodTypeSymbol(); int i = 0; - if (!method.flags().has(AccessFlag.STATIC)) { + if (!isStatic) { vtis = new VerificationTypeInfo[mdesc.parameterCount() + 1]; - var thisClass = method.parent().orElseThrow().thisClass(); - if ("".equals(method.methodName().stringValue()) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) { - vtis[i++] = StackMapDecoder.soleUninitializedThisVerificationTypeInfo; + if ("".equals(methodName) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) { + vtis[i++] = SimpleVerificationTypeInfo.ITEM_UNINITIALIZED_THIS; } else { vtis[i++] = new StackMapDecoder.ObjectVerificationTypeInfoImpl(thisClass); } @@ -79,123 +82,165 @@ static StackMapFrame.Full initFrame(MethodModel method) { } for(var arg : mdesc.parameterList()) { vtis[i++] = switch (arg.descriptorString()) { - case "I", "S", "C" ,"B", "Z" -> StackMapDecoder.soleIntegerVerificationTypeInfo; - case "J" -> StackMapDecoder.soleLongVerificationTypeInfo; - case "F" -> StackMapDecoder.soleFloatVerificationTypeInfo; - case "D" -> StackMapDecoder.soleDoubleVerificationTypeInfo; + 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 new StackMapFrameFullImpl(FULL, FrameKind.FULL_FRAME, -1, -1, List.of(vtis), List.of()); + return List.of(vtis); + } + + public static void writeFrames(BufWriter b, List entries) { + var buf = (BufWriterImpl)b; + var dcb = (DirectCodeBuilder)buf.labelResolver(); + 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 entries() { + private static boolean equals(List 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.labelResolver().labelToBci(uvti.newTarget())); + } + } + + List entries() { p = pos; - StackMapFrame frame = initFrame; - var entries = new StackMapFrame[u2()]; + 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) { - frame = new StackMapFrameSameImpl(frameType, FrameKind.SAME, - frameType, frame.absoluteOffset() + frameType + 1, - false, - frame.effectiveLocals(), List.of()); + bci += frameType + 1; + stack = List.of(); } else if (frameType < 128) { - var stack = readVerificationTypeInfo(); - frame = new StackMapFrameSame1Impl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM, - frameType - 64, frame.absoluteOffset() + frameType - 63, - false, - stack, - frame.effectiveLocals(), List.of(stack)); + bci += frameType - 63; + stack = List.of(readVerificationTypeInfo(bci)); } else { if (frameType < SAME_LOCALS_1_STACK_ITEM_EXTENDED) throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType); - int offsetDelta = u2(); + bci += u2() + 1; if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { - var stack = readVerificationTypeInfo(); - frame = new StackMapFrameSame1Impl(frameType, FrameKind.SAME_LOCALS_1_STACK_ITEM_EXTENDED, - offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - true, - stack, - frame.effectiveLocals(), List.of(stack)); + stack = List.of(readVerificationTypeInfo(bci)); } else if (frameType < SAME_EXTENDED) { - frame = new StackMapFrameChopImpl(frameType, FrameKind.CHOP, - offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - frame.effectiveLocals().subList(frame.effectiveLocals().size() + frameType - SAME_EXTENDED, frame.effectiveLocals().size()), - frame.effectiveLocals().subList(0, frame.effectiveLocals().size() + frameType - SAME_EXTENDED), List.of()); + locals = locals.subList(0, locals.size() + frameType - SAME_EXTENDED); + stack = List.of(); } else if (frameType == SAME_EXTENDED) { - frame = new StackMapFrameSameImpl(frameType, FrameKind.SAME_FRAME_EXTENDED, - offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - true, - frame.effectiveLocals(), List.of()); + stack = List.of(); } else if (frameType < SAME_EXTENDED + 4) { - int actSize = frame.effectiveLocals().size(); - var locals = frame.effectiveLocals().toArray(new VerificationTypeInfo[actSize + frameType - SAME_EXTENDED]); - for (int i = actSize; i < locals.length; i++) - locals[i] = readVerificationTypeInfo(); - var locList = List.of(locals); - frame = new StackMapFrameAppendImpl(frameType, FrameKind.APPEND, - offsetDelta, frame.absoluteOffset() + offsetDelta + 1, - locList.subList(actSize, locList.size()), - locList, List.of()); + 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(bci); + locals = List.of(newLocals); + stack = List.of(); } else { - var locals = new VerificationTypeInfo[u2()]; - for (int i=0; i soleTopVerificationTypeInfo; - case VT_INTEGER -> soleIntegerVerificationTypeInfo; - case VT_FLOAT -> soleFloatVerificationTypeInfo; - case VT_DOUBLE -> soleDoubleVerificationTypeInfo; - case VT_LONG -> soleLongVerificationTypeInfo; - case VT_NULL -> soleNullVerificationTypeInfo; - case VT_UNINITIALIZED_THIS -> soleUninitializedThisVerificationTypeInfo; + case VT_TOP -> 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(u2()); + case VT_UNINITIALIZED -> new UninitializedVerificationTypeInfoImpl(ctx.getLabel(u2())); default -> throw new IllegalArgumentException("Invalid verification type tag: " + tag); }; } - public static record SimpleVerificationTypeInfoImpl(VerificationType type) implements SimpleVerificationTypeInfo { - - @Override - public String toString() { - return switch (type) { - case ITEM_DOUBLE -> "D"; - case ITEM_FLOAT -> "F"; - case ITEM_INTEGER -> "I"; - case ITEM_LONG -> "J"; - case ITEM_NULL -> "null"; - case ITEM_TOP -> "?"; - case ITEM_UNINITIALIZED_THIS -> "THIS"; - default -> throw new AssertionError("should never happen"); - }; - } - } - public static record ObjectVerificationTypeInfoImpl( ClassEntry className) implements ObjectVerificationTypeInfo { @Override - public VerificationType type() { return VerificationType.ITEM_OBJECT; } + public int tag() { return VT_OBJECT; } @Override public String toString() { @@ -203,14 +248,14 @@ public String toString() { } } - public static record UninitializedVerificationTypeInfoImpl(int offset) implements UninitializedVerificationTypeInfo { + public static record UninitializedVerificationTypeInfoImpl(Label newTarget) implements UninitializedVerificationTypeInfo { @Override - public VerificationType type() { return VerificationType.ITEM_UNINITIALIZED; } + public int tag() { return VT_UNINITIALIZED; } @Override public String toString() { - return "UNINIT(" + offset +")"; + return "UNINIT(" + newTarget +")"; } } @@ -220,53 +265,10 @@ private int u2() { return v; } - public static record StackMapFrameSameImpl(int frameType, - FrameKind frameKind, - int offsetDelta, - int absoluteOffset, - boolean extended, - List effectiveLocals, - List effectiveStack) - implements StackMapFrame.Same { - } - - public static record StackMapFrameSame1Impl(int frameType, - FrameKind frameKind, - int offsetDelta, - int absoluteOffset, - boolean extended, - VerificationTypeInfo declaredStack, - List effectiveLocals, - List effectiveStack) - implements StackMapFrame.Same1 { - } - - public static record StackMapFrameAppendImpl(int frameType, - FrameKind frameKind, - int offsetDelta, - int absoluteOffset, - List declaredLocals, - List effectiveLocals, - List effectiveStack) - implements StackMapFrame.Append { - } - - public static record StackMapFrameChopImpl(int frameType, - FrameKind frameKind, - int offsetDelta, - int absoluteOffset, - List choppedLocals, - List effectiveLocals, - List effectiveStack) - implements StackMapFrame.Chop { - } - - public static record StackMapFrameFullImpl(int frameType, - FrameKind frameKind, - int offsetDelta, - int absoluteOffset, - List effectiveLocals, - List effectiveStack) - implements StackMapFrame.Full { + public static record StackMapFrameImpl(int frameType, + Label target, + List locals, + List stack) + implements StackMapFrameInfo { } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java index df6df587e8f45..69a4c8fc16192 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java @@ -85,6 +85,8 @@ import jdk.classfile.attribute.SourceDebugExtensionAttribute; import jdk.classfile.attribute.SourceFileAttribute; import jdk.classfile.attribute.SourceIDAttribute; +import jdk.classfile.attribute.StackMapTableAttribute; +import jdk.classfile.attribute.StackMapTableAttribute.StackMapFrameInfo; import jdk.classfile.attribute.SyntheticAttribute; import jdk.classfile.constantpool.ConstantValueEntry; import jdk.classfile.constantpool.ModuleEntry; @@ -242,6 +244,36 @@ public Utf8Entry 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; + } + + @Override + public Kind codeKind() { + return Kind.STACK_MAP; + } + + @Override + public Opcode opcode() { + return Opcode.STACK_MAP; + } + + @Override + public int sizeInBytes() { + return 0; + } + } + public static final class UnboundInnerClassesAttribute extends UnboundAttribute implements InnerClassesAttribute { diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index c85deed4be41d..11d29bd13483c 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -442,10 +442,17 @@ else switch (i.constantValue()) { 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 -> + StackMapTableAttribute.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()); @@ -569,4 +576,14 @@ static TypeAnnotation.TargetInfo transformTargetInfo(TypeAnnotation.TargetInfo t 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 StackMapTableAttribute.SimpleVerificationTypeInfo i -> i; + case StackMapTableAttribute.ObjectVerificationTypeInfo i -> StackMapTableAttribute.ObjectVerificationTypeInfo.of(i.classSymbol()); + case StackMapTableAttribute.UninitializedVerificationTypeInfo i -> StackMapTableAttribute.UninitializedVerificationTypeInfo.of(labels.computeIfAbsent(i.newTarget(), l -> cob.newLabel())); + }; + }).toList(); + } } diff --git a/test/jdk/jdk/classfile/helpers/Transforms.java b/test/jdk/jdk/classfile/helpers/Transforms.java index 9cee790c70577..527eb4e9e7aa2 100644 --- a/test/jdk/jdk/classfile/helpers/Transforms.java +++ b/test/jdk/jdk/classfile/helpers/Transforms.java @@ -117,7 +117,7 @@ public enum NoOpTransform { return bs; }), BUILD_FROM_SCRATCH(bytes -> { - return RebuildingTransformation.transform(Classfile.parse(bytes)); + return RebuildingTransformation.transform(Classfile.parse(bytes, Classfile.Option.generateStackmap(false))); }), SHARED_1(true, oneLevelNoop), SHARED_2(true, twoLevelNoop), From ced8ffa75cc6d47df88523d46175bc97f4e954a6 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 18 Aug 2022 09:38:19 +0200 Subject: [PATCH 042/190] removed obsolete toString --- .../attribute/StackMapTableAttribute.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java index b778ff0bc537a..c09a1052ae384 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/StackMapTableAttribute.java @@ -81,22 +81,10 @@ public enum SimpleVerificationTypeInfo implements VerificationTypeInfo { this.tag = tag; } + @Override public int tag() { return tag; } - - @Override - public String toString() { - return switch (this) { - case ITEM_DOUBLE -> "D"; - case ITEM_FLOAT -> "F"; - case ITEM_INTEGER -> "I"; - case ITEM_LONG -> "J"; - case ITEM_NULL -> "null"; - case ITEM_TOP -> "?"; - case ITEM_UNINITIALIZED_THIS -> "THIS"; - }; - } } /** From b547682001a2e3bb98d23fe15e2c09d5d0c26d02 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 18 Aug 2022 10:50:06 +0200 Subject: [PATCH 043/190] fixed RebuildingTransformation test helper --- test/jdk/jdk/classfile/helpers/RebuildingTransformation.java | 2 +- test/jdk/jdk/classfile/helpers/Transforms.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 11d29bd13483c..c385bbab22646 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -40,7 +40,7 @@ class RebuildingTransformation { static private Random pathSwitch = new Random(1234); static byte[] transform(ClassModel clm) { - return Classfile.build(clm.thisClass().asSymbol(), clb -> { + 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()); diff --git a/test/jdk/jdk/classfile/helpers/Transforms.java b/test/jdk/jdk/classfile/helpers/Transforms.java index 527eb4e9e7aa2..9cee790c70577 100644 --- a/test/jdk/jdk/classfile/helpers/Transforms.java +++ b/test/jdk/jdk/classfile/helpers/Transforms.java @@ -117,7 +117,7 @@ public enum NoOpTransform { return bs; }), BUILD_FROM_SCRATCH(bytes -> { - return RebuildingTransformation.transform(Classfile.parse(bytes, Classfile.Option.generateStackmap(false))); + return RebuildingTransformation.transform(Classfile.parse(bytes)); }), SHARED_1(true, oneLevelNoop), SHARED_2(true, twoLevelNoop), From de17a3b0eb7c80935d701ba98014f2bd35c221b5 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 18 Aug 2022 12:12:17 +0200 Subject: [PATCH 044/190] fixed ConstantPoolBuilder::methodHandleEntry --- .../jdk/classfile/constantpool/ConstantPoolBuilder.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java index 1363f1e8c8373..41c55b77f8faa 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantPoolBuilder.java @@ -346,7 +346,13 @@ default InterfaceMethodRefEntry interfaceMethodRefEntry(ClassDesc owner, String * @param descriptor the symbolic descriptor of the method handle */ default MethodHandleEntry methodHandleEntry(DirectMethodHandleDesc descriptor) { - return methodHandleEntry(descriptor.refKind(), methodRefEntry(descriptor.owner(), descriptor.methodName(), descriptor.invocationType())); + var owner = classEntry(descriptor.owner()); + var nat = natEntry(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); + }); } /** From cb17b0f7c17af37dcefc3e6fa06f4305ba5edd9d Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 19 Aug 2022 10:32:12 +0200 Subject: [PATCH 045/190] Classfile api label resolver branch (#33) * removed impl.LabelResolver * labelToBci moved to CodeAttribute and impl.LabelContext * removed labelToBci from CodeBuilder --- .../classes/jdk/classfile/CodeBuilder.java | 3 -- .../classes/jdk/classfile/CodeModel.java | 3 +- .../classfile/attribute/CodeAttribute.java | 3 ++ .../impl/AbstractBoundLocalVariable.java | 7 ++-- .../classfile/impl/AbstractInstruction.java | 7 ++-- .../jdk/classfile/impl/BufWriterImpl.java | 10 +++--- .../classfile/impl/BufferedCodeBuilder.java | 5 --- .../jdk/classfile/impl/ClassPrinterImpl.java | 20 +++++------ .../jdk/classfile/impl/DirectCodeBuilder.java | 8 ++--- .../jdk/classfile/impl/LabelContext.java | 3 +- .../jdk/classfile/impl/LabelResolver.java | 35 ------------------- .../impl/NonterminalCodeBuilder.java | 5 --- .../jdk/classfile/impl/StackMapDecoder.java | 4 +-- .../jdk/classfile/impl/UnboundAttribute.java | 4 +-- .../classfile/instruction/LocalVariable.java | 2 +- .../instruction/LocalVariableType.java | 2 +- .../jdk/classfile/helpers/ClassRecord.java | 29 ++++++++------- .../classfile/helpers/CorpusTestHelper.java | 4 +-- 18 files changed, 54 insertions(+), 100 deletions(-) delete mode 100755 src/java.base/share/classes/jdk/classfile/impl/LabelResolver.java diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index bf84fd95e352f..56791189ee44d 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -119,9 +119,6 @@ public sealed interface CodeBuilder * the current block will be the entire method body. */ Label endLabel(); - /** {@return the bytecode offset associated with the specified label} */ - int labelToBci(Label label); - /** * {@return the local variable slot associated with the receiver}. * diff --git a/src/java.base/share/classes/jdk/classfile/CodeModel.java b/src/java.base/share/classes/jdk/classfile/CodeModel.java index a983a15c4edfc..b3a80e9e96291 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeModel.java +++ b/src/java.base/share/classes/jdk/classfile/CodeModel.java @@ -31,7 +31,6 @@ import jdk.classfile.attribute.CodeAttribute; import jdk.classfile.impl.BufferedCodeBuilder; import jdk.classfile.impl.CodeImpl; -import jdk.classfile.impl.LabelResolver; import jdk.classfile.instruction.ExceptionCatch; /** @@ -40,7 +39,7 @@ * #elements()}). */ public sealed interface CodeModel - extends CompoundElement, AttributedElement, MethodElement, LabelResolver + extends CompoundElement, AttributedElement, MethodElement permits CodeAttribute, BufferedCodeBuilder.Model, CodeImpl { /** diff --git a/src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.java b/src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.java index de501b4ec6e01..4f3605c63cdac 100755 --- a/src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/attribute/CodeAttribute.java @@ -27,6 +27,7 @@ import jdk.classfile.Attribute; import jdk.classfile.CodeModel; +import jdk.classfile.Label; import jdk.classfile.impl.BoundAttribute; /** @@ -47,4 +48,6 @@ public sealed interface CodeAttribute extends Attribute, CodeMode * {@return the bytes (bytecode) of the code array} */ byte[] codeArray(); + + int labelToBci(Label label); } diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java index 2e2144cb23291..89b6dbe560bc1 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractBoundLocalVariable.java @@ -93,9 +93,10 @@ public int slot() { return code.classReader.readU2(offset + 8); } - public void writeTo(BufWriter b, CodeBuilder builder) { - int startBci = builder.labelToBci(startScope()); - int endBci = builder.labelToBci(endScope()); + public void writeTo(BufWriter b) { + var lc = ((BufWriterImpl)b).labelContext(); + int startBci = lc.labelToBci(startScope()); + int endBci = lc.labelToBci(endScope()); int length = endBci - startBci; b.writeU2(startBci); b.writeU2(length); diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java index 4e6654703965f..2e074bac6fe69 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java @@ -1487,9 +1487,10 @@ public Label endScope() { return endScope; } - public void writeTo(BufWriter b, CodeBuilder builder) { - int startBci = builder.labelToBci(startScope()); - int endBci = builder.labelToBci(endScope()); + public void writeTo(BufWriter b) { + var lc = ((BufWriterImpl)b).labelContext(); + int startBci = lc.labelToBci(startScope()); + int endBci = lc.labelToBci(endScope()); int length = endBci - startBci; b.writeU2(startBci); b.writeU2(length); diff --git a/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java index 77f5ea2739b9c..83cb1fc49871a 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BufWriterImpl.java @@ -42,7 +42,7 @@ public final class BufWriterImpl implements BufWriter { private final ConstantPoolBuilder constantPool; - private LabelResolver labelResolver; + private LabelContext labelContext; private ClassEntry thisClass; byte[] elems; int offset = 0; @@ -61,12 +61,12 @@ public ConstantPoolBuilder constantPool() { return constantPool; } - public LabelResolver labelResolver() { - return labelResolver; + public LabelContext labelContext() { + return labelContext; } - public void setLabelResolver(LabelResolver labelResolver) { - this.labelResolver = labelResolver; + public void setLabelContext(LabelContext labelContext) { + this.labelContext = labelContext; } @Override public boolean canWriteDirect(ConstantPool other) { diff --git a/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java index 1ca57c551e2d8..d4d0d5b0f614b 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BufferedCodeBuilder.java @@ -196,11 +196,6 @@ public Optional parent() { return Optional.empty(); } - @Override - public int labelToBci(Label label) { - throw new UnsupportedOperationException("nyi"); - } - @Override public void writeTo(DirectMethodBuilder builder) { builder.withCode(new Consumer<>() { diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java index 54e40350c03c0..d03e1cc1edcd4 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java @@ -36,11 +36,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.function.BiConsumer; import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import jdk.classfile.Annotation; @@ -50,8 +48,8 @@ import jdk.classfile.AnnotationValue.*; import jdk.classfile.Attribute; import jdk.classfile.ClassModel; -import jdk.classfile.CodeModel; import jdk.classfile.ClassPrinter.*; +import jdk.classfile.CodeModel; import jdk.classfile.Instruction; import jdk.classfile.MethodModel; import jdk.classfile.TypeAnnotation; @@ -512,7 +510,7 @@ private static Node elementValuePairsToTree(List evps) { new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value()))))); } - private static Stream convertVTIs(LabelResolver lr, List vtis) { + private static Stream convertVTIs(CodeAttribute lr, List vtis) { return vtis.stream().mapMulti((vti, ret) -> { switch (vti) { case SimpleVerificationTypeInfo s -> { @@ -549,7 +547,7 @@ public static MapNode modelToTree(CompoundElement model, Verbosity verbosity) case ClassModel cm -> classToTree(cm, verbosity); case FieldModel fm -> fieldToTree(fm, verbosity); case MethodModel mm -> methodToTree(mm, verbosity); - case CodeModel com -> codeToTree((CodeImpl)com, verbosity); + case CodeModel com -> codeToTree((CodeAttribute)com, verbosity); }; } @@ -647,7 +645,7 @@ private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) { } } - private static Node frameToTree(ConstantDesc name, LabelResolver lr, StackMapFrameInfo f) { + 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()))); @@ -669,14 +667,14 @@ public static MapNode methodToTree(MethodModel m, Verbosity verbosity) { leaf("method type", m.methodType().stringValue()), list("attributes", "attribute", m.attributes().stream().map(Attribute::attributeName))) .with(attributesToTree(m.attributes(), verbosity)) - .with(codeToTree((CodeImpl)m.code().orElse(null), verbosity)); + .with(codeToTree((CodeAttribute)m.code().orElse(null), verbosity)); } - private static MapNode codeToTree(CodeImpl com, Verbosity 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", ((CodeAttribute)com).maxStack())); - codeNode.with(leaf("max locals", ((CodeAttribute)com).maxLocals())); + 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>(); @@ -994,7 +992,7 @@ private static Node[] localInfoToTree(List locals, int slot, return new Node[0]; } - private static void forEachOffset(TypeAnnotation ta, LabelResolver lr, BiConsumer consumer) { + 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); diff --git a/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java index ed7a342e89df4..ced703feb846b 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/DirectCodeBuilder.java @@ -238,7 +238,7 @@ public void writeBody(BufWriter b) { // @@@ Filter out LVs whose boundary labels are not defined? b.writeU2(localVariables.size()); for (LocalVariable l : localVariables) { - l.writeTo(b, DirectCodeBuilder.this); + l.writeTo(b); } // @@@ If we're filtering, then also have to patch count } @@ -254,7 +254,7 @@ public void writeBody(BufWriter b) { // @@@ Filter out LVs whose boundary labels are not defined? b.writeU2(localVariableTypes.size()); for (LocalVariableType l : localVariableTypes) { - l.writeTo(b, DirectCodeBuilder.this); + l.writeTo(b); } // @@@ If we're filtering, then also have to patch count } @@ -272,6 +272,7 @@ public void writeBody(BufWriter b) { @Override public void writeBody(BufWriter b) { BufWriterImpl buf = (BufWriterImpl) b; + buf.setLabelContext(DirectCodeBuilder.this); int codeLength = curPc(); int maxStack, maxLocals; @@ -304,14 +305,13 @@ else if (canReuseStackmaps) { } attributes.withAttribute(stackMapAttr); - buf.setLabelResolver(DirectCodeBuilder.this); buf.writeU2(maxStack); buf.writeU2(maxLocals); buf.writeInt(codeLength); buf.writeBytes(bytecodesBufWriter); writeExceptionHandlers(b); attributes.writeTo(b); - buf.setLabelResolver(null); + buf.setLabelContext(null); } }; } diff --git a/src/java.base/share/classes/jdk/classfile/impl/LabelContext.java b/src/java.base/share/classes/jdk/classfile/impl/LabelContext.java index c7b8fb1d9905c..05aaba1decdf4 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/LabelContext.java +++ b/src/java.base/share/classes/jdk/classfile/impl/LabelContext.java @@ -29,9 +29,10 @@ /** * LabelContext */ -public sealed interface LabelContext extends LabelResolver +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/classfile/impl/LabelResolver.java b/src/java.base/share/classes/jdk/classfile/impl/LabelResolver.java deleted file mode 100755 index 27589c328ec5d..0000000000000 --- a/src/java.base/share/classes/jdk/classfile/impl/LabelResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact 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.classfile.impl; - -import jdk.classfile.CodeModel; -import jdk.classfile.Label; - -/** - * LabelResolver - */ -public sealed interface LabelResolver permits CodeModel, LabelContext { - int labelToBci(Label label); -} diff --git a/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java index f6932667f1a63..30067a48ff193 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java @@ -70,9 +70,4 @@ public Optional original() { public Label newLabel() { return terminal.newLabel(); } - - @Override - public int labelToBci(Label label) { - return terminal.labelToBci(label); - } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java index 21555f86080b4..c175cd3c43511 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapDecoder.java @@ -95,7 +95,7 @@ public static List initFrameLocals(ClassEntry thisClass, S public static void writeFrames(BufWriter b, List entries) { var buf = (BufWriterImpl)b; - var dcb = (DirectCodeBuilder)buf.labelResolver(); + var dcb = (DirectCodeBuilder)buf.labelContext(); var mi = dcb.methodInfo(); var prevLocals = StackMapDecoder.initFrameLocals(buf.thisClass(), mi.methodName().stringValue(), @@ -166,7 +166,7 @@ private static void writeTypeInfo(BufWriterImpl bw, VerificationTypeInfo vti) { case ObjectVerificationTypeInfo ovti -> bw.writeIndex(ovti.className()); case UninitializedVerificationTypeInfo uvti -> - bw.writeU2(bw.labelResolver().labelToBci(uvti.newTarget())); + bw.writeU2(bw.labelContext().labelToBci(uvti.newTarget())); } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java index 69a4c8fc16192..0c73d0729f39d 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java +++ b/src/java.base/share/classes/jdk/classfile/impl/UnboundAttribute.java @@ -811,7 +811,7 @@ public UnboundTypeAnnotation(TargetInfo targetInfo, List targ this.elements = List.copyOf(elements); } - private int labelToBci(LabelResolver lr, Label label) { + 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); @@ -819,7 +819,7 @@ private int labelToBci(LabelResolver lr, Label label) { @Override public void writeTo(BufWriter buf) { - LabelResolver lr = ((BufWriterImpl) buf).labelResolver(); + LabelContext lr = ((BufWriterImpl) buf).labelContext(); // target_type buf.writeU1(targetInfo.targetType().targetTypeValue()); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java index 3943c5ff97572..0e1f4d6d95bc8 100755 --- a/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariable.java @@ -80,5 +80,5 @@ default ClassDesc typeSymbol() { */ Label endScope(); - void writeTo(BufWriter buf, CodeBuilder labelContext); + void writeTo(BufWriter buf); } diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java index edf6ce8025d82..f9d53018f7165 100755 --- a/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/LocalVariableType.java @@ -77,5 +77,5 @@ default Signature signatureSymbol() { */ Label endScope(); - void writeTo(BufWriter buf, CodeBuilder labelContext); + void writeTo(BufWriter buf); } diff --git a/test/jdk/jdk/classfile/helpers/ClassRecord.java b/test/jdk/jdk/classfile/helpers/ClassRecord.java index 2c90487ec03e4..4477426ed5951 100644 --- a/test/jdk/jdk/classfile/helpers/ClassRecord.java +++ b/test/jdk/jdk/classfile/helpers/ClassRecord.java @@ -69,7 +69,6 @@ import jdk.classfile.constantpool.PoolEntry; import jdk.classfile.constantpool.StringEntry; import jdk.classfile.constantpool.Utf8Entry; -import jdk.classfile.impl.LabelResolver; import jdk.classfile.instruction.*; import static java.util.stream.Collectors.toMap; @@ -301,7 +300,7 @@ public static AttributesRecord ofStreamingElements(Supplier 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(), ((CodeModel)a)::elementStream, (LabelResolver)a, new CodeNormalizerHelper(a.codeArray()), cf)), + 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), @@ -427,7 +426,7 @@ public record CodeAttributesRecord( Set runtimeVisibleTypeAnnotationsAttribute, Set runtimeInvisibleTypeAnnotationsAttribute) { - static CodeAttributesRecord ofStreamingElements(Supplier> elements, LabelResolver lc, CodeNormalizerHelper code, CompatibilityFilter... cf) { + static CodeAttributesRecord ofStreamingElements(Supplier> elements, CodeAttribute lc, CodeNormalizerHelper code, CompatibilityFilter... cf) { int[] p = {0}; var characterRanges = new HashSet(); var lineNumbers = new HashSet(); @@ -455,7 +454,7 @@ static CodeAttributesRecord ofStreamingElements(Supplier 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()), @@ -523,7 +522,7 @@ public record CharacterRangeRecord( int characterRangeEnd, int flags) { - public static CharacterRangeRecord ofCharacterRange(CharacterRange cr, LabelResolver lc, CodeNormalizerHelper code) { + 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()); } @@ -637,7 +636,7 @@ public record CodeRecord( Set exceptionHandlers, CodeAttributesRecord codeAttributes) { - private static List instructions(Supplier> elements, CodeNormalizerHelper code, LabelResolver lr) { + 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; @@ -702,7 +701,7 @@ else if ((ins instanceof StoreInstruction local)) { }).toList(); } - public static CodeRecord ofStreamingElements(int maxStack, int maxLocals, int codeLength, Supplier> elements, LabelResolver lc, CodeNormalizerHelper codeHelper, CompatibilityFilter... cf) { + 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), @@ -718,9 +717,9 @@ public static CodeRecord ofCodeAttribute(CodeAttribute a, CompatibilityFilter... By_ClassBuilder.isNotDirectlyComparable(cf, a.maxStack()), By_ClassBuilder.isNotDirectlyComparable(cf, a.maxLocals()), By_ClassBuilder.isNotDirectlyComparable(cf, a.codeLength()), - instructions(((CodeModel)a)::elementStream, codeHelper, a), - a.exceptionHandlers().stream().map(eh -> ExceptionHandlerRecord.ofExceptionCatch(eh, codeHelper, (LabelResolver)a)).collect(toSet()), - CodeAttributesRecord.ofAttributes(((CodeModel) a)::attributes, codeHelper, a, cf)); + 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 static CodeRecord ofClassFileCodeAttribute(com.sun.tools.classfile.Code_attribute a, com.sun.tools.classfile.ConstantPool p, CompatibilityFilter... cf) { @@ -819,7 +818,7 @@ public record ExceptionHandlerRecord( int handlerIndex, ConstantPoolEntryRecord catchType) { - public static ExceptionHandlerRecord ofExceptionCatch(ExceptionCatch et, CodeNormalizerHelper code, LabelResolver labelContext) { + 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())), @@ -888,7 +887,7 @@ public record LocalVariableRecord( String descriptor, int slot) { - public static LocalVariableRecord ofLocalVariable(LocalVariable lv, LabelResolver lc, CodeNormalizerHelper code) { + 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())), @@ -923,7 +922,7 @@ public record LocalVariableTypeRecord( String signature, int index) { - public static LocalVariableTypeRecord ofLocalVariableType(LocalVariableType lvt, LabelResolver lc, CodeNormalizerHelper code) { + 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())), @@ -1147,7 +1146,7 @@ public static TypeAnnotationRecord ofTypeAnnotation(TypeAnnotation ann) { return ofTypeAnnotation(ann, null, null); } - public static TypeAnnotationRecord ofTypeAnnotation(TypeAnnotation ann, LabelResolver lr, CodeNormalizerHelper code) { + public static TypeAnnotationRecord ofTypeAnnotation(TypeAnnotation ann, CodeAttribute lr, CodeNormalizerHelper code) { return new TypeAnnotationRecord( ann.targetInfo().targetType().targetTypeValue(), TargetInfoRecord.ofTargetInfo(ann.targetInfo(), lr, code), @@ -1165,7 +1164,7 @@ public static TypeAnnotationRecord ofClassFileTypeAnnotation(com.sun.tools.class public interface TargetInfoRecord { - public static TargetInfoRecord ofTargetInfo(TypeAnnotation.TargetInfo tiu, LabelResolver lr, CodeNormalizerHelper code) { + 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) { diff --git a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java index a03cdf68e11b9..85b630e440ff8 100644 --- a/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java +++ b/test/jdk/jdk/classfile/helpers/CorpusTestHelper.java @@ -69,14 +69,14 @@ public void writeBody(BufWriter b) { @Override public void writeBody(BufWriter b) { b.writeU2(1); - lv.writeTo(b, dcob); + 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, dcob); + lvt.writeTo(b); } }); default -> cob.with(coe); From 15ba3cd924190de670de05663bc53835a3d4d54a Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 29 Aug 2022 20:06:50 +0200 Subject: [PATCH 046/190] fixed LabelsRemapper --- .../share/classes/jdk/classfile/transforms/LabelsRemapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java b/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java index f09b20a51dc9b..c848c0207d6c2 100644 --- a/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java +++ b/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java @@ -75,7 +75,7 @@ public static CodeTransform remapLabels() { remap(map, lt.label(), cob)); case ExceptionCatch ec -> cob.exceptionCatch( - remap(map, ec.tryEnd(), cob), + remap(map, ec.tryStart(), cob), remap(map, ec.tryEnd(), cob), remap(map, ec.handler(), cob), ec.catchType()); From 674791a3a29340614db85821d54a15c9c63c1baa Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 30 Aug 2022 13:55:20 +0200 Subject: [PATCH 047/190] updated AdvancedTransformationTest according to jdk.jfr --- .../AdvancedTransformationsTest.java | 142 ++++++++---------- 1 file changed, 64 insertions(+), 78 deletions(-) diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index fca98d4660a4d..e63e6f92beacb 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -33,8 +33,8 @@ import java.util.Set; import jdk.classfile.ClassHierarchyResolver; import jdk.classfile.Classfile; +import jdk.classfile.CodeElement; import jdk.classfile.CodeModel; -import jdk.classfile.CodeTransform; import jdk.classfile.MethodModel; import jdk.classfile.TypeKind; import jdk.classfile.impl.StackMapGenerator; @@ -50,14 +50,14 @@ import java.util.stream.Collectors; import jdk.classfile.Attributes; import jdk.classfile.ClassModel; -import jdk.classfile.CodeElement; +import jdk.classfile.ClassTransform; import jdk.classfile.FieldModel; import jdk.classfile.Signature; import jdk.classfile.attribute.ModuleAttribute; import jdk.classfile.impl.AbstractInstruction; import jdk.classfile.impl.RawBytecodeHelper; -import jdk.classfile.impl.Util; import jdk.classfile.instruction.InvokeInstruction; +import jdk.classfile.instruction.StoreInstruction; import java.lang.reflect.AccessFlag; import jdk.classfile.transforms.LabelsRemapper; import jdk.classfile.jdktypes.ModuleDesc; @@ -286,80 +286,66 @@ private static byte[] instrument(ClassModel target, ClassModel instrumentor, Pre 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 Classfile.build(target.thisClass().asSymbol(), clb -> { - target.forEachElement(cle -> { - CodeModel instrumentorCodeModel; - if (cle instanceof MethodModel mm && ((instrumentorCodeModel = instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue())) != null)) { - clb.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(), mm.flags().flagsMask(), mb -> mm.forEachElement(me -> { - if (me instanceof CodeModel targetCodeModel) { - //instrumented methods are merged - var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); - var instrumentorCodeRemapperAndShifter = - instrumentorClassRemapper.codeTransform() - .andThen(instrumentorLocalsShifter); - CodeTransform invokeInterceptor - = (codeBuilder, instrumentorCodeElement) -> { - if (instrumentorCodeElement instanceof InvokeInstruction inv - && instrumentor.thisClass().asInternalName().equals(inv.owner().asInternalName()) - && mm.methodName().stringValue().equals(inv.name().stringValue()) - && mm.methodType().stringValue().equals(inv.type().stringValue())) { - //store stacked arguments (in reverse order) - record Arg(TypeKind tk, int slot) {} - var storeStack = new LinkedList(); - int slot = 0; - if (!mm.flags().has(AccessFlag.STATIC)) { - storeStack.add(new Arg(TypeKind.ReferenceType, slot++)); - } - var mType = mm.methodTypeSymbol(); - for (int i = 0; i < mType.parameterCount(); i++) { - var tk = TypeKind.fromDescriptor(mType.parameterType(i).descriptorString()); - storeStack.add(new Arg(tk, slot)); - slot += tk.slotSize(); - } - while (!storeStack.isEmpty()) { - var arg = storeStack.removeLast(); - codeBuilder.storeInstruction(arg.tk, arg.slot); - } - var endLabel = codeBuilder.newLabel(); - //inlined target locals must be shifted based on the actual instrumentor locals shifter next free slot, relabeled and returns must be replaced with goto - var sequenceTransform = - instrumentorLocalsShifter.fork() - .andThen(LabelsRemapper.remapLabels()) - .andThen((innerBuilder, shiftedRelabeledTargetCode) -> { - if (shiftedRelabeledTargetCode.codeKind() == CodeElement.Kind.RETURN) { - innerBuilder.goto_w(endLabel); - } - else - innerBuilder.with(shiftedRelabeledTargetCode); - }) - .andThen(CodeTransform.endHandler(b -> codeBuilder.labelBinding(endLabel))); - codeBuilder.transform(targetCodeModel, sequenceTransform); - } - else - codeBuilder.with(instrumentorCodeElement); - }; - mb.transformCode(instrumentorCodeModel, - invokeInterceptor.andThen(instrumentorCodeRemapperAndShifter)); - } - else { - mb.with(me); - } - })); - } - else { - clb.with(cle); - } - }); - var remapperConsumer = instrumentorClassRemapper.classTransform().resolve(clb).consumer(); - instrumentor.forEachElement(cle -> { - //remaining instrumentor fields and methods are remapped and moved - if (cle instanceof FieldModel fm && !targetFieldNames.contains(fm.fieldName().stringValue())) { - remapperConsumer.accept(cle); - } - else if (cle instanceof MethodModel mm && !"".equals(mm.methodName().stringValue()) && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue())) { - remapperConsumer.accept(cle); - } - }); - }); + return target.transform( + ClassTransform.transformingMethods( + instrumentedMethodsFilter, + (mb, me) -> { + if (me instanceof CodeModel targetCodeModel) { + var mm = targetCodeModel.parent().get(); + var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); + //instrumented methods code is taken from instrumentor + mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()), + //locals shifter monitors locals + instrumentorLocalsShifter + .andThen((codeBuilder, instrumentorCodeElement) -> { + //all invocations of target methods from instrumentor are inlined + if (instrumentorCodeElement instanceof InvokeInstruction inv + && instrumentor.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); + + var endLabel = codeBuilder.newLabel(); + //inlined target locals must be shifted based on the actual instrumentor locals + codeBuilder.transform(targetCodeModel, instrumentorLocalsShifter.fork() + .andThen(LabelsRemapper.remapLabels()) + .andThen((innerBuilder, shiftedTargetCode) -> { + //returns must be replaced with jump to the end of the inlined method + if (shiftedTargetCode.codeKind() == CodeElement.Kind.RETURN) + innerBuilder.goto_(endLabel); + else + innerBuilder.with(shiftedTargetCode); + })); + codeBuilder.labelBinding(endLabel); + } else + codeBuilder.with(instrumentorCodeElement); + }) + //all references to the instrumentor class are remapped to target class + .andThen(instrumentorClassRemapper.codeTransform())); + } 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.classTransform()))))); } } From 1bca888f1c3a6081f1783ea51eacaa6b40d94948 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 31 Aug 2022 15:13:53 +0200 Subject: [PATCH 048/190] Classfile api stacktracker branch (#34) --- .../classes/jdk/classfile/CodeBuilder.java | 18 +- .../constantpool/ConstantDynamicEntry.java | 8 + .../classfile/constantpool/DoubleEntry.java | 8 + .../classfile/constantpool/FloatEntry.java | 8 + .../classfile/constantpool/IntegerEntry.java | 8 + .../constantpool/LoadableConstantEntry.java | 8 + .../jdk/classfile/constantpool/LongEntry.java | 8 + .../classfile/impl/BlockCodeBuilderImpl.java | 1 + .../jdk/classfile/impl/CatchBuilderImpl.java | 2 +- .../classfile/impl/TerminalCodeBuilder.java | 2 +- .../impl/TransformingCodeBuilder.java | 94 +++++ .../instruction/ConstantInstruction.java | 27 ++ .../classfile/transforms/StackTracker.java | 337 ++++++++++++++++++ test/jdk/jdk/classfile/StackTrackerTest.java | 108 ++++++ .../helpers/RebuildingTransformation.java | 5 +- 15 files changed, 637 insertions(+), 5 deletions(-) create mode 100644 src/java.base/share/classes/jdk/classfile/impl/TransformingCodeBuilder.java create mode 100644 src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java create mode 100644 test/jdk/jdk/classfile/StackTrackerTest.java diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index 56791189ee44d..d6e9b38d22a54 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -83,6 +83,7 @@ import static java.util.Objects.requireNonNull; import static jdk.classfile.impl.BytecodeHelpers.handleDescToHandleInfo; +import jdk.classfile.impl.TransformingCodeBuilder; /** * A builder for code attributes (method bodies). Builders are not created @@ -152,6 +153,22 @@ public sealed interface CodeBuilder */ int allocateLocal(TypeKind typeKind); + /** + * Builds code fragment generated by the handler and synchronously transformed. + * + * @param transform the transform to apply to the code generated by the handler + * @param handler handler that receives a {@linkplain CodeBuilder} to + * generate 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. */ @@ -1388,7 +1405,6 @@ default CodeBuilder tableswitch(Label defaultTarget, List cases) { return tableSwitchInstruction(low, high, defaultTarget, cases); } - // Structured conveniences: // allocLocal(type) diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.java index 167cf1262475c..1632b7dd06697 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ConstantDynamicEntry.java @@ -24,6 +24,7 @@ */ package jdk.classfile.constantpool; +import jdk.classfile.TypeKind; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDesc; import java.lang.constant.DynamicConstantDesc; @@ -49,4 +50,11 @@ default DynamicConstantDesc asSymbol() { .map(LoadableConstantEntry::constantValue) .toArray(ConstantDesc[]::new)); } + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return TypeKind.fromDescriptor(type().stringValue()); + } } diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java index a8e901930d8e2..15f163b76281f 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/DoubleEntry.java @@ -24,6 +24,7 @@ */ package jdk.classfile.constantpool; +import jdk.classfile.TypeKind; import jdk.classfile.impl.ConcreteEntry; /** @@ -38,4 +39,11 @@ sealed public interface DoubleEntry * {@return the double value} */ double doubleValue(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return TypeKind.DoubleType; + } } diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java index 921f8a3632a8b..e2bc575540ce6 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/FloatEntry.java @@ -24,6 +24,7 @@ */ package jdk.classfile.constantpool; +import jdk.classfile.TypeKind; import jdk.classfile.impl.ConcreteEntry; /** @@ -39,4 +40,11 @@ sealed public interface FloatEntry */ float floatValue(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return TypeKind.FloatType; + } } diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java index 7a633134cf466..6b4ec0dd52d5a 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/IntegerEntry.java @@ -24,6 +24,7 @@ */ package jdk.classfile.constantpool; +import jdk.classfile.TypeKind; import jdk.classfile.impl.ConcreteEntry; /** @@ -38,4 +39,11 @@ sealed public interface IntegerEntry * {@return the integer value} */ int intValue(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return TypeKind.IntType; + } } diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java index 42ee4aae97d14..69f3878ca0531 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/LoadableConstantEntry.java @@ -25,6 +25,7 @@ package jdk.classfile.constantpool; import java.lang.constant.ConstantDesc; +import jdk.classfile.TypeKind; /** * Marker interface for constant pool entries suitable for loading via the @@ -37,4 +38,11 @@ sealed public interface LoadableConstantEntry extends PoolEntry * {@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/classfile/constantpool/LongEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.java index 4338f6a794748..42bdbcffde04d 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/LongEntry.java @@ -24,6 +24,7 @@ */ package jdk.classfile.constantpool; +import jdk.classfile.TypeKind; import jdk.classfile.impl.ConcreteEntry; /** @@ -38,4 +39,11 @@ sealed public interface LongEntry * {@return the long value} */ long longValue(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return TypeKind.LongType; + } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java index ad6a10fae763f..7ff6ac341bf4a 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java @@ -81,6 +81,7 @@ private int topLocal(CodeBuilder parent) { case ChainedCodeBuilder b -> topLocal(b.terminal); case DirectCodeBuilder b -> b.curTopLocal(); case BufferedCodeBuilder b -> b.curTopLocal(); + case TransformingCodeBuilder b -> topLocal(b.delegate); }; } diff --git a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java index 30d7284aa87c7..bfd740ee2dda5 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java @@ -74,13 +74,13 @@ public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, 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/classfile/instruction/ConstantInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java index 7810ba1189e44..a15a638bc107a 100755 --- a/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java @@ -30,6 +30,8 @@ import jdk.classfile.CodeModel; import jdk.classfile.Instruction; import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.constantpool.ConstantDynamicEntry; import jdk.classfile.constantpool.LoadableConstantEntry; import jdk.classfile.impl.AbstractInstruction; import jdk.classfile.impl.Util; @@ -49,6 +51,11 @@ sealed public interface ConstantInstruction extends Instruction { */ ConstantDesc constantValue(); + /** + * {@return the type of the constant} + */ + TypeKind typeKind(); + /** * Models an "intrinsic constant" instruction (e.g., {@code * aload_0}). @@ -56,6 +63,12 @@ sealed public interface ConstantInstruction extends Instruction { sealed interface IntrinsicConstantInstruction extends ConstantInstruction permits AbstractInstruction.UnboundIntrinsicConstantInstruction { + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return opcode().primaryTypeKind(); + } } /** @@ -68,6 +81,13 @@ sealed interface ArgumentConstantInstruction extends ConstantInstruction @Override Integer constantValue(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return opcode().primaryTypeKind(); + } } /** @@ -82,6 +102,13 @@ sealed interface LoadConstantInstruction extends ConstantInstruction * {@return the constant value} */ LoadableConstantEntry constantEntry(); + + /** + * {@return the type of the constant} + */ + default TypeKind typeKind() { + return constantEntry().typeKind(); + } } /** diff --git a/src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java b/src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java new file mode 100644 index 0000000000000..26b910b953f3a --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute 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.classfile.transforms; + +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.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.*; + +/** + * + */ +public final class StackTracker implements CodeTransform { + + + /** + * 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 + */ + public Optional> stack() { + return Optional.ofNullable(fork()); + } + + /** + * 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 + */ + public Optional maxStackSize() { + return Optional.ofNullable(maxSize); + } + + 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; + + private 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/test/jdk/jdk/classfile/StackTrackerTest.java b/test/jdk/jdk/classfile/StackTrackerTest.java new file mode 100644 index 0000000000000..fad581e3458c8 --- /dev/null +++ b/test/jdk/jdk/classfile/StackTrackerTest.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. + */ + +/* + * @test + * @summary Testing StackTracker in CodeBuilder. + * @run testng StackTrackerTest + */ +import java.util.List; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.constant.ConstantDescs; +import jdk.classfile.*; +import jdk.classfile.transforms.StackTracker; +import static jdk.classfile.TypeKind.*; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * StackTrackerTest + */ +@Test +public class StackTrackerTest { + + public void testStackTracker() { + Classfile.build(ClassDesc.of("Foo"), clb -> + clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_Void), 0, cob -> { + var stackTracker = new StackTracker(); + cob.transforming(stackTracker, stcb -> { + assertEquals(stackTracker.stack().get(), List.of()); + stcb.aload(0); + assertEquals(stackTracker.stack().get(), List.of(ReferenceType)); + stcb.lconst_0(); + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + stcb.trying(tryb -> { + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + tryb.iconst_1(); + assertEquals(stackTracker.stack().get(), List.of(IntType, LongType, ReferenceType)); + tryb.ifThen(thb -> { + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + thb.constantInstruction(ClassDesc.of("Phee")); + assertEquals(stackTracker.stack().get(), List.of(ReferenceType, LongType, ReferenceType)); + thb.athrow(); + assertFalse(stackTracker.stack().isPresent()); + }); + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + tryb.return_(); + assertFalse(stackTracker.stack().isPresent()); + }, catchb -> catchb.catching(ClassDesc.of("Phee"), cb -> { + assertEquals(stackTracker.stack().get(), List.of(ReferenceType)); + cb.athrow(); + assertFalse(stackTracker.stack().isPresent()); + })); + }); + assertTrue(stackTracker.maxStackSize().isPresent()); + assertEquals((int)stackTracker.maxStackSize().get(), 4); + })); + } + + public void testTrackingLost() { + Classfile.build(ClassDesc.of("Foo"), clb -> + clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_Void), 0, cob -> { + var stackTracker = new StackTracker(); + cob.transforming(stackTracker, stcb -> { + assertEquals(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/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index c385bbab22646..d108dd584dd0d 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -34,6 +34,7 @@ import jdk.classfile.instruction.*; import jdk.classfile.jdktypes.ModuleDesc; import jdk.classfile.jdktypes.PackageDesc; +import jdk.classfile.transforms.StackTracker; class RebuildingTransformation { @@ -70,7 +71,7 @@ static byte[] transform(ClassModel clm) { for (var me : mm) { switch (me) { case AccessFlags af -> mb.withFlags(af.flagsMask()); - case CodeModel com -> mb.withCode(cob -> { + case CodeModel com -> mb.withCode(cb -> cb.transforming(new StackTracker(), cob -> { var labels = new HashMap(); for (var coe : com) { switch (coe) { @@ -453,7 +454,7 @@ else switch (i.constantValue()) { StackMapTableAttribute.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))); From 7919e2a70489330cb5fe82f2e7aed18adcd914c6 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 31 Aug 2022 16:30:49 +0200 Subject: [PATCH 049/190] added JCov report link to README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e2c330be5f03..110f2731f8790 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Test can be selectivelly executed as: make test TEST=jdk/classfile -See [doc/testing.md](doc/testing.md) for more information about JDK testing. +See [online JCov report](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openjdk/jdk-sandbox/classfile-api-javadoc-branch/jcov-report/jdk/classfile/package-summary.html) for more information about Classfile API tests coverage +and [doc/testing.md](doc/testing.md) for more information about JDK testing. ### Benchmarking From c148bc28f6462d5558a0cbe025f758430a4ffac3 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 1 Sep 2022 15:19:16 +0200 Subject: [PATCH 050/190] cleanup in CodeBuilder::constantInstruction added support for ConstatntDescs.NULL added CodeBuilder::ldc(LoadableConstantEntry) --- .../classes/jdk/classfile/CodeBuilder.java | 156 +++++++----------- .../share/classes/jdk/classfile/Opcode.java | 3 +- .../classfile/impl/AbstractInstruction.java | 15 -- .../jdk/classfile/impl/BytecodeHelpers.java | 81 +++------ .../instruction/ConstantInstruction.java | 2 +- 5 files changed, 82 insertions(+), 175 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java index d6e9b38d22a54..7eb3b1f1e672c 100755 --- a/src/java.base/share/classes/jdk/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/CodeBuilder.java @@ -27,6 +27,7 @@ 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; @@ -526,95 +527,45 @@ default CodeBuilder operatorInstruction(Opcode opcode) { } default CodeBuilder constantInstruction(Opcode opcode, ConstantDesc value) { - if (opcode == null) // Infer opcode - return constantInstruction(value); - BytecodeHelpers.validateValue(opcode, value); - - if (opcode == Opcode.ACONST_NULL) { - with(ConstantInstruction.ofIntrinsic(Opcode.ACONST_NULL)); - } - else if (opcode == Opcode.SIPUSH && value instanceof Integer iVal) { - with(ConstantInstruction.ofArgument(Opcode.SIPUSH, iVal)); - } - else if (opcode == Opcode.SIPUSH && value instanceof Long lVal) { - with(ConstantInstruction.ofArgument(Opcode.SIPUSH, (int) (lVal & (long)0xffff))); - } - else if (opcode == Opcode.BIPUSH && value instanceof Integer iVal) { - with(ConstantInstruction.ofArgument(Opcode.BIPUSH, iVal)); - } - else if (opcode == Opcode.BIPUSH && value instanceof Long lVal) { - with(ConstantInstruction.ofArgument(Opcode.BIPUSH, (int) (lVal & (long)0xff))); - } - else if (opcode == Opcode.LDC || opcode == Opcode.LDC_W || opcode == Opcode.LDC2_W) { - with(ConstantInstruction.ofLoad(opcode, BytecodeHelpers.constantEntry(constantPool(), value))); - } - else { - Opcode known = BytecodeHelpers.constantsToOpcodes.get(value); - if (known == null) - throw new AssertionError("Couldn't determine which opcode to use to load " + value); - with(ConstantInstruction.ofIntrinsic(known)); - } - - return this; - } - - default CodeBuilder constantInstruction(int value) { - return with(switch (value) { - case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); - case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); - case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); - case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); - case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); - case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); - case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); - default -> { - if (value >= -128 && value <= 127) { - yield ConstantInstruction.ofArgument(Opcode.BIPUSH, value); - } - else if (value >= -32768 && value <= 32767) { - yield ConstantInstruction.ofArgument(Opcode.SIPUSH, value); - } - else { - yield ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), 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) { - // This method must ensure any call to constant(Opcode, ConstantDesc) has a non-null Opcode. - if (value == null) { - return constantInstruction(Opcode.ACONST_NULL, null); - } - else if (value instanceof Integer iVal) { - return constantInstruction((int)iVal); - } else if (value instanceof Long lVal) { - if (lVal == 0) - return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_0)); - else if (lVal == 1) - return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_1)); - else - return constantInstruction(Opcode.LDC2_W, lVal); - } else if (value instanceof Float fVal) { - if (fVal == 0.0) - return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_0)); - else if (fVal == 1.0) - return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_1)); - else if (fVal == 2.0) - return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_2)); - else - return constantInstruction(Opcode.LDC, fVal); - } else if (value instanceof Double dVal) { - if (dVal == 0.0d) - return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_0)); - else if (dVal == 1.0d) - return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_1)); - else - return constantInstruction(Opcode.LDC2_W, dVal); - } else { - return constantInstruction(Opcode.LDC, 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 fVal == 0.0f ? fconst_0() + : fVal == 1.0f ? fconst_1() + : fVal == 2.0f ? fconst_2() + : ldc(constantPool().floatEntry(fVal)); + if (value instanceof Double dVal) + return dVal == 0.0d ? dconst_0() + : dVal == 1.0d ? dconst_1() + : ldc(constantPool().doubleEntry(dVal)); + return ldc(BytecodeHelpers.constantEntry(constantPool(), value)); } default CodeBuilder monitorInstruction(Opcode opcode) { @@ -706,7 +657,7 @@ default CodeBuilder localVariableType(int slot, String name, Signature signature // Bytecode conveniences default CodeBuilder aconst_null() { - return constantInstruction(Opcode.ACONST_NULL, null); + return with(ConstantInstruction.ofIntrinsic(Opcode.ACONST_NULL)); } default CodeBuilder aaload() { @@ -806,11 +757,11 @@ default CodeBuilder dcmpl() { } default CodeBuilder dconst_0() { - return constantInstruction(Opcode.DCONST_0, 0.0d); + return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_0)); } default CodeBuilder dconst_1() { - return constantInstruction(Opcode.DCONST_1, 1.0d); + return with(ConstantInstruction.ofIntrinsic(Opcode.DCONST_1)); } default CodeBuilder ddiv() { @@ -902,15 +853,15 @@ default CodeBuilder fcmpl() { } default CodeBuilder fconst_0() { - return constantInstruction(Opcode.FCONST_0, 0.0f); + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_0)); } default CodeBuilder fconst_1() { - return constantInstruction(Opcode.FCONST_1, 1.0f); + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_1)); } default CodeBuilder fconst_2() { - return constantInstruction(Opcode.FCONST_2, 2.0f); + return with(ConstantInstruction.ofIntrinsic(Opcode.FCONST_2)); } default CodeBuilder fdiv() { @@ -1010,31 +961,31 @@ default CodeBuilder iastore() { } default CodeBuilder iconst_0() { - return constantInstruction(Opcode.ICONST_0, 0); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_0)); } default CodeBuilder iconst_1() { - return constantInstruction(Opcode.ICONST_1, 1); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_1)); } default CodeBuilder iconst_2() { - return constantInstruction(Opcode.ICONST_2, 2); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_2)); } default CodeBuilder iconst_3() { - return constantInstruction(Opcode.ICONST_3, 3); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_3)); } default CodeBuilder iconst_4() { - return constantInstruction(Opcode.ICONST_4, 4); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_4)); } default CodeBuilder iconst_5() { - return constantInstruction(Opcode.ICONST_5, 5); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_5)); } default CodeBuilder iconst_m1() { - return constantInstruction(Opcode.ICONST_M1, -1); + return with(ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1)); } default CodeBuilder idiv() { @@ -1259,11 +1210,18 @@ default CodeBuilder lcmp() { } default CodeBuilder lconst_0() { - return constantInstruction(Opcode.LCONST_0, 0L); + return with(ConstantInstruction.ofIntrinsic(Opcode.LCONST_0)); } default CodeBuilder lconst_1() { - return constantInstruction(Opcode.LCONST_1, 1L); + 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() { diff --git a/src/java.base/share/classes/jdk/classfile/Opcode.java b/src/java.base/share/classes/jdk/classfile/Opcode.java index cc816be21fa56..011db9dd6e837 100755 --- a/src/java.base/share/classes/jdk/classfile/Opcode.java +++ b/src/java.base/share/classes/jdk/classfile/Opcode.java @@ -25,6 +25,7 @@ package jdk.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 @@ -36,7 +37,7 @@ */ public enum Opcode { NOP(Classfile.NOP, 1, CodeElement.Kind.NOP), - ACONST_NULL(Classfile.ACONST_NULL, 1, CodeElement.Kind.CONSTANT, TypeKind.ReferenceType), + ACONST_NULL(Classfile.ACONST_NULL, 1, CodeElement.Kind.CONSTANT, TypeKind.ReferenceType, 0, ConstantDescs.NULL), ICONST_M1(Classfile.ICONST_M1, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, -1), ICONST_0(Classfile.ICONST_0, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 0), ICONST_1(Classfile.ICONST_1, 1, CodeElement.Kind.CONSTANT, TypeKind.IntType, 0, 1), diff --git a/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java index 2e074bac6fe69..8cda3c760c72e 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/impl/AbstractInstruction.java @@ -1238,21 +1238,6 @@ public UnboundIntrinsicConstantInstruction(Opcode op) { constant = op.constantValue(); } - public UnboundIntrinsicConstantInstruction(byte byteVal) { - super(Opcode.BIPUSH); - constant = (int) byteVal; - } - - public UnboundIntrinsicConstantInstruction(short shortVal) { - super(Opcode.SIPUSH); - constant = (int) shortVal; - } - - public UnboundIntrinsicConstantInstruction(ConstantDesc constant) { - super(Opcode.LDC); - this.constant = constant; - } - @Override public void writeTo(DirectCodeBuilder writer) { super.writeTo(writer); diff --git a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java index cd1f2de6e082e..01147a216d0d2 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java @@ -26,9 +26,11 @@ 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.HashMap; import java.util.List; @@ -45,29 +47,11 @@ import jdk.classfile.constantpool.MethodHandleEntry; import jdk.classfile.constantpool.NameAndTypeEntry; -import static java.lang.invoke.MethodHandleInfo.REF_putStatic; -import static jdk.classfile.Opcode.IFEQ; -import static jdk.classfile.Opcode.IFGE; -import static jdk.classfile.Opcode.IFGT; -import static jdk.classfile.Opcode.IFLE; -import static jdk.classfile.Opcode.IFLT; -import static jdk.classfile.Opcode.IFNE; -import static jdk.classfile.Opcode.IFNONNULL; -import static jdk.classfile.Opcode.IFNULL; -import static jdk.classfile.Opcode.IF_ACMPEQ; -import static jdk.classfile.Opcode.IF_ACMPNE; -import static jdk.classfile.Opcode.IF_ICMPEQ; -import static jdk.classfile.Opcode.IF_ICMPGE; -import static jdk.classfile.Opcode.IF_ICMPGT; -import static jdk.classfile.Opcode.IF_ICMPLE; -import static jdk.classfile.Opcode.IF_ICMPLT; -import static jdk.classfile.Opcode.IF_ICMPNE; - /** * BytecodeHelpers */ public class BytecodeHelpers { - public static Map constantsToOpcodes = new HashMap<>(16); +// public static Map constantsToOpcodes = new HashMap<>(16); public BytecodeHelpers() { } @@ -195,22 +179,22 @@ public static Opcode arrayStoreOpcode(TypeKind tk) { public static Opcode reverseBranchOpcode(Opcode op) { return switch (op) { - case IFEQ -> IFNE; - case IFNE -> IFEQ; - case IFLT -> IFGE; - case IFGE -> IFLT; - case IFGT -> IFLE; - case IFLE -> IFGT; - case IF_ICMPEQ -> IF_ICMPNE; - case IF_ICMPNE -> IF_ICMPEQ; - case IF_ICMPLT -> IF_ICMPGE; - case IF_ICMPGE -> IF_ICMPLT; - case IF_ICMPGT -> IF_ICMPLE; - case IF_ICMPLE -> IF_ICMPGT; - case IF_ACMPEQ -> IF_ACMPNE; - case IF_ACMPNE -> IF_ACMPEQ; - case IFNULL -> IFNONNULL; - case IFNONNULL -> IFNULL; + 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); }; } @@ -287,7 +271,7 @@ public static MethodHandleEntry handleDescToHandleInfo(ConstantPoolBuilder const static MemberRefEntry toBootstrapMemberRef(ConstantPoolBuilder constantPool, int bsRefKind, ClassEntry owner, NameAndTypeEntry nat, boolean isOwnerInterface) { return isOwnerInterface ? constantPool.interfaceMethodRefEntry(owner, nat) - : bsRefKind <= REF_putStatic + : bsRefKind <= MethodHandleInfo.REF_putStatic ? constantPool.fieldRefEntry(owner, nat) : constantPool.methodRefEntry(owner, nat); } @@ -315,8 +299,8 @@ static ConstantDynamicEntry handleConstantDescToHandleInfo(ConstantPoolBuilder c public static void validateValue(Opcode opcode, ConstantDesc v) { switch (opcode) { case ACONST_NULL -> { - if (v != null) - throw new IllegalArgumentException("value must be null with opcode 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); @@ -369,25 +353,4 @@ public static LoadableConstantEntry constantEntry(ConstantPoolBuilder constantPo } throw new UnsupportedOperationException("not yet: " + constantValue); } - - private static void constantMapping(ConstantDesc c, Opcode o) { - constantsToOpcodes.put(c, o); - } - - static { - constantMapping(-1, Opcode.ICONST_M1); - constantMapping(0, Opcode.ICONST_0); - constantMapping(1, Opcode.ICONST_1); - constantMapping(2, Opcode.ICONST_2); - constantMapping(3, Opcode.ICONST_3); - constantMapping(4, Opcode.ICONST_4); - constantMapping(5, Opcode.ICONST_5); - constantMapping(0L, Opcode.LCONST_0); - constantMapping(1L, Opcode.LCONST_1); - constantMapping(0.0f, Opcode.FCONST_0); - constantMapping(1.0f, Opcode.FCONST_1); - constantMapping(2.0f, Opcode.FCONST_2); - constantMapping(0.0d, Opcode.DCONST_0); - constantMapping(1.0d, Opcode.DCONST_1); - } } diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java index a15a638bc107a..d203e93022116 100755 --- a/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java @@ -119,7 +119,7 @@ default TypeKind typeKind() { */ static IntrinsicConstantInstruction ofIntrinsic(Opcode op) { Util.checkKind(op, Kind.CONSTANT); - if (op.constantValue() == null && op != Opcode.ACONST_NULL) + if (op.constantValue() == null) throw new IllegalArgumentException(String.format("Wrong opcode specified; found %s, expected xCONST_val", op)); return new AbstractInstruction.UnboundIntrinsicConstantInstruction(op); } From aabda890ac95daa99228c8f9b69e5ef0d22dcf3a Mon Sep 17 00:00:00 2001 From: Dan Heidinga Date: Tue, 13 Sep 2022 04:33:05 -0400 Subject: [PATCH 051/190] Add List combining methods to ClassEntry (#35) * Add List combining methods to ClassEntry jdk.classfile.ClassEntry and java.lang.constant.ClassDesc are two ways of describing similar data. Often when working with Attributes, we need to create lists that combine ClassEntrys and ClassDescs into a single list This PR adds support working with such lists by adding 4 static methods to ClassEntry: * List adding(List base, List additions) * List adding(List base, CE... additions) * List addingSymbols(List base, List additions) * List addingSymbols(List base, CD...additions) This methods convert from CD to CE to create a combined List. The methods are null-hostile in the "additions" but do not check for nulls in the "base" List. The returned List is mutable so these methods can be common building blocks for composing with other CE entries. A test has been added that validates the existing behaviours. * Return immutable lists rather than mutable ones. A side-effect of this change is that the `base` List is now null-hostile as well as the List.copyOf on the comination is null-hostile. * Implement `List deduplicate(List)` This allows users to add to a list without worrying about the existing contents and in cases where they wish to apply a "no duplicates" invariant, they can deduplicate the List * Implement missing ConcreteEntry equals() methods * Basic deduplicate() test --- .../classfile/constantpool/ClassEntry.java | 74 +++++++++ .../jdk/classfile/impl/ConcreteEntry.java | 146 ++++++++++++++++++ test/jdk/jdk/classfile/ClassEntryTest.java | 146 ++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 test/jdk/jdk/classfile/ClassEntryTest.java diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java index 83d5948dc7bfc..aff990a0584a6 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java @@ -25,8 +25,14 @@ package jdk.classfile.constantpool; import java.lang.constant.ClassDesc; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import jdk.classfile.impl.ConcreteEntry; +import jdk.classfile.impl.TemporaryConstantPool; +import jdk.classfile.impl.Util; + /** * Models a {@code CONSTANT_Class_info} constant in the constant pool of a @@ -50,4 +56,72 @@ sealed public interface ClassEntry * {@return the class name, as a symbolic descriptor} */ ClassDesc asSymbol(); + + /** + * Return a List composed by appending the additions to the base list. + * @param base The base elements for the list, must not include null + * @param additions The ClassEntrys to add to the list, must not include null + * @return the combined List + */ + static List adding(List base, List additions) { + ArrayList members = new ArrayList<>(base); + members.addAll(additions); + return List.copyOf(members); + } + + /** + * Return a List composed by appending the additions to the base list. + * @param base The base elements for the list, must not include null + * @param additions The ClassEntrys to add to the list, must not include null + * @return the combined List + */ + static List adding(List base, ClassEntry... additions) { + ArrayList members = new ArrayList<>(base); + for (ClassEntry e : additions) { + members.add(e); + } + return List.copyOf(members); + } + + /** + * Return a List composed by appending the additions to the base list. + * @param base The base elements for the list, must not include null + * @param additions The ClassDescs to add to the list, must not include null + * @return the combined List + */ + static List addingSymbols(List base, List additions) { + ArrayList members = new ArrayList<>(base); + members.addAll(Util.entryList(additions)); + return List.copyOf(members); + } + + /** + * Return a List composed by appending the additions to the base list. + * @param base The base elements for the list, must not include null + * @param additions The ClassDescs to add to the list, must not include null + * @return the combined List + */ + static List addingSymbols(List base, ClassDesc...additions) { + ArrayList members = new ArrayList<>(base); + for (ClassDesc e : additions) { + members.add(TemporaryConstantPool.INSTANCE.classEntry(TemporaryConstantPool.INSTANCE.utf8Entry(Util.toInternalName(e)))); + } + return List.copyOf(members); + } + + /** + * Remove duplicate ClassEntry elements from the List. + * + * @param original The list to deduplicate + * @return a List without any duplicate ClassEntry + */ + static List deduplicate(List original) { + ArrayList newList = new ArrayList<>(original.size()); + for (ClassEntry e : original) { + if (!newList.contains(e)) { + newList.add(e); + } + } + return List.copyOf(newList); + } } diff --git a/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java b/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java index f17727e0dd964..93c2f4a756602 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ConcreteEntry.java @@ -352,6 +352,17 @@ 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 ConcreteUtf8Entry u) { + return equalsUtf8(u); + } else if (o instanceof Utf8Entry u) { + return equalsString(u.stringValue()); + } + return false; + } + public boolean equalsUtf8(ConcreteUtf8Entry u) { if (hashCode() != u.hashCode() || length() != u.length()) @@ -520,6 +531,14 @@ public Utf8Entry name() { public String asInternalName() { return ref1.stringValue(); } + + public boolean equals(Object o) { + if (o == this) { return true; } + if (o instanceof NamedEntry ne) { + return tag == ne.tag() && name().equals(ref1()); + } + return false; + } } public static final class ConcreteClassEntry extends NamedEntry implements ClassEntry { @@ -542,6 +561,17 @@ public ClassEntry clone(ConstantPoolBuilder cp) { public ClassDesc asSymbol() { return Util.toClassDesc(asInternalName()); } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ConcreteClassEntry 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 ConcretePackageEntry extends NamedEntry implements PackageEntry { @@ -559,6 +589,15 @@ public PackageEntry clone(ConstantPoolBuilder cp) { 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 ConcreteModuleEntry extends NamedEntry implements ModuleEntry { @@ -576,6 +615,15 @@ public ModuleEntry clone(ConstantPoolBuilder cp) { public ModuleDesc asSymbol() { return ModuleDesc.of(asInternalName()); } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ConcreteModuleEntry m) { + return name().equals(m.name()); + } + return false; + } } public static final class ConcreteNameAndTypeEntry extends RefsEntry @@ -599,6 +647,15 @@ public Utf8Entry type() { public NameAndTypeEntry clone(ConstantPoolBuilder cp) { return cp.canWriteDirect(constantPool) ? this : cp.natEntry(ref1, ref2); } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ConcreteNameAndTypeEntry nat) { + return name().equals(nat.name()) && type().equals(nat.type()); + } + return false; + } } public static sealed abstract class MemberRefEntry @@ -625,6 +682,17 @@ 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 MemberRefEntry m) { + return tag == m.tag() + && owner().equals(m.owner()) + && nameAndType().equals(m.nameAndType()); + } + return false; + } } public static final class ConcreteFieldRefEntry extends MemberRefEntry implements FieldRefEntry { @@ -746,6 +814,17 @@ 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 ConcreteInvokeDynamicEntry @@ -862,6 +941,16 @@ public String toString() { return tag() + " " + kind() + ":" + ((jdk.classfile.constantpool.MemberRefEntry) reference()).owner().asInternalName() + "." + ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().name().stringValue() + "-" + ((jdk.classfile.constantpool.MemberRefEntry) reference()).nameAndType().type().stringValue(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ConcreteMethodHandleEntry m) { + return kind() == m.kind() + && reference.equals(m.reference()); + } + return false; + } } public static final class ConcreteMethodTypeEntry @@ -885,6 +974,15 @@ public ConstantDesc constantValue() { 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 ConcreteMethodTypeEntry m) { + return descriptor().equals(m.descriptor()); + } + return false; + } } public static final class ConcreteStringEntry @@ -919,6 +1017,18 @@ public StringEntry clone(ConstantPoolBuilder cp) { public String toString() { return tag() + " \"" + stringValue() + "\""; } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ConcreteStringEntry s) { + // check utf8 rather allocating a string + return utf8().equals(s.utf8()); + } + return false; + } + + } static abstract sealed class PrimitiveEntry @@ -966,6 +1076,15 @@ public IntegerEntry clone(ConstantPoolBuilder cp) { public int intValue() { return value(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ConcreteIntegerEntry e) { + return intValue() == e.intValue(); + } + return false; + } } public static final class ConcreteFloatEntry extends PrimitiveEntry @@ -990,6 +1109,15 @@ public FloatEntry clone(ConstantPoolBuilder cp) { public float floatValue() { return value(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ConcreteFloatEntry e) { + return floatValue() == e.floatValue(); + } + return false; + } } public static final class ConcreteLongEntry extends PrimitiveEntry implements LongEntry { @@ -1013,6 +1141,15 @@ public LongEntry clone(ConstantPoolBuilder cp) { public long longValue() { return value(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ConcreteLongEntry e) { + return longValue() == e.longValue(); + } + return false; + } } public static final class ConcreteDoubleEntry extends PrimitiveEntry implements DoubleEntry { @@ -1036,6 +1173,15 @@ public DoubleEntry clone(ConstantPoolBuilder cp) { public double doubleValue() { return value(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ConcreteDoubleEntry e) { + return doubleValue() == e.doubleValue(); + } + return false; + } } static class CpException extends RuntimeException { diff --git a/test/jdk/jdk/classfile/ClassEntryTest.java b/test/jdk/jdk/classfile/ClassEntryTest.java new file mode 100644 index 0000000000000..667d6e8d0c54a --- /dev/null +++ b/test/jdk/jdk/classfile/ClassEntryTest.java @@ -0,0 +1,146 @@ +/* + * @test + * @summary Testing Classfile ClassEntry lists methods. + * @run testng ClassEntryTest + */ +import jdk.classfile.constantpool.ClassEntry; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.testng.Assert.*; + +public class ClassEntryTest { + + static final List additionCE = List.copyOf(ClassEntry.addingSymbols(List.of(), new ClassDesc[] {ConstantDescs.CD_void, ConstantDescs.CD_Enum, ConstantDescs.CD_Class})); + static final List additionCD = List.of(ConstantDescs.CD_void, ConstantDescs.CD_Enum, ConstantDescs.CD_Class); + static final List base = List.copyOf(additionCE); + + @Test + public void testNPECombos() { + // NPE on first param + try { + ClassEntry.adding(null, additionCE); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.adding(null, additionCE.get(1), additionCE.get(2)); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.addingSymbols(null, additionCD); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.addingSymbols(null, additionCD.get(1), additionCD.get(2)); + fail("NPE expected"); + } catch(NullPointerException e) { } + // NPE on second param + try { + ClassEntry.adding(base, (List)null); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.adding(base, (ClassEntry[])null); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.addingSymbols(base, (List)null); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.addingSymbols(base, (ClassDesc[])null); + fail("NPE expected"); + } catch(NullPointerException e) { } + } + + @Test + void combine() { + List expected = new ArrayList<>(base); + expected.addAll(additionCE); + expected = List.copyOf(expected); + // Ensure inputs are equivalent before using 'expected' as a common result + Assert.assertTrue(listCompare(additionCE, ClassEntry.addingSymbols(List.of(), additionCD))); + Assert.assertTrue(listCompare(expected, ClassEntry.adding(base, additionCE))); + Assert.assertTrue(listCompare(expected, ClassEntry.adding(base, additionCE.toArray(new ClassEntry[0])))); + Assert.assertTrue(listCompare(expected, ClassEntry.addingSymbols(base, additionCD))); + Assert.assertTrue(listCompare(expected, ClassEntry.addingSymbols(base, additionCD.toArray(new ClassDesc[0])))); + } + + boolean listCompare(List a, List b) { + if (a.size() != b.size()) return false; + + for (int i = 0; i < a.size(); i++) { + ClassEntry ca = a.get(i); + ClassEntry cb = b.get(i); + if (!ca.asSymbol().equals(cb.asSymbol())) { + return false; + } + } + return true; + } + + @Test + void throwOnNullAdditions() { + // NPE when adding a null element + ArrayList withNullElement = new ArrayList(additionCE); + withNullElement.add(null); + try { + ClassEntry.adding(base, withNullElement); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.adding(base, withNullElement.toArray(new ClassEntry[0])); + fail("NPE expected"); + } catch(NullPointerException e) { } + ArrayList withNullElementCD = new ArrayList(additionCD); + withNullElementCD.add(null); + try { + ClassEntry.addingSymbols(base, withNullElementCD); + fail("NPE expected"); + } catch(NullPointerException e) { } + try { + ClassEntry.addingSymbols(base, withNullElementCD.toArray(new ClassDesc[0])); + fail("NPE expected"); + } catch(NullPointerException e) { } + } + + @Test + void addEmpty() { + Assert.assertEquals(base, ClassEntry.adding(base, List.of())); + Assert.assertEquals(base, ClassEntry.adding(base, new ClassEntry[0])); + Assert.assertEquals(base, ClassEntry.addingSymbols(base, List.of())); + Assert.assertEquals(base, ClassEntry.addingSymbols(base, new ClassDesc[0])); + } + + @Test + void dedup() { + { + List duplicates = ClassEntry.adding(base, base); + List dedup = ClassEntry.deduplicate(duplicates); + boolean result = listCompare(base, dedup); + if (!result) { + fail("Different: " + Arrays.toString(base.toArray())+ " : " + Arrays.toString(dedup.toArray())); + } + Assert.assertTrue(result); + } + { + List duplicates = ClassEntry.addingSymbols(List.of(), additionCD); + duplicates = ClassEntry.addingSymbols(duplicates, additionCD); + List dedup = ClassEntry.deduplicate(duplicates); + boolean result = listCompare(base, dedup); + if (!result) { + fail("Different: " + Arrays.toString(base.toArray())+ " : " + Arrays.toString(dedup.toArray())); + } + Assert.assertTrue(result); + } + + } + +} From 4b153a7c094a938927b073b2e9a967bf2f8cb326 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 14 Sep 2022 11:56:52 +0200 Subject: [PATCH 052/190] package jdk.classfile.transforms cleanup and move (#36) * package jdk.classfile.transforms API cleanup * simplified CodeLocalsShifter to delegate locals allocation on CodeBuilder * added parent field to NonterminalCodeBuilder * fixed ChainedCodeBuilder::allocateLocal * jdk.classfile.transforms package rename to jdk.classfile.components * ClassPrinter move to jdk.classfile.components package --- make/RunTests.gmk | 2 +- make/test/BuildMicrobenchmark.gmk | 2 +- .../{ => components}/ClassPrinter.java | 3 +- .../ClassRemapper.java | 179 ++++++--- .../components/CodeLocalsShifter.java | 136 +++++++ .../classfile/components/CodeRelabeler.java | 158 ++++++++ .../components/CodeStackTracker.java | 374 ++++++++++++++++++ .../classfile/impl/BlockCodeBuilderImpl.java | 2 - .../classfile/impl/ChainedCodeBuilder.java | 2 +- .../jdk/classfile/impl/ClassPrinterImpl.java | 2 +- .../impl/NonterminalCodeBuilder.java | 6 +- .../jdk/classfile/impl/StackMapGenerator.java | 2 +- .../classfile/impl/verifier/VerifierImpl.java | 2 +- .../transforms/CodeLocalsShifter.java | 123 ------ .../classfile/transforms/LabelsRemapper.java | 105 ----- .../classfile/transforms/StackTracker.java | 337 ---------------- .../AdvancedTransformationsTest.java | 59 +-- test/jdk/jdk/classfile/ClassPrinterTest.java | 1 + test/jdk/jdk/classfile/StackTrackerTest.java | 26 +- test/jdk/jdk/classfile/TEST.properties | 2 +- .../examples/AnnotationsExamples.java | 2 +- .../helpers/RebuildingTransformation.java | 4 +- .../jdk/jdk/classfile/helpers/Transforms.java | 2 +- .../bench/jdk/classfile/Transforms.java | 2 +- 24 files changed, 848 insertions(+), 685 deletions(-) rename src/java.base/share/classes/jdk/classfile/{ => components}/ClassPrinter.java (97%) rename src/java.base/share/classes/jdk/classfile/{transforms => components}/ClassRemapper.java (70%) create mode 100644 src/java.base/share/classes/jdk/classfile/components/CodeLocalsShifter.java create mode 100644 src/java.base/share/classes/jdk/classfile/components/CodeRelabeler.java create mode 100644 src/java.base/share/classes/jdk/classfile/components/CodeStackTracker.java delete mode 100644 src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java delete mode 100644 src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java delete mode 100644 src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 0f4015fdc9c35..8db59ef57748d 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -599,7 +599,7 @@ define SetupRunMicroTestBody --add-exports java.base/jdk.classfile.constantpool=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.instruction=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.jdktypes=ALL-UNNAMED \ - --add-exports java.base/jdk.classfile.transforms=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.components=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.impl=ALL-UNNAMED ifneq ($$(MICRO_VM_OPTIONS)$$(MICRO_JAVA_OPTIONS), ) diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 1a3836bbb72f7..2d35cdb13dc63 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -101,7 +101,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.classfile.constantpool=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.instruction=ALL-UNNAMED \ --add-exports java.base/jdk.classfile.jdktypes=ALL-UNNAMED \ - --add-exports java.base/jdk.classfile.transforms=ALL-UNNAMED \ + --add-exports java.base/jdk.classfile.components=ALL-UNNAMED \ --add-exports java.base/jdk.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 \ diff --git a/src/java.base/share/classes/jdk/classfile/ClassPrinter.java b/src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java similarity index 97% rename from src/java.base/share/classes/jdk/classfile/ClassPrinter.java rename to src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java index 33dbca41732b3..2de3a85d56106 100644 --- a/src/java.base/share/classes/jdk/classfile/ClassPrinter.java +++ b/src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java @@ -22,13 +22,14 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package jdk.classfile; +package jdk.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.classfile.CompoundElement; import jdk.classfile.impl.ClassPrinterImpl; diff --git a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java b/src/java.base/share/classes/jdk/classfile/components/ClassRemapper.java similarity index 70% rename from src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java rename to src/java.base/share/classes/jdk/classfile/components/ClassRemapper.java index 8558ece4c799e..21240ea05455b 100644 --- a/src/java.base/share/classes/jdk/classfile/transforms/ClassRemapper.java +++ b/src/java.base/share/classes/jdk/classfile/components/ClassRemapper.java @@ -21,7 +21,7 @@ * questions. * */ -package jdk.classfile.transforms; +package jdk.classfile.components; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDesc; @@ -90,89 +90,142 @@ import jdk.classfile.instruction.ConstantInstruction.LoadConstantInstruction; /** - * + * ClassRemapper is a {@link jdk.classfile.ClassTransform}, {@link jdk.classfile.FieldTransform}, + * {@link jdk.classfile.MethodTransform} and {@link jdk.classfile.CodeTransform} + * re-mapping all class references in any form according to given map or map function. + *

+ * 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. + *

+ * Sample re-mapping class Foo to Bar: + *

+ * {@snippet lang=java : + * var classMap = Map.of(ClassDesc.of("Foo"), ClassDesc.of("Bar")); + * var classRemapper = ClassRemapper.of(classMap); + * for (var classModel : allMyClasses) { + * var remappedClassBytes = classRemapper.remapClass(classModel); + * ... + * } + * } + *

+ * Sample re-mapping all classes in com.mypackage and sub-packages to com.otherpackage: + *

+ * {@snippet lang=java : + * var classRemapper = ClassRemapper.of(cd -> + * ClassDesc.ofDescriptor(cd.descriptorString().replace("Lcom/mypackage/", "Lcom/otherpackage/"))); + * for (var classModel : allMyClasses) { + * var remappedClassBytes = classRemapper.remapClass(classModel); + * ... + * } + * } */ -public sealed interface ClassRemapper { +public sealed interface ClassRemapper extends ClassTransform { + /** + * Creates new instance of 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 ClassRemapper + */ static ClassRemapper of(Map classMap) { return of(desc -> classMap.getOrDefault(desc, desc)); } + /** + * Creates new instance of 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 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); - ClassTransform classTransform(); - - FieldTransform fieldTransform(); + /** + * Returns this ClassRemapper as {@link jdk.classfile.FieldTransform} instance + * @return this ClassRemapper as {@link jdk.classfile.FieldTransform} instance + */ + FieldTransform asFieldTransform(); - MethodTransform methodTransform(); + /** + * Returns this ClassRemapper as {@link jdk.classfile.MethodTransform} instance + * @return this ClassRemapper as {@link jdk.classfile.MethodTransform} instance + */ + MethodTransform asMethodTransform(); - CodeTransform codeTransform(); + /** + * Returns this ClassRemapper as {@link jdk.classfile.CodeTransform} instance + * @return this ClassRemapper as {@link jdk.classfile.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(classTransform().resolve(clb).consumer())); + clb -> clm.forEachElement(resolve(clb).consumer())); } - final static class ClassRemapperImpl implements ClassRemapper { - - private final Function mapFunction; - - ClassRemapperImpl(Function mapFunction) { - this.mapFunction = mapFunction; - } + record ClassRemapperImpl(Function mapFunction) implements ClassRemapper { @Override - public ClassTransform classTransform() { - return (ClassBuilder clb, ClassElement cle) -> { - switch (cle) { - case FieldModel fm -> - clb.withField(fm.fieldName().stringValue(), map(fm.fieldTypeSymbol()), fb -> fm.forEachElement(fieldTransform().resolve(fb).consumer())); - case MethodModel mm -> - clb.withMethod(mm.methodName().stringValue(), mapMethodDesc(mm.methodTypeSymbol()), mm.flags().flagsMask(), mb -> mm.forEachElement(methodTransform().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 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); - } - }; + 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 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 fieldTransform() { + public FieldTransform asFieldTransform() { return (FieldBuilder fb, FieldElement fe) -> { switch (fe) { case SignatureAttribute sa -> @@ -192,11 +245,11 @@ public FieldTransform fieldTransform() { } @Override - public MethodTransform methodTransform() { + public MethodTransform asMethodTransform() { return (MethodBuilder mb, MethodElement me) -> { switch (me) { case CodeModel com -> - mb.transformCode(com, codeTransform()); + mb.transformCode(com, asCodeTransform()); case ExceptionsAttribute ea -> mb.with(ExceptionsAttribute.ofSymbols(ea.exceptions().stream().map(ce -> map(ce.asSymbol())).toList())); case SignatureAttribute sa -> @@ -220,7 +273,7 @@ public MethodTransform methodTransform() { } @Override - public CodeTransform codeTransform() { + public CodeTransform asCodeTransform() { return (CodeBuilder cob, CodeElement coe) -> { switch (coe) { case FieldInstruction fai -> diff --git a/src/java.base/share/classes/jdk/classfile/components/CodeLocalsShifter.java b/src/java.base/share/classes/jdk/classfile/components/CodeLocalsShifter.java new file mode 100644 index 0000000000000..e6ae3909f33df --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/components/CodeLocalsShifter.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute 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.classfile.components; + +import java.lang.constant.MethodTypeDesc; +import java.util.Arrays; + +import java.lang.reflect.AccessFlag; +import jdk.classfile.AccessFlags; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.Signature; +import jdk.classfile.instruction.IncrementInstruction; +import jdk.classfile.instruction.LoadInstruction; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.StoreInstruction; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.LocalVariableType; + +/** + * CodeLocalsShifter is a {@link jdk.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 use in code injection transformation: + *

+ * {@snippet lang=java : + * methodBuilder.transformCode(codeModel, (codeBuilder, codeElement) -> { + * ... + * codeBuilder.transform(injectedCodeModel, + * CodeLocalsShifter.of(methodModel.flags(), methodModel.methodTypeSymbol()) + * .andThen(otherInjectedCodeTransforms)); + * ... + * })); + * } + */ +public sealed interface CodeLocalsShifter extends CodeTransform { + + /** + * Creates a new instance of CodeLocalsShifter + * with fixed local slots calculated from provided method information + * @param methodFlags flags of the method to construct CodeLocalsShifter for + * @param methodDescriptor descriptor of the method to construct CodeLocalsShifter for + * @return new instance of 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/classfile/components/CodeRelabeler.java b/src/java.base/share/classes/jdk/classfile/components/CodeRelabeler.java new file mode 100644 index 0000000000000..c59a40ff26976 --- /dev/null +++ b/src/java.base/share/classes/jdk/classfile/components/CodeRelabeler.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute 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.classfile.components; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.BiFunction; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.instruction.BranchInstruction; +import jdk.classfile.instruction.LookupSwitchInstruction; +import jdk.classfile.instruction.SwitchCase; +import jdk.classfile.instruction.TableSwitchInstruction; +import jdk.classfile.Label; +import jdk.classfile.instruction.ExceptionCatch; +import jdk.classfile.instruction.LabelTarget; +import jdk.classfile.instruction.LocalVariable; +import jdk.classfile.instruction.LocalVariableType; + +/** + * CodeRelabeler is a {@link jdk.classfile.CodeTransform} replacing all occurences + * of {@link jdk.classfile.Label} in the transformed code with new instances. + * All {@link jdk.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.classfile.Label} is bound in the target bytecode exactly once. + *

+ * Sample use: + *

+ * {@snippet lang=java : + * //first injection does not require relabeling + * codeBuilder.transform(codeModel, myCodeInjectionTransformation); + * ... + * //repeated injection of the same code requires relabeling + * codeBuilder.transform(codeModel, CodeRelabeler.of() + * .andThen(myCodeInjectionTransformation)); + * } + */ +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)); + default -> + cob.with(coe); + } + } + } +} diff --git a/src/java.base/share/classes/jdk/classfile/components/CodeStackTracker.java b/src/java.base/share/classes/jdk/classfile/components/CodeStackTracker.java new file mode 100644 index 0000000000000..fbf3cb9c47213 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.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.classfile.CodeBuilder; +import jdk.classfile.CodeElement; +import jdk.classfile.CodeTransform; +import jdk.classfile.Label; +import jdk.classfile.Opcode; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.*; + +/** + * CodeStackTracker is a {@link jdk.classfile.CodeTransform} synchronously 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 CodeStackTracker initialized with provided stack items + * @param initialStack initial stack content + * @return new instance of 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 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/classfile/impl/BlockCodeBuilderImpl.java b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java index 7ff6ac341bf4a..2be7c33d07e6c 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java @@ -39,7 +39,6 @@ public final class BlockCodeBuilderImpl extends NonterminalCodeBuilder implements CodeBuilder.BlockCodeBuilder { - private final CodeBuilder parent; private final Label startLabel, endLabel, breakLabel; private boolean reachable = true; private boolean hasInstructions = false; @@ -48,7 +47,6 @@ public final class BlockCodeBuilderImpl public BlockCodeBuilderImpl(CodeBuilder parent, Label breakLabel) { super(parent); - this.parent = parent; this.startLabel = parent.newLabel(); this.endLabel = parent.newLabel(); this.breakLabel = Objects.requireNonNull(breakLabel); diff --git a/src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.java index bccd4d082f3cd..b3b3056e11b31 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ChainedCodeBuilder.java @@ -57,7 +57,7 @@ public Label endLabel() { @Override public int allocateLocal(TypeKind typeKind) { - return terminal.allocateLocal(typeKind); + return parent.allocateLocal(typeKind); } @Override diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java index d03e1cc1edcd4..2adb5fb78d2ca 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassPrinterImpl.java @@ -48,7 +48,7 @@ import jdk.classfile.AnnotationValue.*; import jdk.classfile.Attribute; import jdk.classfile.ClassModel; -import jdk.classfile.ClassPrinter.*; +import jdk.classfile.components.ClassPrinter.*; import jdk.classfile.CodeModel; import jdk.classfile.Instruction; import jdk.classfile.MethodModel; diff --git a/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java index 30067a48ff193..21d8797887549 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java +++ b/src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java @@ -37,9 +37,11 @@ public abstract sealed class NonterminalCodeBuilder implements CodeBuilder permits ChainedCodeBuilder, BlockCodeBuilderImpl { protected final TerminalCodeBuilder terminal; + protected final CodeBuilder parent; - public NonterminalCodeBuilder(CodeBuilder downstream) { - this.terminal = switch (downstream) { + public NonterminalCodeBuilder(CodeBuilder parent) { + this.parent = parent; + this.terminal = switch (parent) { case ChainedCodeBuilder cb -> cb.terminal; case BlockCodeBuilderImpl cb -> cb.terminal; case TerminalCodeBuilder cb -> cb; diff --git a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java index d2a204f4e4324..bfd5347941b9d 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/classfile/impl/StackMapGenerator.java @@ -46,7 +46,7 @@ import jdk.classfile.Label; import jdk.classfile.attribute.StackMapTableAttribute; import jdk.classfile.Attributes; -import jdk.classfile.ClassPrinter; +import jdk.classfile.components.ClassPrinter; import jdk.classfile.attribute.CodeAttribute; /** diff --git a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java index e46a1d2f9b565..727103777c69c 100644 --- a/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/verifier/VerifierImpl.java @@ -30,7 +30,7 @@ import java.util.function.Consumer; import jdk.classfile.ClassHierarchyResolver; import jdk.classfile.ClassModel; -import jdk.classfile.ClassPrinter; +import jdk.classfile.components.ClassPrinter; import jdk.classfile.Classfile; import jdk.classfile.impl.ClassHierarchyImpl; import jdk.classfile.impl.RawBytecodeHelper; diff --git a/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java b/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java deleted file mode 100644 index 0db457934c0e8..0000000000000 --- a/src/java.base/share/classes/jdk/classfile/transforms/CodeLocalsShifter.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute 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.classfile.transforms; - -import java.lang.constant.MethodTypeDesc; -import java.util.Arrays; - -import java.lang.reflect.AccessFlag; -import jdk.classfile.AccessFlags; -import jdk.classfile.CodeBuilder; -import jdk.classfile.CodeElement; -import jdk.classfile.CodeTransform; -import jdk.classfile.Signature; -import jdk.classfile.instruction.IncrementInstruction; -import jdk.classfile.instruction.LoadInstruction; -import jdk.classfile.instruction.LocalVariable; -import jdk.classfile.instruction.StoreInstruction; -import jdk.classfile.TypeKind; -import jdk.classfile.instruction.LocalVariableType; - -/** - * - */ -public final class CodeLocalsShifter implements CodeTransform { - - private int[] locals = new int[0]; - private final int fixed; - private int next; - - public CodeLocalsShifter(AccessFlags methodFlags, MethodTypeDesc methodDescriptor) { - next = methodFlags.has(AccessFlag.STATIC) ? 0 : 1; - for (var param : methodDescriptor.parameterList()) - next += TypeKind.fromDescriptor(param.descriptorString()).slotSize(); - fixed = next; - } - - private CodeLocalsShifter(int fixed, int next) { - this.fixed = fixed; - this.next = next; - } - - public CodeLocalsShifter fork() { - return new CodeLocalsShifter(fixed, next); - } - - public int addLocal(TypeKind tk) { - int local = next; - next += tk.slotSize(); - return local; - } - - @Override - public void accept(CodeBuilder cob, CodeElement coe) { - switch (coe) { - case LoadInstruction li -> - cob.loadInstruction( - li.typeKind(), - shift(li.slot(), li.typeKind())); - case StoreInstruction si -> - cob.storeInstruction( - si.typeKind(), - shift(si.slot(), si.typeKind())); - case IncrementInstruction ii -> - cob.incrementInstruction( - shift(ii.slot(), TypeKind.IntType), - ii.constant()); - case LocalVariable lv -> - cob.localVariable( - shift(lv.slot(), TypeKind.fromDescriptor(lv.type().stringValue())), - lv.name(), - lv.type(), - lv.startScope(), - lv.endScope()); - case LocalVariableType lvt -> - cob.localVariableType( - shift(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(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 = addLocal(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/classfile/transforms/LabelsRemapper.java b/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java deleted file mode 100644 index c848c0207d6c2..0000000000000 --- a/src/java.base/share/classes/jdk/classfile/transforms/LabelsRemapper.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute 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.classfile.transforms; - -import java.util.IdentityHashMap; -import java.util.Map; -import jdk.classfile.CodeBuilder; -import jdk.classfile.CodeElement; -import jdk.classfile.CodeTransform; -import jdk.classfile.instruction.BranchInstruction; -import jdk.classfile.instruction.LookupSwitchInstruction; -import jdk.classfile.instruction.SwitchCase; -import jdk.classfile.instruction.TableSwitchInstruction; -import jdk.classfile.Label; -import jdk.classfile.instruction.ExceptionCatch; -import jdk.classfile.instruction.LabelTarget; -import jdk.classfile.instruction.LocalVariable; -import jdk.classfile.instruction.LocalVariableType; - -/** - * - */ -public final class LabelsRemapper { - - private LabelsRemapper() { - } - - public static CodeTransform remapLabels() { - var map = new IdentityHashMap(); - return (CodeBuilder cob, CodeElement coe) -> { - switch (coe) { - case BranchInstruction bi -> - cob.branchInstruction( - bi.opcode(), - remap(map, bi.target(), cob)); - case LookupSwitchInstruction lsi -> - cob.lookupSwitchInstruction( - remap(map, lsi.defaultTarget(), cob), - lsi.cases().stream().map(c -> - SwitchCase.of( - c.caseValue(), - remap(map, c.target(), cob))).toList()); - case TableSwitchInstruction tsi -> - cob.tableSwitchInstruction( - tsi.lowValue(), - tsi.highValue(), - remap(map, tsi.defaultTarget(), cob), - tsi.cases().stream().map(c -> - SwitchCase.of( - c.caseValue(), - remap(map, c.target(), cob))).toList()); - case LabelTarget lt -> - cob.labelBinding( - remap(map, lt.label(), cob)); - case ExceptionCatch ec -> - cob.exceptionCatch( - remap(map, ec.tryStart(), cob), - remap(map, ec.tryEnd(), cob), - remap(map, ec.handler(), cob), - ec.catchType()); - case LocalVariable lv -> - cob.localVariable( - lv.slot(), - lv.name().stringValue(), - lv.typeSymbol(), - remap(map, lv.startScope(), cob), - remap(map, lv.endScope(), cob)); - case LocalVariableType lvt -> - cob.localVariableType( - lvt.slot(), - lvt.name().stringValue(), - lvt.signatureSymbol(), - remap(map, lvt.startScope(), cob), - remap(map, lvt.endScope(), cob)); - default -> - cob.with(coe); - } - }; - } - - private static Label remap(Map map, Label l, CodeBuilder cob) { - return map.computeIfAbsent(l, ll -> cob.newLabel()); - } -} diff --git a/src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java b/src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java deleted file mode 100644 index 26b910b953f3a..0000000000000 --- a/src/java.base/share/classes/jdk/classfile/transforms/StackTracker.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute 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.classfile.transforms; - -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.classfile.CodeBuilder; -import jdk.classfile.CodeElement; -import jdk.classfile.CodeTransform; -import jdk.classfile.Label; -import jdk.classfile.Opcode; -import jdk.classfile.TypeKind; -import jdk.classfile.instruction.*; - -/** - * - */ -public final class StackTracker implements CodeTransform { - - - /** - * 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 - */ - public Optional> stack() { - return Optional.ofNullable(fork()); - } - - /** - * 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 - */ - public Optional maxStackSize() { - return Optional.ofNullable(maxSize); - } - - 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; - - private 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/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index e63e6f92beacb..89810f1cd5da9 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -38,8 +38,8 @@ import jdk.classfile.MethodModel; import jdk.classfile.TypeKind; import jdk.classfile.impl.StackMapGenerator; -import jdk.classfile.transforms.ClassRemapper; -import jdk.classfile.transforms.CodeLocalsShifter; +import jdk.classfile.components.ClassRemapper; +import jdk.classfile.components.CodeLocalsShifter; import org.testng.annotations.Test; import static org.testng.Assert.*; import static helpers.TestUtil.assertEmpty; @@ -51,6 +51,8 @@ import jdk.classfile.Attributes; import jdk.classfile.ClassModel; import jdk.classfile.ClassTransform; +import jdk.classfile.CodeBuilder; +import jdk.classfile.CodeTransform; import jdk.classfile.FieldModel; import jdk.classfile.Signature; import jdk.classfile.attribute.ModuleAttribute; @@ -59,9 +61,9 @@ import jdk.classfile.instruction.InvokeInstruction; import jdk.classfile.instruction.StoreInstruction; import java.lang.reflect.AccessFlag; -import jdk.classfile.transforms.LabelsRemapper; +import jdk.classfile.components.CodeRelabeler; import jdk.classfile.jdktypes.ModuleDesc; -import jdk.classfile.ClassPrinter; +import jdk.classfile.components.ClassPrinter; import static java.lang.annotation.ElementType.*; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -77,20 +79,27 @@ public void testShiftLocals() throws Exception { if (cle instanceof MethodModel mm) { clb.transformMethod(mm, (mb, me) -> { if (me instanceof CodeModel com) { - var shifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); - shifter.addLocal(TypeKind.ReferenceType); - shifter.addLocal(TypeKind.LongType); - shifter.addLocal(TypeKind.IntType); - shifter.addLocal(TypeKind.DoubleType); - mb.transformCode(com, shifter); - } - mb.with(me); + 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); //System.out::print); + remapped.verify(null); } } @@ -292,15 +301,14 @@ private static byte[] instrument(ClassModel target, ClassModel instrumentor, Pre (mb, me) -> { if (me instanceof CodeModel targetCodeModel) { var mm = targetCodeModel.parent().get(); - var instrumentorLocalsShifter = new CodeLocalsShifter(mm.flags(), mm.methodTypeSymbol()); //instrumented methods code is taken from instrumentor mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()), - //locals shifter monitors locals - instrumentorLocalsShifter + //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 - && instrumentor.thisClass().asInternalName().equals(inv.owner().asInternalName()) + && target.thisClass().asInternalName().equals(inv.owner().asInternalName()) && mm.methodName().stringValue().equals(inv.name().stringValue()) && mm.methodType().stringValue().equals(inv.type().stringValue())) { @@ -316,23 +324,20 @@ private static byte[] instrument(ClassModel target, ClassModel instrumentor, Pre } storeStack.forEach(codeBuilder::with); - var endLabel = codeBuilder.newLabel(); //inlined target locals must be shifted based on the actual instrumentor locals - codeBuilder.transform(targetCodeModel, instrumentorLocalsShifter.fork() - .andThen(LabelsRemapper.remapLabels()) + 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.codeKind() == CodeElement.Kind.RETURN) - innerBuilder.goto_(endLabel); + innerBuilder.goto_(inlinedBlockBuilder.breakLabel()); else innerBuilder.with(shiftedTargetCode); - })); - codeBuilder.labelBinding(endLabel); + }))); } else codeBuilder.with(instrumentorCodeElement); - }) - //all references to the instrumentor class are remapped to target class - .andThen(instrumentorClassRemapper.codeTransform())); + })); } else mb.with(me); }) @@ -346,6 +351,6 @@ private static byte[] instrument(ClassModel target, ClassModel instrumentor, Pre && !"".equals(mm.methodName().stringValue()) && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue()))) //and instrumentor class references remapped to target class - .andThen(instrumentorClassRemapper.classTransform()))))); + .andThen(instrumentorClassRemapper))))); } } diff --git a/test/jdk/jdk/classfile/ClassPrinterTest.java b/test/jdk/jdk/classfile/ClassPrinterTest.java index e7975b4adc234..175f1655fbe60 100644 --- a/test/jdk/jdk/classfile/ClassPrinterTest.java +++ b/test/jdk/jdk/classfile/ClassPrinterTest.java @@ -36,6 +36,7 @@ import java.util.Optional; import jdk.classfile.*; import jdk.classfile.attribute.*; +import jdk.classfile.components.ClassPrinter; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; diff --git a/test/jdk/jdk/classfile/StackTrackerTest.java b/test/jdk/jdk/classfile/StackTrackerTest.java index fad581e3458c8..f7164d301e67d 100644 --- a/test/jdk/jdk/classfile/StackTrackerTest.java +++ b/test/jdk/jdk/classfile/StackTrackerTest.java @@ -25,7 +25,7 @@ /* * @test - * @summary Testing StackTracker in CodeBuilder. + * @summary Testing CodeStackTracker in CodeBuilder. * @run testng StackTrackerTest */ import java.util.List; @@ -33,7 +33,7 @@ import java.lang.constant.MethodTypeDesc; import java.lang.constant.ConstantDescs; import jdk.classfile.*; -import jdk.classfile.transforms.StackTracker; +import jdk.classfile.components.CodeStackTracker; import static jdk.classfile.TypeKind.*; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -47,25 +47,25 @@ public class StackTrackerTest { public void testStackTracker() { Classfile.build(ClassDesc.of("Foo"), clb -> clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_Void), 0, cob -> { - var stackTracker = new StackTracker(); + var stackTracker = CodeStackTracker.of(DoubleType, FloatType); //initial stack tracker pre-set cob.transforming(stackTracker, stcb -> { - assertEquals(stackTracker.stack().get(), List.of()); + assertEquals(stackTracker.stack().get(), List.of(DoubleType, FloatType)); stcb.aload(0); - assertEquals(stackTracker.stack().get(), List.of(ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(ReferenceType, DoubleType, FloatType)); stcb.lconst_0(); - assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); stcb.trying(tryb -> { - assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); tryb.iconst_1(); - assertEquals(stackTracker.stack().get(), List.of(IntType, LongType, ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(IntType, LongType, ReferenceType, DoubleType, FloatType)); tryb.ifThen(thb -> { - assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); thb.constantInstruction(ClassDesc.of("Phee")); - assertEquals(stackTracker.stack().get(), List.of(ReferenceType, LongType, ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(ReferenceType, LongType, ReferenceType, DoubleType, FloatType)); thb.athrow(); assertFalse(stackTracker.stack().isPresent()); }); - assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType)); + assertEquals(stackTracker.stack().get(), List.of(LongType, ReferenceType, DoubleType, FloatType)); tryb.return_(); assertFalse(stackTracker.stack().isPresent()); }, catchb -> catchb.catching(ClassDesc.of("Phee"), cb -> { @@ -75,14 +75,14 @@ public void testStackTracker() { })); }); assertTrue(stackTracker.maxStackSize().isPresent()); - assertEquals((int)stackTracker.maxStackSize().get(), 4); + assertEquals((int)stackTracker.maxStackSize().get(), 7); })); } public void testTrackingLost() { Classfile.build(ClassDesc.of("Foo"), clb -> clb.withMethodBody("m", MethodTypeDesc.of(ConstantDescs.CD_Void), 0, cob -> { - var stackTracker = new StackTracker(); + var stackTracker = CodeStackTracker.of(); cob.transforming(stackTracker, stcb -> { assertEquals(stackTracker.stack().get(), List.of()); var l1 = stcb.newLabel(); diff --git a/test/jdk/jdk/classfile/TEST.properties b/test/jdk/jdk/classfile/TEST.properties index 5bc418e95324c..cdcfa6b444528 100644 --- a/test/jdk/jdk/classfile/TEST.properties +++ b/test/jdk/jdk/classfile/TEST.properties @@ -8,7 +8,7 @@ modules = \ java.base/jdk.classfile.impl \ java.base/jdk.classfile.impl.verifier \ java.base/jdk.classfile.jdktypes \ - java.base/jdk.classfile.transforms \ + java.base/jdk.classfile.components \ java.base/jdk.classfile.util \ java.base/jdk.internal.org.objectweb.asm \ java.base/jdk.internal.org.objectweb.asm.tree \ diff --git a/test/jdk/jdk/classfile/examples/AnnotationsExamples.java b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java index a9fc1fe45bc61..7519e7e0ff9a8 100644 --- a/test/jdk/jdk/classfile/examples/AnnotationsExamples.java +++ b/test/jdk/jdk/classfile/examples/AnnotationsExamples.java @@ -39,7 +39,7 @@ import jdk.classfile.Classfile; import jdk.classfile.attribute.RuntimeVisibleAnnotationsAttribute; import jdk.classfile.constantpool.ConstantPoolBuilder; -import jdk.classfile.ClassPrinter; +import jdk.classfile.components.ClassPrinter; import org.testng.Assert; import org.testng.annotations.Factory; import org.testng.annotations.Test; diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index d108dd584dd0d..9e5a4ee32311e 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -34,7 +34,7 @@ import jdk.classfile.instruction.*; import jdk.classfile.jdktypes.ModuleDesc; import jdk.classfile.jdktypes.PackageDesc; -import jdk.classfile.transforms.StackTracker; +import jdk.classfile.components.CodeStackTracker; class RebuildingTransformation { @@ -71,7 +71,7 @@ static byte[] transform(ClassModel clm) { for (var me : mm) { switch (me) { case AccessFlags af -> mb.withFlags(af.flagsMask()); - case CodeModel com -> mb.withCode(cb -> cb.transforming(new StackTracker(), cob -> { + case CodeModel com -> mb.withCode(cb -> cb.transforming(CodeStackTracker.of(), cob -> { var labels = new HashMap(); for (var coe : com) { switch (coe) { diff --git a/test/jdk/jdk/classfile/helpers/Transforms.java b/test/jdk/jdk/classfile/helpers/Transforms.java index 9cee790c70577..3fd319880d94c 100644 --- a/test/jdk/jdk/classfile/helpers/Transforms.java +++ b/test/jdk/jdk/classfile/helpers/Transforms.java @@ -47,7 +47,7 @@ import jdk.classfile.CodeTransform; import jdk.classfile.MethodModel; import jdk.classfile.MethodTransform; -import jdk.classfile.transforms.ClassRemapper; +import jdk.classfile.components.ClassRemapper; import jdk.internal.org.objectweb.asm.AnnotationVisitor; import jdk.internal.org.objectweb.asm.Attribute; import jdk.internal.org.objectweb.asm.ClassReader; diff --git a/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java b/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java index acea89f2cb358..13a48744a1a5c 100644 --- a/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java +++ b/test/micro/org/openjdk/bench/jdk/classfile/Transforms.java @@ -45,7 +45,7 @@ import jdk.classfile.CodeTransform; import jdk.classfile.MethodModel; import jdk.classfile.MethodTransform; -import jdk.classfile.transforms.ClassRemapper; +import jdk.classfile.components.ClassRemapper; import jdk.internal.org.objectweb.asm.AnnotationVisitor; import jdk.internal.org.objectweb.asm.Attribute; import jdk.internal.org.objectweb.asm.ClassReader; From d0f7775b3636a1cc2a86d3af68dca982593f8cd0 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 20 Sep 2022 13:39:09 +0200 Subject: [PATCH 053/190] application of ClassDesc::ofInternalName and internal name conversions cleanup --- .../classes/jdk/classfile/Signature.java | 2 +- .../classfile/constantpool/ClassEntry.java | 7 ++++- .../classfile/impl/ClassHierarchyImpl.java | 4 +-- .../jdk/classfile/impl/ConcreteEntry.java | 7 +---- .../classes/jdk/classfile/impl/Util.java | 27 +++++++------------ test/jdk/jdk/classfile/ClassEntryTest.java | 5 ++-- 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/Signature.java b/src/java.base/share/classes/jdk/classfile/Signature.java index b55d89bcae152..0cc4062b94e48 100644 --- a/src/java.base/share/classes/jdk/classfile/Signature.java +++ b/src/java.base/share/classes/jdk/classfile/Signature.java @@ -128,7 +128,7 @@ public sealed interface ClassTypeSig /** {@return the class name, as a symbolic descriptor} */ default ClassDesc classDesc() { - return Util.toClassDesc(className()); + return ClassDesc.ofInternalName(className()); } /** {@return the type arguments of the class} */ diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java index aff990a0584a6..7d922e0ca2009 100755 --- a/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/ClassEntry.java @@ -25,9 +25,9 @@ package jdk.classfile.constantpool; import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import jdk.classfile.impl.ConcreteEntry; import jdk.classfile.impl.TemporaryConstantPool; @@ -42,6 +42,11 @@ sealed public interface ClassEntry extends LoadableConstantEntry permits ConcreteEntry.ConcreteClassEntry { + @Override + default ConstantDesc constantValue() { + return asSymbol(); + } + /** * {@return the UTF8 constant pool entry for the class name} */ diff --git a/src/java.base/share/classes/jdk/classfile/impl/ClassHierarchyImpl.java b/src/java.base/share/classes/jdk/classfile/impl/ClassHierarchyImpl.java index e036b6ec6e038..fd7a0d25b3a10 100755 --- a/src/java.base/share/classes/jdk/classfile/impl/ClassHierarchyImpl.java +++ b/src/java.base/share/classes/jdk/classfile/impl/ClassHierarchyImpl.java @@ -147,12 +147,12 @@ public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDes boolean isInterface = (in.readUnsignedShort() & 0x0200) != 0; in.skipBytes(2); int superIndex = in.readUnsignedShort(); - var superClass = superIndex > 0 ? Util.toClassDesc(cpStrings[cpClasses[superIndex]]) : null; + 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 desc; + case 'L' -> desc.substring(1, desc.length() - 1); + default -> throw new IllegalArgumentException(desc); + }; } - public static ClassDesc toClassDesc(String internalName) { - return ClassDesc.ofDescriptor((internalName.charAt(0) == '[') - ? internalName - : "L" + internalName + ";"); + public static ClassDesc toClassDesc(String classInternalNameOrArrayDesc) { + return classInternalNameOrArrayDesc.charAt(0) == '[' + ? ClassDesc.ofDescriptor(classInternalNameOrArrayDesc) + : ClassDesc.ofInternalName(classInternalNameOrArrayDesc); } public static List mappedList(List list, Function mapper) { diff --git a/test/jdk/jdk/classfile/ClassEntryTest.java b/test/jdk/jdk/classfile/ClassEntryTest.java index 667d6e8d0c54a..a9a3eee6985e8 100644 --- a/test/jdk/jdk/classfile/ClassEntryTest.java +++ b/test/jdk/jdk/classfile/ClassEntryTest.java @@ -9,7 +9,6 @@ import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; -import java.lang.constant.MethodTypeDesc; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -18,8 +17,8 @@ public class ClassEntryTest { - static final List additionCE = List.copyOf(ClassEntry.addingSymbols(List.of(), new ClassDesc[] {ConstantDescs.CD_void, ConstantDescs.CD_Enum, ConstantDescs.CD_Class})); - static final List additionCD = List.of(ConstantDescs.CD_void, ConstantDescs.CD_Enum, ConstantDescs.CD_Class); + static final List additionCE = List.copyOf(ClassEntry.addingSymbols(List.of(), new ClassDesc[] {ConstantDescs.CD_Void, ConstantDescs.CD_Enum, ConstantDescs.CD_Class})); + static final List additionCD = List.of(ConstantDescs.CD_Void, ConstantDescs.CD_Enum, ConstantDescs.CD_Class); static final List base = List.copyOf(additionCE); @Test From aee0f85861783e42864a15314a03bfb0950e26ad Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 21 Sep 2022 16:26:37 +0200 Subject: [PATCH 054/190] javadoc for ClassPrinter --- .../classfile/components/ClassPrinter.java | 108 +++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java b/src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java index 2de3a85d56106..02001963061a3 100644 --- a/src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java +++ b/src/java.base/share/classes/jdk/classfile/components/ClassPrinter.java @@ -29,62 +29,164 @@ import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; +import jdk.classfile.ClassModel; +import jdk.classfile.FieldModel; +import jdk.classfile.MethodModel; +import jdk.classfile.CodeModel; import jdk.classfile.CompoundElement; import jdk.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. */ public final class ClassPrinter { - public enum Verbosity { MEMBERS_ONLY, CRITICAL_ATTRIBUTES, TRACE_ALL } - + /** + * 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: + *

* @jvms 4.7 Attributes */ CRITICAL_ATTRIBUTES, diff --git a/src/java.base/share/classes/jdk/classfile/components/package-info.java b/src/java.base/share/classes/jdk/classfile/components/package-info.java index 3f983e6f52dc7..e7442f27a1a26 100644 --- a/src/java.base/share/classes/jdk/classfile/components/package-info.java +++ b/src/java.base/share/classes/jdk/classfile/components/package-info.java @@ -40,16 +40,13 @@ * to support automated offline processing. *

* The most frequent use case is to simply print a class: - *

- * {@snippet lang="java" class="jdk.classfile.snippet-files.PackageSnippets" region="printClass"} + * {@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="jdk.classfile.snippet-files.PackageSnippets" region="customPrint"} + * {@snippet lang="java" class="PackageSnippets" region="customPrint"} *

* Another use case for {@link ClassPrinter} is to simplify writing of automated tests: - *

- * {@snippet lang="java" class="jdk.classfile.snippet-files.PackageSnippets" region="printNodesInTest"} + * {@snippet lang="java" class="PackageSnippets" region="printNodesInTest"} * *

{@link ClassRemapper}

* ClassRemapper is a {@link jdk.classfile.ClassTransform}, {@link jdk.classfile.FieldTransform}, @@ -65,12 +62,10 @@ * 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="jdk.classfile.snippet-files.PackageSnippets" region="singleClassRemap"} + * {@snippet lang="java" class="PackageSnippets" region="singleClassRemap"} *

* Remapping of all classes under specific package: - *

- * {@snippet lang="java" class="jdk.classfile.snippet-files.PackageSnippets" region="allPackageRemap"} + * {@snippet lang="java" class="PackageSnippets" region="allPackageRemap"} * *

{@link CodeLocalsShifter}

* {@link CodeLocalsShifter} is a {@link jdk.classfile.CodeTransform} shifting locals to @@ -79,8 +74,7 @@ * 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="jdk.classfile.snippet-files.PackageSnippets" region="codeLocalsShifting"} + * {@snippet lang="java" class="PackageSnippets" region="codeLocalsShifting"} * *

{@link CodeRelabeler}

* {@link CodeRelabeler} is a {@link jdk.classfile.CodeTransform} replacing all occurences @@ -93,12 +87,11 @@ * {@link jdk.classfile.Label} is bound in the target bytecode exactly once. *

* Sample transformation relabeling all methods: - *

- * {@snippet lang="java" class="jdk.classfile.snippet-files.PackageSnippets" region="codeRelabeling"} + * {@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="jdk.classfile.snippet-files.PackageSnippets" region="classInstrumentation"} + * {@snippet lang="java" class="PackageSnippets" region="classInstrumentation"} */ package jdk.classfile.components; \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/components/snippet-files/PackageSnippets.java b/src/java.base/share/classes/jdk/classfile/components/snippet-files/PackageSnippets.java new file mode 100644 index 0000000000000..bdfa5d992e830 --- /dev/null +++ b/src/java.base/share/classes/jdk/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.LinkedList; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import jdk.classfile.ClassModel; +import jdk.classfile.ClassTransform; +import jdk.classfile.CodeModel; +import jdk.classfile.CodeTransform; +import jdk.classfile.FieldModel; +import jdk.classfile.MethodModel; +import jdk.classfile.TypeKind; +import jdk.classfile.instruction.InvokeInstruction; + +import jdk.classfile.MethodTransform; +import jdk.classfile.components.ClassPrinter; +import jdk.classfile.components.ClassRemapper; +import jdk.classfile.components.CodeLocalsShifter; +import jdk.classfile.components.CodeRelabeler; +import jdk.classfile.instruction.ReturnInstruction; +import jdk.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().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 +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.java b/src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.java index 7e8602fef8681..7d1603b44280a 100644 --- a/src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.java +++ b/src/java.base/share/classes/jdk/classfile/constantpool/PoolEntry.java @@ -46,7 +46,6 @@ sealed public interface PoolEntry extends WritableElement /** * {@return the index within the constant pool corresponding to this entry} - * @return */ int index(); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ArrayLoadInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ArrayLoadInstruction.java index 3b50658f97408..09812259ce086 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/ArrayLoadInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ArrayLoadInstruction.java @@ -49,7 +49,7 @@ sealed public interface ArrayLoadInstruction extends Instruction * {@return an array load instruction} * * @param op the opcode for the specific type of array load instruction, - * which must be of kind {@link Kind#ARRAY_LOAD} + * which must be of kind {@link Opcode.Kind#ARRAY_LOAD} */ static ArrayLoadInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.ARRAY_LOAD); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java index c883cbe6e81b6..7e5b04d7a6afd 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ArrayStoreInstruction.java @@ -49,7 +49,7 @@ sealed public interface ArrayStoreInstruction extends Instruction * {@return an array store instruction} * * @param op the opcode for the specific type of array store instruction, - * which must be of kind {@link Kind#ARRAY_STORE} + * which must be of kind {@link Opcode.Kind#ARRAY_STORE} */ static ArrayStoreInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.ARRAY_STORE); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java index 9ad4051441d55..7535e1357b281 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/BranchInstruction.java @@ -50,7 +50,7 @@ sealed public interface BranchInstruction extends Instruction * {@return a branch instruction} * * @param op the opcode for the specific type of branch instruction, - * which must be of kind {@link Kind#BRANCH} + * which must be of kind {@link Opcode.Kind#BRANCH} */ static BranchInstruction of(Opcode op, Label target) { Util.checkKind(op, Opcode.Kind.BRANCH); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java b/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java index 0706d0cfabae1..1c0b59b4531f8 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/CharacterRange.java @@ -69,7 +69,8 @@ sealed public interface CharacterRange extends PseudoInstruction /** * A flags word, indicating the kind of range. Multiple flag bits - * may be set. Valid flags include {@link jdk.classfile.Classfile#CRT_STATEMENT}, + * may be set. Valid flags include + * {@link jdk.classfile.Classfile#CRT_STATEMENT}, * {@link jdk.classfile.Classfile#CRT_BLOCK}, * {@link jdk.classfile.Classfile#CRT_ASSIGNMENT}, * {@link jdk.classfile.Classfile#CRT_FLOW_CONTROLLER}, @@ -79,7 +80,7 @@ sealed public interface CharacterRange extends PseudoInstruction * {@link jdk.classfile.Classfile#CRT_BRANCH_TRUE}, * {@link jdk.classfile.Classfile#CRT_BRANCH_FALSE}. * - * @@@ Need reference for interpretation of flags. + * @see jdk.classfile.attribute.CharacterRangeInfo#flags() * * @return the flags */ diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java index 9ff370edb1a2a..01f69891d0d71 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ConstantInstruction.java @@ -115,7 +115,7 @@ default TypeKind typeKind() { * {@return an intrinsic constant instruction} * * @param op the opcode for the specific type of intrinsic constant instruction, - * which must be of kind {@link Kind#CONSTANT} + * which must be of kind {@link Opcode.Kind#CONSTANT} */ static IntrinsicConstantInstruction ofIntrinsic(Opcode op) { Util.checkKind(op, Opcode.Kind.CONSTANT); @@ -128,7 +128,7 @@ static IntrinsicConstantInstruction ofIntrinsic(Opcode op) { * {@return an argument constant instruction} * * @param op the opcode for the specific type of intrinsic constant instruction, - * which must be of kind {@link Kind#CONSTANT} + * which must be of kind {@link Opcode.Kind#CONSTANT} * @param value the constant value */ static ArgumentConstantInstruction ofArgument(Opcode op, int value) { @@ -142,7 +142,7 @@ static ArgumentConstantInstruction ofArgument(Opcode op, int value) { * {@return a load constant instruction} * * @param op the opcode for the specific type of load constant instruction, - * which must be of kind {@link Kind#CONSTANT} + * which must be of kind {@link Opcode.Kind#CONSTANT} * @param constant the constant value */ static LoadConstantInstruction ofLoad(Opcode op, LoadableConstantEntry constant) { diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ConvertInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ConvertInstruction.java index d87f84aeca1d0..456a8ca8aba26 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/ConvertInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ConvertInstruction.java @@ -65,7 +65,7 @@ static ConvertInstruction of(TypeKind fromType, TypeKind toType) { * {@return a conversion instruction} * * @param op the opcode for the specific type of conversion instruction, - * which must be of kind {@link Kind#CONVERT} + * which must be of kind {@link Opcode.Kind#CONVERT} */ static ConvertInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.CONVERT); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java index bbbdb09606a7f..59bfb2dd080f9 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/FieldInstruction.java @@ -83,7 +83,7 @@ default ClassDesc typeSymbol() { * {@return a field access instruction} * * @param op the opcode for the specific type of field access instruction, - * which must be of kind {@link Kind#FIELD_ACCESS} + * 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) { @@ -95,7 +95,7 @@ static FieldInstruction of(Opcode op, FieldRefEntry field) { * {@return a field access instruction} * * @param op the opcode for the specific type of field access instruction, - * which must be of kind {@link Kind#FIELD_ACCESS} + * 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 @@ -111,7 +111,7 @@ static FieldInstruction of(Opcode op, * {@return a field access instruction} * * @param op the opcode for the specific type of field access instruction, - * which must be of kind {@link Kind#FIELD_ACCESS} + * 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 */ diff --git a/src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.java index 69403bd8ecdf6..3db0e6ea3750d 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/IncrementInstruction.java @@ -27,6 +27,7 @@ import jdk.classfile.CodeElement; import jdk.classfile.CodeModel; import jdk.classfile.Instruction; +import jdk.classfile.Opcode; import jdk.classfile.impl.AbstractInstruction; /** diff --git a/src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java index 1bf09db9c15e1..3629e1e89ee11 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/InvokeInstruction.java @@ -98,7 +98,7 @@ default MethodTypeDesc typeSymbol() { * {@return an invocation instruction} * * @param op the opcode for the specific type of invocation instruction, - * which must be of kind {@link Kind#INVOKE} + * 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) { @@ -110,7 +110,7 @@ static InvokeInstruction of(Opcode op, MemberRefEntry method) { * {@return an invocation instruction} * * @param op the opcode for the specific type of invocation instruction, - * which must be of kind {@link Kind#INVOKE} + * 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 @@ -128,7 +128,7 @@ static InvokeInstruction of(Opcode op, * {@return an invocation instruction} * * @param op the opcode for the specific type of invocation instruction, - * which must be of kind {@link Kind#INVOKE} + * 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 diff --git a/src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java index 964fcb707d0f1..fc141b3ce36e5 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/LoadInstruction.java @@ -60,7 +60,7 @@ static LoadInstruction of(TypeKind kind, int slot) { * {@return a local variable load instruction} * * @param op the opcode for the specific type of load instruction, - * which must be of kind {@link Kind#LOAD} + * 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) { diff --git a/src/java.base/share/classes/jdk/classfile/instruction/MonitorInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/MonitorInstruction.java index 50bba007af663..0cde27034e851 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/MonitorInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/MonitorInstruction.java @@ -43,7 +43,7 @@ sealed public interface MonitorInstruction extends Instruction * {@return a monitor instruction} * * @param op the opcode for the specific type of monitor instruction, - * which must be of kind {@link Kind#MONITOR} + * which must be of kind {@link Opcode.Kind#MONITOR} */ static MonitorInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.MONITOR); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/OperatorInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/OperatorInstruction.java index 225edd5e258a2..3acc40a40e227 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/OperatorInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/OperatorInstruction.java @@ -49,7 +49,7 @@ sealed public interface OperatorInstruction extends Instruction * {@return an operator instruction} * * @param op the opcode for the specific type of array load instruction, - * which must be of kind {@link Kind#OPERATOR} + * which must be of kind {@link Opcode.Kind#OPERATOR} */ static OperatorInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.OPERATOR); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java index 3a8741b903827..84d20f66d82a0 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/ReturnInstruction.java @@ -56,7 +56,7 @@ static ReturnInstruction of(TypeKind typeKind) { * {@return a return instruction} * * @param op the opcode for the specific type of return instruction, - * which must be of kind {@link Kind#RETURN} + * which must be of kind {@link Opcode.Kind#RETURN} */ static ReturnInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.RETURN); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java index 1a534d51dce79..c396cf0c3849e 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/StackInstruction.java @@ -44,7 +44,7 @@ sealed public interface StackInstruction extends Instruction * {@return a stack manipulation instruction} * * @param op the opcode for the specific type of stack instruction, - * which must be of kind {@link Kind#STACK} + * which must be of kind {@link Opcode.Kind#STACK} */ static StackInstruction of(Opcode op) { Util.checkKind(op, Opcode.Kind.STACK); diff --git a/src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java index 1adc204943053..4ec80d7c6d4dc 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/StoreInstruction.java @@ -58,7 +58,7 @@ static StoreInstruction of(TypeKind kind, int slot) { * {@return a local variable store instruction} * * @param op the opcode for the specific type of store instruction, - * which must be of kind {@link Kind#STORE} + * 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) { diff --git a/src/java.base/share/classes/jdk/classfile/instruction/TypeCheckInstruction.java b/src/java.base/share/classes/jdk/classfile/instruction/TypeCheckInstruction.java index 59087d081b51f..1cb6684603bd6 100644 --- a/src/java.base/share/classes/jdk/classfile/instruction/TypeCheckInstruction.java +++ b/src/java.base/share/classes/jdk/classfile/instruction/TypeCheckInstruction.java @@ -49,7 +49,7 @@ sealed public interface TypeCheckInstruction extends Instruction * {@return a type check instruction} * * @param op the opcode for the specific type of type check instruction, - * which must be of kind {@link Kind#TYPE_CHECK} + * 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) { @@ -61,7 +61,7 @@ static TypeCheckInstruction of(Opcode op, 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 Kind#TYPE_CHECK} + * 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) { diff --git a/src/java.base/share/classes/jdk/classfile/package-info.java b/src/java.base/share/classes/jdk/classfile/package-info.java index 4f9b7aca69cd9..14be76d34f477 100644 --- a/src/java.base/share/classes/jdk/classfile/package-info.java +++ b/src/java.base/share/classes/jdk/classfile/package-info.java @@ -24,7 +24,7 @@ */ /** - *

Classfile parsing, generation, and transformation

+ *

Classfile parsing, generation, and transformation

* The {@code jdk.classfile} package contains classes for reading, writing, and * modifying Java class files, as specified in Chapter 4 of the
Java @@ -51,8 +51,7 @@ * 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="jdk.classfile.snippet-files.PackageSnippets" region="enumerateFieldsMethods1"} + * {@snippet lang="java" class="PackageSnippets" region="enumerateFieldsMethods1"} *

* When we enumerate the methods, we get a {@link jdk.classfile.MethodModel} for each method; like a * {@code ClassModel}, it gives us access to method metadata and @@ -69,8 +68,7 @@ * 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="jdk.classfile.snippet-files.PackageSnippets" region="enumerateFieldsMethods2"} + * {@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 @@ -79,13 +77,11 @@ * 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="jdk.classfile.snippet-files.PackageSnippets" region="gatherDependencies1"} + * {@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="jdk.classfile.snippet-files.PackageSnippets" region="gatherDependencies2"} + * {@snippet lang="java" class="PackageSnippets" region="gatherDependencies2"} * *

Models and elements

* The view of classfiles presented by this API is framed in terms of @@ -168,14 +164,13 @@ * classfile option. Implementations of custom attributes should extend {@link * jdk.classfile.CustomAttribute}. Custom attributes will be delivered as * elements in all of the contexts specified by {@link jdk.classfile.AttributeMapper#whereApplicable()}. - *

+ * *

Options

*

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

*