mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
8321248: ClassFile API ClassModel::verify is inconsistent with the rest of the API
Reviewed-by: jlahoda, mcimadamore
This commit is contained in:
parent
7fbfb3b74a
commit
0217b5ac8b
11 changed files with 62 additions and 50 deletions
|
@ -43,6 +43,7 @@ import java.lang.classfile.attribute.CharacterRangeInfo;
|
||||||
import java.lang.classfile.attribute.LocalVariableInfo;
|
import java.lang.classfile.attribute.LocalVariableInfo;
|
||||||
import java.lang.classfile.attribute.LocalVariableTypeInfo;
|
import java.lang.classfile.attribute.LocalVariableTypeInfo;
|
||||||
import java.lang.classfile.instruction.ExceptionCatch;
|
import java.lang.classfile.instruction.ExceptionCatch;
|
||||||
|
import java.util.List;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import jdk.internal.javac.PreviewFeature;
|
import jdk.internal.javac.PreviewFeature;
|
||||||
|
|
||||||
|
@ -481,6 +482,33 @@ public sealed interface ClassFile
|
||||||
*/
|
*/
|
||||||
byte[] transform(ClassModel model, ClassEntry newClassName, ClassTransform transform);
|
byte[] transform(ClassModel model, ClassEntry newClassName, ClassTransform transform);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a classfile. Any verification errors found will be returned.
|
||||||
|
* @param model the class model to verify
|
||||||
|
* @return a list of verification errors, or an empty list if no errors are
|
||||||
|
* found
|
||||||
|
*/
|
||||||
|
List<VerifyError> verify(ClassModel model);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a classfile. Any verification errors found will be returned.
|
||||||
|
* @param bytes the classfile bytes to verify
|
||||||
|
* @return a list of verification errors, or an empty list if no errors are
|
||||||
|
* found
|
||||||
|
*/
|
||||||
|
List<VerifyError> verify(byte[] bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a classfile. Any verification errors found will be returned.
|
||||||
|
* @param path the classfile path to verify
|
||||||
|
* @return a list of verification errors, or an empty list if no errors are
|
||||||
|
* found
|
||||||
|
* @throws java.io.IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
default List<VerifyError> verify(Path path) throws IOException {
|
||||||
|
return verify(Files.readAllBytes(path));
|
||||||
|
}
|
||||||
|
|
||||||
/** 0xCAFEBABE */
|
/** 0xCAFEBABE */
|
||||||
int MAGIC_NUMBER = 0xCAFEBABE;
|
int MAGIC_NUMBER = 0xCAFEBABE;
|
||||||
|
|
||||||
|
|
|
@ -78,29 +78,4 @@ public sealed interface ClassModel
|
||||||
|
|
||||||
/** {@return whether this class is a module descriptor} */
|
/** {@return whether this class is a module descriptor} */
|
||||||
boolean isModuleInfo();
|
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<VerifyError> verify(Consumer<String> 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 hierarchy
|
|
||||||
* @return a list of verification errors, or an empty list if no errors are
|
|
||||||
* found
|
|
||||||
*/
|
|
||||||
default List<VerifyError> verify(ClassHierarchyResolver classHierarchyResolver,
|
|
||||||
Consumer<String> debugOutput) {
|
|
||||||
return VerifierImpl.verify(this, classHierarchyResolver, debugOutput);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,8 +277,8 @@
|
||||||
* constantPoolBuilder.utf8Entry("mypackage.MyClass"));
|
* constantPoolBuilder.utf8Entry("mypackage.MyClass"));
|
||||||
* }
|
* }
|
||||||
* <p>
|
* <p>
|
||||||
* More complex verification of a classfile can be achieved by explicit invocation
|
* More complex verification of a classfile can be achieved by invocation of
|
||||||
* of {@link java.lang.classfile.ClassModel#verify}.
|
* {@link java.lang.classfile.ClassFile#verify}.
|
||||||
*
|
*
|
||||||
* <h2>Transforming classfiles</h2>
|
* <h2>Transforming classfiles</h2>
|
||||||
* ClassFile Processing APIs are most frequently used to combine reading and
|
* ClassFile Processing APIs are most frequently used to combine reading and
|
||||||
|
|
|
@ -39,6 +39,7 @@ import java.lang.classfile.ClassTransform;
|
||||||
import java.lang.classfile.constantpool.ClassEntry;
|
import java.lang.classfile.constantpool.ClassEntry;
|
||||||
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
||||||
import java.lang.classfile.constantpool.Utf8Entry;
|
import java.lang.classfile.constantpool.Utf8Entry;
|
||||||
|
import jdk.internal.classfile.impl.verifier.VerifierImpl;
|
||||||
|
|
||||||
public record ClassFileImpl(StackMapsOption stackMapsOption,
|
public record ClassFileImpl(StackMapsOption stackMapsOption,
|
||||||
DebugElementsOption debugElementsOption,
|
DebugElementsOption debugElementsOption,
|
||||||
|
@ -128,6 +129,21 @@ public record ClassFileImpl(StackMapsOption stackMapsOption,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<VerifyError> verify(ClassModel model) {
|
||||||
|
return VerifierImpl.verify(model, classHierarchyResolverOption().classHierarchyResolver(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<VerifyError> verify(byte[] bytes) {
|
||||||
|
try {
|
||||||
|
return verify(parse(bytes));
|
||||||
|
} catch (IllegalArgumentException parsingError) {
|
||||||
|
return List.of(new VerifyError(parsingError.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public record AttributeMapperOptionImpl(Function<Utf8Entry, AttributeMapper<?>> attributeMapper)
|
public record AttributeMapperOptionImpl(Function<Utf8Entry, AttributeMapper<?>> attributeMapper)
|
||||||
implements AttributeMapperOption {
|
implements AttributeMapperOption {
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,7 +212,7 @@ public class BindingSpecializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PERFORM_VERIFICATION) {
|
if (PERFORM_VERIFICATION) {
|
||||||
List<VerifyError> errors = ClassFile.of().parse(bytes).verify(null);
|
List<VerifyError> errors = ClassFile.of().verify(bytes);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
errors.forEach(System.err::println);
|
errors.forEach(System.err::println);
|
||||||
throw new IllegalStateException("Verification error(s)");
|
throw new IllegalStateException("Verification error(s)");
|
||||||
|
|
|
@ -75,7 +75,7 @@ class AdvancedTransformationsTest {
|
||||||
try (var in = StackMapGenerator.class.getResourceAsStream("StackMapGenerator.class")) {
|
try (var in = StackMapGenerator.class.getResourceAsStream("StackMapGenerator.class")) {
|
||||||
var cc = ClassFile.of();
|
var cc = ClassFile.of();
|
||||||
var clm = cc.parse(in.readAllBytes());
|
var clm = cc.parse(in.readAllBytes());
|
||||||
var remapped = cc.parse(cc.transform(clm, (clb, cle) -> {
|
cc.verify(cc.transform(clm, (clb, cle) -> {
|
||||||
if (cle instanceof MethodModel mm) {
|
if (cle instanceof MethodModel mm) {
|
||||||
clb.transformMethod(mm, (mb, me) -> {
|
clb.transformMethod(mm, (mb, me) -> {
|
||||||
if (me instanceof CodeModel com) {
|
if (me instanceof CodeModel com) {
|
||||||
|
@ -99,7 +99,6 @@ class AdvancedTransformationsTest {
|
||||||
else
|
else
|
||||||
clb.with(cle);
|
clb.with(cle);
|
||||||
}));
|
}));
|
||||||
remapped.verify(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,12 +114,12 @@ class AdvancedTransformationsTest {
|
||||||
var cc = ClassFile.of();
|
var cc = ClassFile.of();
|
||||||
var clm = cc.parse(in.readAllBytes());
|
var clm = cc.parse(in.readAllBytes());
|
||||||
var remapped = cc.parse(ClassRemapper.of(map).remapClass(cc, clm));
|
var remapped = cc.parse(ClassRemapper.of(map).remapClass(cc, clm));
|
||||||
assertEmpty(remapped.verify(
|
assertEmpty(ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(
|
||||||
ClassHierarchyResolver.of(Set.of(ClassDesc.of("remapped.List")), Map.of(
|
ClassHierarchyResolver.of(Set.of(ClassDesc.of("remapped.List")), Map.of(
|
||||||
ClassDesc.of("remapped.RemappedBytecode"), ConstantDescs.CD_Object,
|
ClassDesc.of("remapped.RemappedBytecode"), ConstantDescs.CD_Object,
|
||||||
ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode")))
|
ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode")))
|
||||||
.orElse(ClassHierarchyResolver.defaultResolver())
|
.orElse(ClassHierarchyResolver.defaultResolver())
|
||||||
, null)); //System.out::print));
|
)).verify(remapped));
|
||||||
remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa ->
|
remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa ->
|
||||||
verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature())));
|
verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature())));
|
||||||
remapped.methods().forEach(m -> m.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> {
|
remapped.methods().forEach(m -> m.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> {
|
||||||
|
@ -239,7 +238,7 @@ class AdvancedTransformationsTest {
|
||||||
var instrumentor = cc.parse(AdvancedTransformationsTest.class.getResourceAsStream("AdvancedTransformationsTest$InstrumentorClass.class").readAllBytes());
|
var instrumentor = cc.parse(AdvancedTransformationsTest.class.getResourceAsStream("AdvancedTransformationsTest$InstrumentorClass.class").readAllBytes());
|
||||||
var target = cc.parse(AdvancedTransformationsTest.class.getResourceAsStream("AdvancedTransformationsTest$TargetClass.class").readAllBytes());
|
var target = cc.parse(AdvancedTransformationsTest.class.getResourceAsStream("AdvancedTransformationsTest$TargetClass.class").readAllBytes());
|
||||||
var instrumentedBytes = instrument(target, instrumentor, mm -> mm.methodName().stringValue().equals("instrumentedMethod"));
|
var instrumentedBytes = instrument(target, instrumentor, mm -> mm.methodName().stringValue().equals("instrumentedMethod"));
|
||||||
assertEmpty(cc.parse(instrumentedBytes).verify(null)); //System.out::print));
|
assertEmpty(cc.verify(instrumentedBytes));
|
||||||
var targetClass = new ByteArrayClassLoader(AdvancedTransformationsTest.class.getClassLoader(), "AdvancedTransformationsTest$TargetClass", instrumentedBytes).loadClass("AdvancedTransformationsTest$TargetClass");
|
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);
|
assertEquals(targetClass.getDeclaredMethod("instrumentedMethod", Boolean.class).invoke(targetClass.getDeclaredConstructor().newInstance(), false), 34);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ class ClassHierarchyInfoTest {
|
||||||
else
|
else
|
||||||
clb.with(cle);
|
clb.with(cle);
|
||||||
});
|
});
|
||||||
var errors = ClassFile.of().parse(newBytes).verify(null);
|
var errors = ClassFile.of().verify(newBytes);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
var itr = errors.iterator();
|
var itr = errors.iterator();
|
||||||
var thrown = itr.next();
|
var thrown = itr.next();
|
||||||
|
|
|
@ -208,7 +208,7 @@ class CorpusTest {
|
||||||
ClassRecord.ofClassModel(classModel, CompatibilityFilter.By_ClassBuilder),
|
ClassRecord.ofClassModel(classModel, CompatibilityFilter.By_ClassBuilder),
|
||||||
"ClassModel[%s] transformed by ClassBuilder (actual) vs ClassModel before transformation (expected)".formatted(path));
|
"ClassModel[%s] transformed by ClassBuilder (actual) vs ClassModel before transformation (expected)".formatted(path));
|
||||||
|
|
||||||
assertEmpty(newModel.verify(null));
|
assertEmpty(cc.verify(newModel));
|
||||||
|
|
||||||
//testing maxStack and maxLocals are calculated identically by StackMapGenerator and StackCounter
|
//testing maxStack and maxLocals are calculated identically by StackMapGenerator and StackCounter
|
||||||
byte[] noStackMaps = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS)
|
byte[] noStackMaps = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS)
|
||||||
|
|
|
@ -229,11 +229,10 @@ class StackMapsTest {
|
||||||
.walk().anyMatch(n -> n.name().equals("stack map frames")));
|
.walk().anyMatch(n -> n.name().equals("stack map frames")));
|
||||||
|
|
||||||
//test transformation to class version 50 with re-generation of StackMapTable attributes
|
//test transformation to class version 50 with re-generation of StackMapTable attributes
|
||||||
assertEmpty(cc.parse(cc.transform(
|
assertEmpty(cc.verify(cc.transform(
|
||||||
version49,
|
version49,
|
||||||
ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)
|
ClassTransform.transformingMethodBodies(CodeTransform.ACCEPT_ALL)
|
||||||
.andThen(ClassTransform.endHandler(clb -> clb.withVersion(50, 0)))))
|
.andThen(ClassTransform.endHandler(clb -> clb.withVersion(50, 0))))));
|
||||||
.verify(null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -271,7 +270,7 @@ class StackMapsTest {
|
||||||
});
|
});
|
||||||
|
|
||||||
//then verify transformed bytecode
|
//then verify transformed bytecode
|
||||||
assertEmpty(cc.parse(transformedBytes).verify(null));
|
assertEmpty(cc.verify(transformedBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -51,7 +51,7 @@ class VerifierSelfTest {
|
||||||
.flatMap(p -> p)
|
.flatMap(p -> p)
|
||||||
.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")).forEach(path -> {
|
.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")).forEach(path -> {
|
||||||
try {
|
try {
|
||||||
ClassFile.of().parse(path).verify(null);
|
ClassFile.of().verify(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class VerifierSelfTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailedDump() throws IOException {
|
void testFailed() throws IOException {
|
||||||
Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class");
|
Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class");
|
||||||
var cc = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(
|
var cc = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(
|
||||||
className -> ClassHierarchyResolver.ClassHierarchyInfo.ofClass(null)));
|
className -> ClassHierarchyResolver.ClassHierarchyInfo.ofClass(null)));
|
||||||
|
@ -79,13 +79,8 @@ class VerifierSelfTest {
|
||||||
clb.with(cle);
|
clb.with(cle);
|
||||||
});
|
});
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (ClassFile.of().parse(brokenClassBytes).verify(sb::append).isEmpty()) {
|
if (ClassFile.of().verify(brokenClassBytes).isEmpty()) {
|
||||||
throw new AssertionError("expected verification failure");
|
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,10 +223,10 @@ public class JImageValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void readClass(byte[] clazz) throws IOException{
|
public static void readClass(byte[] clazz) throws IOException{
|
||||||
var errors = ClassFile.of().parse(clazz).verify(
|
var errors = ClassFile.of(
|
||||||
//resolution of all classes as interfaces cancels assignability verification
|
//resolution of all classes as interfaces cancels assignability verification
|
||||||
cls -> ClassHierarchyResolver.ClassHierarchyInfo.ofInterface(),
|
ClassFile.ClassHierarchyResolverOption.of(cls -> ClassHierarchyResolver.ClassHierarchyInfo.ofInterface()))
|
||||||
null);
|
.verify(clazz);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
var itr = errors.iterator();
|
var itr = errors.iterator();
|
||||||
var thrown = itr.next();
|
var thrown = itr.next();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue