8320396: Class-File API ClassModel::verify should include checks from hotspot/share/classfile/classFileParser.cpp

Reviewed-by: liach, mcimadamore
This commit is contained in:
Adam Sotona 2024-05-31 06:26:35 +00:00
parent 2ab8ab5613
commit 22ef827e2c
6 changed files with 862 additions and 34 deletions

View File

@ -28,6 +28,7 @@ import java.lang.constant.ConstantDesc;
import java.lang.constant.DirectMethodHandleDesc;
import java.lang.reflect.AccessFlag;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -35,7 +36,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.Set;
import java.util.function.Consumer;
@ -75,16 +75,22 @@ public final class ClassPrinterImpl {
}
}
public static final class ListNodeImpl extends AbstractList<Node> implements ListNode {
public static sealed class ListNodeImpl extends AbstractList<Node> implements ListNode {
private final Style style;
private final ConstantDesc name;
private final Node[] nodes;
protected final List<Node> nodes;
public ListNodeImpl(Style style, ConstantDesc name, Stream<Node> nodes) {
this.style = style;
this.name = name;
this.nodes = nodes.toArray(Node[]::new);
this.nodes = nodes.toList();
}
protected ListNodeImpl(Style style, ConstantDesc name, List<Node> nodes) {
this.style = style;
this.name = name;
this.nodes = nodes;
}
@Override
@ -103,18 +109,23 @@ public final class ClassPrinterImpl {
@Override
public Node get(int index) {
Objects.checkIndex(index, nodes.length);
return nodes[index];
return nodes.get(index);
}
@Override
public int size() {
return nodes.length;
return nodes.size();
}
}
public static final class MapNodeImpl implements MapNode {
private static final class PrivateListNodeImpl extends ListNodeImpl {
PrivateListNodeImpl(Style style, ConstantDesc name, Node... n) {
super(style, name, new ArrayList<>(List.of(n)));
}
}
private final Style style;
private final ConstantDesc name;
private final Map<ConstantDesc, Node> map;
@ -198,9 +209,19 @@ public final class ClassPrinterImpl {
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);
for (var n : nodes) {
if (n != null) {
var prev = map.putIfAbsent(n.name(), n);
if (prev != null) {
//nodes with duplicite keys are joined into a list
if (prev instanceof PrivateListNodeImpl list) {
list.nodes.add(n);
} else {
map.put(n.name(), new PrivateListNodeImpl(style, n.name(), prev, n));
}
}
}
}
return this;
}
}
@ -975,7 +996,7 @@ public final class ClassPrinterImpl {
nodes.add(leaf("module main class", mmca.mainClass().name().stringValue()));
case RecordAttribute ra ->
nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream()
.map(rc -> new MapNodeImpl(BLOCK, "record")
.map(rc -> new MapNodeImpl(BLOCK, "component")
.with(leafs(
"name", rc.name().stringValue(),
"type", rc.descriptor().stringValue()))

View File

@ -0,0 +1,494 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.classfile.impl.verifier;
import java.lang.classfile.Annotation;
import java.lang.classfile.AnnotationValue;
import java.lang.constant.ClassDesc;
import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME;
import static java.lang.constant.ConstantDescs.INIT_NAME;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.lang.classfile.Attribute;
import java.lang.classfile.AttributedElement;
import java.lang.classfile.Attributes;
import java.lang.classfile.ClassModel;
import java.lang.classfile.ClassFileElement;
import java.lang.classfile.CodeModel;
import java.lang.classfile.CompoundElement;
import java.lang.classfile.CustomAttribute;
import java.lang.classfile.FieldModel;
import java.lang.classfile.MethodModel;
import java.lang.classfile.TypeAnnotation;
import java.lang.classfile.TypeKind;
import java.lang.classfile.attribute.*;
import java.lang.classfile.constantpool.*;
import java.lang.constant.ConstantDescs;
import java.lang.reflect.AccessFlag;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import jdk.internal.classfile.impl.BoundAttribute;
import jdk.internal.classfile.impl.Util;
/**
* ParserVerifier performs selected checks of the class file format according to
* {@jvms 4.8 Format Checking}
*
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/classfile/classFileParser.cpp">hotspot/share/classfile/classFileParser.cpp</a>
*/
public record ParserVerifier(ClassModel classModel) {
List<VerifyError> verify() {
var errors = new ArrayList<VerifyError>();
verifyConstantPool(errors);
verifyInterfaces(errors);
verifyFields(errors);
verifyMethods(errors);
verifyAttributes(classModel, errors);
return errors;
}
private void verifyConstantPool(List<VerifyError> errors) {
for (var cpe : classModel.constantPool()) {
Consumer<Runnable> check = c -> {
try {
c.run();
} catch (VerifyError|Exception e) {
errors.add(new VerifyError("%s at constant pool index %d in %s".formatted(e.getMessage(), cpe.index(), toString(classModel))));
}
};
check.accept(switch (cpe) {
case DoubleEntry de -> de::doubleValue;
case FloatEntry fe -> fe::floatValue;
case IntegerEntry ie -> ie::intValue;
case LongEntry le -> le::longValue;
case Utf8Entry ue -> ue::stringValue;
case ConstantDynamicEntry cde -> cde::asSymbol;
case InvokeDynamicEntry ide -> ide::asSymbol;
case ClassEntry ce -> ce::asSymbol;
case StringEntry se -> se::stringValue;
case MethodHandleEntry mhe -> mhe::asSymbol;
case MethodTypeEntry mte -> mte::asSymbol;
case FieldRefEntry fre -> {
check.accept(fre.owner()::asSymbol);
check.accept(fre::typeSymbol);
yield () -> verifyFieldName(fre.name().stringValue());
}
case InterfaceMethodRefEntry imre -> {
check.accept(imre.owner()::asSymbol);
check.accept(imre::typeSymbol);
yield () -> verifyMethodName(imre.name().stringValue());
}
case MethodRefEntry mre -> {
check.accept(mre.owner()::asSymbol);
check.accept(mre::typeSymbol);
yield () -> verifyMethodName(mre.name().stringValue());
}
case ModuleEntry me -> me::asSymbol;
case NameAndTypeEntry nate -> {
check.accept(nate.name()::stringValue);
yield () -> nate.type().stringValue();
}
case PackageEntry pe -> pe::asSymbol;
});
}
}
private void verifyFieldName(String name) {
if (name.length() == 0 || name.chars().anyMatch(ch -> switch(ch) {
case '.', ';', '[', '/' -> true;
default -> false;
})) {
throw new VerifyError("Illegal field name %s in %s".formatted(name, toString(classModel)));
}
}
private void verifyMethodName(String name) {
if (!name.equals(INIT_NAME)
&& !name.equals(CLASS_INIT_NAME)
&& (name.length() == 0 || name.chars().anyMatch(ch -> switch(ch) {
case '.', ';', '[', '/', '<', '>' -> true;
default -> false;
}))) {
throw new VerifyError("Illegal method name %s in %s".formatted(name, toString(classModel)));
}
}
private void verifyInterfaces(List<VerifyError> errors) {
var intfs = new HashSet<ClassEntry>();
for (var intf : classModel.interfaces()) {
if (!intfs.add(intf)) {
errors.add(new VerifyError("Duplicate interface %s in %s".formatted(intf.asSymbol().displayName(), toString(classModel))));
}
}
}
private void verifyFields(List<VerifyError> errors) {
record F(Utf8Entry name, Utf8Entry type) {};
var fields = new HashSet<F>();
for (var f : classModel.fields()) try {
if (!fields.add(new F(f.fieldName(), f.fieldType()))) {
errors.add(new VerifyError("Duplicate field name %s with signature %s in %s".formatted(f.fieldName().stringValue(), f.fieldType().stringValue(), toString(classModel))));
}
verifyFieldName(f.fieldName().stringValue());
} catch (VerifyError ve) {
errors.add(ve);
}
}
private void verifyMethods(List<VerifyError> errors) {
record M(Utf8Entry name, Utf8Entry type) {};
var methods = new HashSet<M>();
for (var m : classModel.methods()) try {
if (!methods.add(new M(m.methodName(), m.methodType()))) {
errors.add(new VerifyError("Duplicate method name %s with signature %s in %s".formatted(m.methodName().stringValue(), m.methodType().stringValue(), toString(classModel))));
}
if (m.methodName().equalsString(CLASS_INIT_NAME)
&& !m.flags().has(AccessFlag.STATIC)) {
errors.add(new VerifyError("Method <clinit> is not static in %s".formatted(toString(classModel))));
}
if (classModel.flags().has(AccessFlag.INTERFACE)
&& m.methodName().equalsString(INIT_NAME)) {
errors.add(new VerifyError("Interface cannot have a method named <init> in %s".formatted(toString(classModel))));
}
verifyMethodName(m.methodName().stringValue());
} catch (VerifyError ve) {
errors.add(ve);
}
}
private void verifyAttributes(ClassFileElement cfe, List<VerifyError> errors) {
if (cfe instanceof AttributedElement ae) {
var attrNames = new HashSet<String>();
for (var a : ae.attributes()) {
if (!a.attributeMapper().allowMultiple() && !attrNames.add(a.attributeName())) {
errors.add(new VerifyError("Multiple %s attributes in %s".formatted(a.attributeName(), toString(ae))));
}
verifyAttribute(ae, a, errors);
}
}
switch (cfe) {
case CompoundElement<?> comp -> {
for (var e : comp) verifyAttributes(e, errors);
}
case RecordAttribute ra -> {
for(var rc : ra.components()) verifyAttributes(rc, errors);
}
default -> {}
}
}
private void verifyAttribute(AttributedElement ae, Attribute<?> a, List<VerifyError> errors) {
int size = switch (a) {
case AnnotationDefaultAttribute aa ->
valueSize(aa.defaultValue());
case BootstrapMethodsAttribute bma ->
2 + bma.bootstrapMethods().stream().mapToInt(bm -> 4 + 2 * bm.arguments().size()).sum();
case CharacterRangeTableAttribute cra ->
2 + 14 * cra.characterRangeTable().size();
case CodeAttribute ca -> {
MethodModel mm = (MethodModel)ae;
if (mm.flags().has(AccessFlag.NATIVE) || mm.flags().has(AccessFlag.ABSTRACT)) {
errors.add(new VerifyError("Code attribute in native or abstract %s".formatted(toString(ae))));
}
if (ca.maxLocals() < Util.maxLocals(mm.flags().flagsMask(), mm.methodTypeSymbol())) {
errors.add(new VerifyError("Arguments can't fit into locals in %s".formatted(toString(ae))));
}
yield 10 + ca.codeLength() + 8 * ca.exceptionHandlers().size() + attributesSize(ca.attributes());
}
case CompilationIDAttribute cida -> {
cida.compilationId();
yield 2;
}
case ConstantValueAttribute cva -> {
ClassDesc type = ((FieldModel)ae).fieldTypeSymbol();
ConstantValueEntry cve = cva.constant();
if (!switch (TypeKind.from(type)) {
case BooleanType, ByteType, CharType, IntType, ShortType -> cve instanceof IntegerEntry;
case DoubleType -> cve instanceof DoubleEntry;
case FloatType -> cve instanceof FloatEntry;
case LongType -> cve instanceof LongEntry;
case ReferenceType -> type.equals(ConstantDescs.CD_String) && cve instanceof StringEntry;
case VoidType -> false;
}) {
errors.add(new VerifyError("Bad constant value type in %s".formatted(toString(ae))));
}
yield 2;
}
case DeprecatedAttribute _ ->
0;
case EnclosingMethodAttribute ema -> {
ema.enclosingClass();
ema.enclosingMethod();
yield 4;
}
case ExceptionsAttribute ea ->
2 + 2 * ea.exceptions().size();
case InnerClassesAttribute ica -> {
for (var ici : ica.classes()) {
if (ici.outerClass().isPresent() && ici.outerClass().get().equals(ici.innerClass())) {
errors.add(new VerifyError("Class is both outer and inner class in %s".formatted(toString(ae))));
}
}
yield 2 + 8 * ica.classes().size();
}
case LineNumberTableAttribute lta ->
2 + 4 * lta.lineNumbers().size();
case LocalVariableTableAttribute lvta ->
2 + 10 * lvta.localVariables().size();
case LocalVariableTypeTableAttribute lvta ->
2 + 10 * lvta.localVariableTypes().size();
case MethodParametersAttribute mpa ->
1 + 4 * mpa.parameters().size();
case ModuleAttribute ma ->
16 + subSize(ma.exports(), ModuleExportInfo::exportsTo, 6, 2)
+ subSize(ma.opens(), ModuleOpenInfo::opensTo, 6, 2)
+ subSize(ma.provides(), ModuleProvideInfo::providesWith, 4, 2)
+ 6 * ma.requires().size()
+ 2 * ma.uses().size();
case ModuleHashesAttribute mha ->
2 + moduleHashesSize(mha.hashes());
case ModuleMainClassAttribute mmca -> {
mmca.mainClass();
yield 2;
}
case ModulePackagesAttribute mpa ->
2 + 2 * mpa.packages().size();
case ModuleResolutionAttribute mra ->
2;
case ModuleTargetAttribute mta -> {
mta.targetPlatform();
yield 2;
}
case NestHostAttribute nha -> {
nha.nestHost();
yield 2;
}
case NestMembersAttribute nma -> {
if (ae.findAttribute(Attributes.nestHost()).isPresent()) {
errors.add(new VerifyError("Conflicting NestHost and NestMembers attributes in %s".formatted(toString(ae))));
}
yield 2 + 2 * nma.nestMembers().size();
}
case PermittedSubclassesAttribute psa -> {
if (classModel.flags().has(AccessFlag.FINAL)) {
errors.add(new VerifyError("PermittedSubclasses attribute in final %s".formatted(toString(ae))));
}
yield 2 + 2 * psa.permittedSubclasses().size();
}
case RecordAttribute ra ->
componentsSize(ra.components());
case RuntimeVisibleAnnotationsAttribute aa ->
annotationsSize(aa.annotations());
case RuntimeInvisibleAnnotationsAttribute aa ->
annotationsSize(aa.annotations());
case RuntimeVisibleTypeAnnotationsAttribute aa ->
typeAnnotationsSize(aa.annotations());
case RuntimeInvisibleTypeAnnotationsAttribute aa ->
typeAnnotationsSize(aa.annotations());
case RuntimeVisibleParameterAnnotationsAttribute aa ->
parameterAnnotationsSize(aa.parameterAnnotations());
case RuntimeInvisibleParameterAnnotationsAttribute aa ->
parameterAnnotationsSize(aa.parameterAnnotations());
case SignatureAttribute sa -> {
sa.signature();
yield 2;
}
case SourceDebugExtensionAttribute sda ->
sda.contents().length;
case SourceFileAttribute sfa -> {
sfa.sourceFile();
yield 2;
}
case SourceIDAttribute sida -> {
sida.sourceId();
yield 2;
}
case StackMapTableAttribute smta ->
2 + subSize(smta.entries(), frame -> stackMapFrameSize(frame));
case SyntheticAttribute _ ->
0;
case UnknownAttribute _ ->
-1;
case CustomAttribute<?> _ ->
-1;
default -> // should not happen if all known attributes are verified
throw new AssertionError(a);
};
if (size >= 0 && size != ((BoundAttribute)a).payloadLen()) {
errors.add(new VerifyError("Wrong %s attribute length in %s".formatted(a.attributeName(), toString(ae))));
}
}
private static <T, S extends Collection<?>> int subSize(Collection<T> entries, Function<T, S> subMH, int entrySize, int subSize) {
return subSize(entries, (ToIntFunction<T>) t -> entrySize + subSize * subMH.apply(t).size());
}
private static <T> int subSize(Collection<T> entries, ToIntFunction<T> subMH) {
int l = 0;
for (T entry : entries) {
l += subMH.applyAsInt(entry);
}
return l;
}
private static int componentsSize(List<RecordComponentInfo> comps) {
int l = 2;
for (var rc : comps) {
l += 4 + attributesSize(rc.attributes());
}
return l;
}
private static int attributesSize(List<Attribute<?>> attrs) {
int l = 2;
for (var a : attrs) {
l += 6 + ((BoundAttribute)a).payloadLen();
}
return l;
}
private static int parameterAnnotationsSize(List<List<Annotation>> pans) {
int l = 1;
for (var ans : pans) {
l += annotationsSize(ans);
}
return l;
}
private static int annotationsSize(List<Annotation> ans) {
int l = 2;
for (var an : ans) {
l += annotationSize(an);
}
return l;
}
private static int typeAnnotationsSize(List<TypeAnnotation> ans) {
int l = 2;
for (var an : ans) {
l += 2 + an.targetInfo().size() + 2 * an.targetPath().size() + annotationSize(an);
}
return l;
}
private static int annotationSize(Annotation an) {
int l = 4;
for (var el : an.elements()) {
l += 2 + valueSize(el.value());
}
return l;
}
private static int valueSize(AnnotationValue val) {
return 1 + switch (val) {
case AnnotationValue.OfAnnotation oan ->
annotationSize(oan.annotation());
case AnnotationValue.OfArray oar -> {
int l = 2;
for (var v : oar.values()) {
l += valueSize(v);
}
yield l;
}
case AnnotationValue.OfConstant _, AnnotationValue.OfClass _ -> 2;
case AnnotationValue.OfEnum _ -> 4;
};
}
private static int moduleHashesSize(List<ModuleHashInfo> hashes) {
int l = 2;
for (var h : hashes) {
h.moduleName();
l += 4 + h.hash().length;
}
return l;
}
private int stackMapFrameSize(StackMapFrameInfo frame) {
int ft = frame.frameType();
if (ft < 64) return 1;
if (ft < 128) return 1 + verificationTypeSize(frame.stack().getFirst());
if (ft > 246) {
if (ft == 247) return 3 + verificationTypeSize(frame.stack().getFirst());
if (ft < 252) return 3;
if (ft < 255) {
var loc = frame.locals();
int l = 3;
for (int i = loc.size() + 251 - ft; i < loc.size(); i++) {
l += verificationTypeSize(loc.get(i));
}
return l;
}
if (ft == 255) {
int l = 7;
for (var vt : frame.stack()) {
l += verificationTypeSize(vt);
}
for (var vt : frame.locals()) {
l += verificationTypeSize(vt);
}
return l;
}
}
throw new IllegalArgumentException("Invalid stack map frame type " + ft);
}
private static int verificationTypeSize(StackMapFrameInfo.VerificationTypeInfo vti) {
return switch (vti) {
case StackMapFrameInfo.SimpleVerificationTypeInfo _ -> 1;
case StackMapFrameInfo.ObjectVerificationTypeInfo ovti -> {
ovti.classSymbol();
yield 3;
}
case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> 3;
};
}
private String className() {
return classModel.thisClass().asSymbol().displayName();
}
private String toString(AttributedElement ae) {
return switch (ae) {
case CodeModel m -> "Code attribute for " + toString(m.parent().get());
case FieldModel m -> "field %s.%s".formatted(
className(),
m.fieldName().stringValue());
case MethodModel m -> "method %s::%s(%s)".formatted(
className(),
m.methodName().stringValue(),
m.methodTypeSymbol().parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")));
case RecordComponentInfo i -> "Record component %s of class %s".formatted(
i.name().stringValue(),
className());
default -> "class " + className();
};
}
}

View File

@ -25,6 +25,7 @@
*/
package jdk.internal.classfile.impl.verifier;
import java.lang.constant.ClassDesc;
import java.util.LinkedList;
import java.util.List;
@ -33,6 +34,7 @@ import java.lang.classfile.constantpool.DynamicConstantPoolEntry;
import java.lang.classfile.constantpool.MemberRefEntry;
import java.lang.classfile.constantpool.NameAndTypeEntry;
import java.lang.reflect.AccessFlag;
import java.util.stream.Collectors;
import java.lang.classfile.ClassModel;
import java.lang.classfile.constantpool.ConstantPool;
import java.lang.classfile.MethodModel;
@ -129,6 +131,10 @@ public final class VerificationWrapper {
return m.methodType().stringValue();
}
String parameters() {
return m.methodTypeSymbol().parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(","));
}
int codeLength() {
return c == null ? 0 : c.codeLength();
}

View File

@ -41,6 +41,11 @@ import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType;
import static jdk.internal.classfile.impl.verifier.VerificationFrame.FLAG_THIS_UNINIT;
/**
* VerifierImpl performs selected checks and verifications of the class file
* format according to {@jvms 4.8 Format Checking},
* {@jvms 4.9 Constraints on Java Virtual Machine code},
* {@jvms 4.10 Verification of class Files} and {@jvms 6.5 Instructions}
*
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/classfile_constants.h.template">java.base/share/native/include/classfile_constants.h.template</a>
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/classfile/verifier.hpp">hotspot/share/classfile/verifier.hpp</a>
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/classfile/verifier.cpp">hotspot/share/classfile/verifier.cpp</a>
@ -110,22 +115,24 @@ public final class VerifierImpl {
public static List<VerifyError> verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer<String> 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);
var errors = new ArrayList<VerifyError>();
errors.addAll(new ParserVerifier(classModel).verify());
if (is_eligible_for_verification(klass)) {
if (klass.majorVersion() >= STACKMAP_ATTRIBUTE_MAJOR_VERSION) {
var verifierErrors = new VerifierImpl(klass, classHierarchyResolver, logger).verify_class();
if (!verifierErrors.isEmpty() && klass.majorVersion() < NOFAILOVER_MAJOR_VERSION) {
log_info(logger, "Fail over class verification to old verifier for: %s", klass.thisClassName());
errors.addAll(inference_verify(klass));
} else {
errors.addAll(verifierErrors);
}
} else {
return errors;
errors.addAll(inference_verify(klass));
}
} else {
return inference_verify(klass);
}
return errors;
} finally {
log_info(logger, "End class verification for: %s", klass.thisClassName());
}
@ -281,7 +288,7 @@ public final class VerifierImpl {
void verify_method(VerificationWrapper.MethodWrapper m, List<VerifyError> errorsCollector) {
try {
verify_method(m, m.maxLocals(), m.maxStack(), m.stackMapTableRawData());
verify_method(m);
} catch (VerifyError err) {
errorsCollector.add(err);
} catch (Error | Exception e) {
@ -290,9 +297,14 @@ public final class VerifierImpl {
}
@SuppressWarnings("fallthrough")
void verify_method(VerificationWrapper.MethodWrapper m, int max_locals, int max_stack, byte[] stackmap_data) {
void verify_method(VerificationWrapper.MethodWrapper m) {
_method = m;
log_info(_logger, "Verifying method %s%s", m.name(), m.descriptor());
byte[] codeArray = m.codeArray();
if (codeArray == null) verifyError("Missing Code attribute");
int max_locals = m.maxLocals();
int max_stack = m.maxStack();
byte[] stackmap_data = m.stackMapTableRawData();
var cp = m.constantPool();
if (!VerificationSignature.isValidMethodSignature(m.descriptor())) verifyError("Invalid method signature");
VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, this);
@ -302,7 +314,7 @@ public final class VerifierImpl {
if (code_length < 1 || code_length > MAX_CODE_SIZE) {
verifyError(String.format("Invalid method Code length %d", code_length));
}
var code = ByteBuffer.wrap(_method.codeArray(), 0, _method.codeLength());
var code = ByteBuffer.wrap(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);
@ -1816,16 +1828,16 @@ public final class VerifierImpl {
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));
throw new VerifyError(String.format("%s in %s::%s(%s) @%d %s", msg, _klass.thisClassName(), _method.name(), _method.parameters(), bci, errorContext).trim());
}
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));
throw new VerifyError(String.format("%s in %s::%s(%s) @%d %s%n while assigning %s%n to %s", msg, _klass.thisClassName(), _method.name(), _method.parameters(), 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()));
throw new VerifyError(String.format("%s in %s::%s(%s)", msg, _klass.thisClassName(), _method.name(), _method.parameters()));
}
}

View File

@ -730,13 +730,13 @@ class ClassPrinterTest {
<nest_host>Phee</nest_host>
<nest_members><member>Phoo</member><member>Boo</member><member>Bee</member></nest_members>
<record_components>
<record>
<component>
<name>fee</name>
<type>LPhoo;</type>
<attributes><attribute>Signature</attribute><attribute>RuntimeInvisibleTypeAnnotations</attribute></attributes>
<signature>LPhoo;</signature>
<invisible_type_annotations>
<anno><annotation_class>LBoo;</annotation_class><target_info>FIELD</target_info><values></values></anno></invisible_type_annotations></record></record_components>
<anno><annotation_class>LBoo;</annotation_class><target_info>FIELD</target_info><values></values></anno></invisible_type_annotations></component></record_components>
<invisible_annotations>
<anno><annotation_class>LPhoo;</annotation_class><values><pair><name>flfl</name><value><float>2.0</float></value></pair><pair><name>frfl</name><value><float>3.0</float></value></pair></values></anno></invisible_annotations>
<permitted_subclasses><subclass>Boo</subclass><subclass>Phoo</subclass></permitted_subclasses>

View File

@ -24,20 +24,30 @@
/*
* @test
* @summary Testing ClassFile Verifier.
* @enablePreview
* @run junit VerifierSelfTest
*/
import java.io.IOException;
import java.lang.constant.ClassDesc;
import static java.lang.constant.ConstantDescs.*;
import java.lang.invoke.MethodHandleInfo;
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.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.lang.classfile.ClassHierarchyResolver;
import java.lang.classfile.ClassFile;
import java.lang.classfile.CodeModel;
import java.lang.classfile.MethodModel;
import java.lang.classfile.*;
import java.lang.classfile.attribute.*;
import java.lang.classfile.components.ClassPrinter;
import java.lang.constant.ModuleDesc;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
class VerifierSelfTest {
@ -83,4 +93,289 @@ class VerifierSelfTest {
throw new AssertionError("expected verification failure");
}
}
@Test
void testParserVerification() {
var cc = ClassFile.of();
var cd_test = ClassDesc.of("ParserVerificationTestClass");
var indexes = new Object[9];
var clm = cc.parse(cc.build(cd_test, clb -> {
clb.withFlags(ClassFile.ACC_INTERFACE | ClassFile.ACC_FINAL);
var cp = clb.constantPool();
var ce_valid = cp.classEntry(cd_test);
var ce_invalid = cp.classEntry(cp.utf8Entry("invalid.class.name"));
indexes[0] = ce_invalid.index();
var nate_invalid_field = cp.nameAndTypeEntry("field;", CD_int);
var nate_invalid_method = cp.nameAndTypeEntry("method;", MTD_void);
var bsme = cp.bsmEntry(BSM_INVOKE, List.of());
indexes[1] = cp.methodTypeEntry(cp.utf8Entry("invalid method type")).index();
indexes[2] = cp.constantDynamicEntry(bsme, nate_invalid_method).index();
indexes[3] = cp.invokeDynamicEntry(bsme, nate_invalid_field).index();
indexes[4] = cp.fieldRefEntry(ce_invalid, nate_invalid_method).index();
indexes[5] = cp.methodRefEntry(ce_invalid, nate_invalid_field).index();
indexes[6] = cp.interfaceMethodRefEntry(ce_invalid, nate_invalid_field).index();
indexes[7] = cp.methodHandleEntry(MethodHandleInfo.REF_getField, cp.methodRefEntry(cd_test, "method", MTD_void)).index();
indexes[8] = cp.methodHandleEntry(MethodHandleInfo.REF_invokeVirtual, cp.fieldRefEntry(cd_test, "field", CD_int)).index();
patch(clb,
CompilationIDAttribute.of("12345"),
DeprecatedAttribute.of(),
EnclosingMethodAttribute.of(cd_test, Optional.empty(), Optional.empty()),
InnerClassesAttribute.of(InnerClassInfo.of(cd_test, Optional.of(cd_test), Optional.of("inner"), 0)),
ModuleAttribute.of(ModuleDesc.of("m"), mab -> {}),
ModuleHashesAttribute.of("alg", List.of()),
ModuleMainClassAttribute.of(cd_test),
ModulePackagesAttribute.of(),
ModuleResolutionAttribute.of(0),
ModuleTargetAttribute.of("t"),
NestHostAttribute.of(cd_test),
NestMembersAttribute.ofSymbols(cd_test),
PermittedSubclassesAttribute.ofSymbols(cd_test),
RecordAttribute.of(RecordComponentInfo.of("c", CD_String, patch(
SignatureAttribute.of(Signature.of(CD_String)),
RuntimeVisibleAnnotationsAttribute.of(),
RuntimeInvisibleAnnotationsAttribute.of(),
RuntimeVisibleTypeAnnotationsAttribute.of(),
RuntimeInvisibleTypeAnnotationsAttribute.of()))),
RuntimeVisibleAnnotationsAttribute.of(),
RuntimeInvisibleAnnotationsAttribute.of(),
RuntimeVisibleTypeAnnotationsAttribute.of(),
RuntimeInvisibleTypeAnnotationsAttribute.of(),
SignatureAttribute.of(ClassSignature.of(Signature.ClassTypeSig.of(cd_test))),
SourceDebugExtensionAttribute.of("sde".getBytes()),
SourceFileAttribute.of("ParserVerificationTestClass.java"),
SourceIDAttribute.of("sID"),
SyntheticAttribute.of())
.withInterfaceSymbols(CD_List, CD_List)
.withField("f", CD_String, fb -> patch(fb,
ConstantValueAttribute.of(0),
DeprecatedAttribute.of(),
RuntimeVisibleAnnotationsAttribute.of(),
RuntimeInvisibleAnnotationsAttribute.of(),
RuntimeVisibleTypeAnnotationsAttribute.of(),
RuntimeInvisibleTypeAnnotationsAttribute.of(),
SignatureAttribute.of(Signature.of(CD_String)),
SyntheticAttribute.of()))
.withField("/", CD_int, 0)
.withField("/", CD_int, 0)
.withMethod("m", MTD_void, ClassFile.ACC_ABSTRACT | ClassFile.ACC_STATIC, mb -> patch(mb,
AnnotationDefaultAttribute.of(AnnotationValue.ofInt(0)),
DeprecatedAttribute.of(),
ExceptionsAttribute.ofSymbols(CD_Exception),
MethodParametersAttribute.of(MethodParameterInfo.ofParameter(Optional.empty(), 0)),
RuntimeVisibleAnnotationsAttribute.of(),
RuntimeInvisibleAnnotationsAttribute.of(),
RuntimeVisibleParameterAnnotationsAttribute.of(List.of()),
RuntimeInvisibleParameterAnnotationsAttribute.of(List.of()),
SignatureAttribute.of(MethodSignature.of(MTD_void)),
SyntheticAttribute.of())
.withCode(cob ->
cob.iconst_0()
.ifThen(CodeBuilder::nop)
.return_()
.with(new CloneAttribute(StackMapTableAttribute.of(List.of())))
.with(new CloneAttribute(CharacterRangeTableAttribute.of(List.of())))
.with(new CloneAttribute(LineNumberTableAttribute.of(List.of())))
.with(new CloneAttribute(LocalVariableTableAttribute.of(List.of())))
.with(new CloneAttribute(LocalVariableTypeTableAttribute.of(List.of())))))
.withMethod("<>", MTD_void, ClassFile.ACC_NATIVE, mb -> {})
.withMethod("<>", MTD_void, ClassFile.ACC_NATIVE, mb -> {})
.withMethod(INIT_NAME, MTD_void, 0, mb -> {})
.withMethod(CLASS_INIT_NAME, MTD_void, 0, mb -> {});
}));
var found = cc.verify(clm).stream().map(VerifyError::getMessage).collect(Collectors.toCollection(LinkedList::new));
var expected = """
Invalid class name: invalid.class.name at constant pool index %1$d in class ParserVerificationTestClass
Bad method descriptor: invalid method type at constant pool index %2$d in class ParserVerificationTestClass
not a valid reference type descriptor: ()V at constant pool index %3$d in class ParserVerificationTestClass
Bad method descriptor: I at constant pool index %4$d in class ParserVerificationTestClass
not a valid reference type descriptor: ()V at constant pool index %5$d in class ParserVerificationTestClass
Invalid class name: invalid.class.name at constant pool index %5$d in class ParserVerificationTestClass
Illegal field name method; in class ParserVerificationTestClass at constant pool index %5$d in class ParserVerificationTestClass
Bad method descriptor: I at constant pool index %6$d in class ParserVerificationTestClass
Invalid class name: invalid.class.name at constant pool index %6$d in class ParserVerificationTestClass
Illegal method name field; in class ParserVerificationTestClass at constant pool index %6$d in class ParserVerificationTestClass
Bad method descriptor: I at constant pool index %7$d in class ParserVerificationTestClass
Invalid class name: invalid.class.name at constant pool index %7$d in class ParserVerificationTestClass
Illegal method name field; in class ParserVerificationTestClass at constant pool index %7$d in class ParserVerificationTestClass
not a valid reference type descriptor: ()V at constant pool index %8$d in class ParserVerificationTestClass
Bad method descriptor: I at constant pool index %9$d in class ParserVerificationTestClass
Duplicate interface List in class ParserVerificationTestClass
Illegal field name / in class ParserVerificationTestClass
Duplicate field name / with signature I in class ParserVerificationTestClass
Illegal field name / in class ParserVerificationTestClass
Illegal method name <> in class ParserVerificationTestClass
Duplicate method name <> with signature ()V in class ParserVerificationTestClass
Illegal method name <> in class ParserVerificationTestClass
Interface cannot have a method named <init> in class ParserVerificationTestClass
Method <clinit> is not static in class ParserVerificationTestClass
Multiple CompilationID attributes in class ParserVerificationTestClass
Wrong CompilationID attribute length in class ParserVerificationTestClass
Wrong Deprecated attribute length in class ParserVerificationTestClass
Multiple EnclosingMethod attributes in class ParserVerificationTestClass
Wrong EnclosingMethod attribute length in class ParserVerificationTestClass
Class is both outer and inner class in class ParserVerificationTestClass
Multiple InnerClasses attributes in class ParserVerificationTestClass
Class is both outer and inner class in class ParserVerificationTestClass
Wrong InnerClasses attribute length in class ParserVerificationTestClass
Multiple Module attributes in class ParserVerificationTestClass
Wrong Module attribute length in class ParserVerificationTestClass
Multiple ModuleHashes attributes in class ParserVerificationTestClass
Wrong ModuleHashes attribute length in class ParserVerificationTestClass
Multiple ModuleMainClass attributes in class ParserVerificationTestClass
Wrong ModuleMainClass attribute length in class ParserVerificationTestClass
Multiple ModulePackages attributes in class ParserVerificationTestClass
Wrong ModulePackages attribute length in class ParserVerificationTestClass
Multiple ModuleResolution attributes in class ParserVerificationTestClass
Wrong ModuleResolution attribute length in class ParserVerificationTestClass
Multiple ModuleTarget attributes in class ParserVerificationTestClass
Wrong ModuleTarget attribute length in class ParserVerificationTestClass
Multiple NestHost attributes in class ParserVerificationTestClass
Wrong NestHost attribute length in class ParserVerificationTestClass
Conflicting NestHost and NestMembers attributes in class ParserVerificationTestClass
Multiple NestMembers attributes in class ParserVerificationTestClass
Conflicting NestHost and NestMembers attributes in class ParserVerificationTestClass
Wrong NestMembers attribute length in class ParserVerificationTestClass
PermittedSubclasses attribute in final class ParserVerificationTestClass
Multiple PermittedSubclasses attributes in class ParserVerificationTestClass
PermittedSubclasses attribute in final class ParserVerificationTestClass
Wrong PermittedSubclasses attribute length in class ParserVerificationTestClass
Multiple Record attributes in class ParserVerificationTestClass
Wrong Record attribute length in class ParserVerificationTestClass
Multiple RuntimeVisibleAnnotations attributes in class ParserVerificationTestClass
Wrong RuntimeVisibleAnnotations attribute length in class ParserVerificationTestClass
Multiple RuntimeInvisibleAnnotations attributes in class ParserVerificationTestClass
Wrong RuntimeInvisibleAnnotations attribute length in class ParserVerificationTestClass
Multiple RuntimeVisibleTypeAnnotations attributes in class ParserVerificationTestClass
Wrong RuntimeVisibleTypeAnnotations attribute length in class ParserVerificationTestClass
Multiple RuntimeInvisibleTypeAnnotations attributes in class ParserVerificationTestClass
Wrong RuntimeInvisibleTypeAnnotations attribute length in class ParserVerificationTestClass
Multiple Signature attributes in class ParserVerificationTestClass
Wrong Signature attribute length in class ParserVerificationTestClass
Multiple SourceDebugExtension attributes in class ParserVerificationTestClass
Multiple SourceFile attributes in class ParserVerificationTestClass
Wrong SourceFile attribute length in class ParserVerificationTestClass
Multiple SourceID attributes in class ParserVerificationTestClass
Wrong SourceID attribute length in class ParserVerificationTestClass
Wrong Synthetic attribute length in class ParserVerificationTestClass
Bad constant value type in field ParserVerificationTestClass.f
Multiple ConstantValue attributes in field ParserVerificationTestClass.f
Bad constant value type in field ParserVerificationTestClass.f
Wrong ConstantValue attribute length in field ParserVerificationTestClass.f
Wrong Deprecated attribute length in field ParserVerificationTestClass.f
Multiple RuntimeVisibleAnnotations attributes in field ParserVerificationTestClass.f
Wrong RuntimeVisibleAnnotations attribute length in field ParserVerificationTestClass.f
Multiple RuntimeInvisibleAnnotations attributes in field ParserVerificationTestClass.f
Wrong RuntimeInvisibleAnnotations attribute length in field ParserVerificationTestClass.f
Multiple RuntimeVisibleTypeAnnotations attributes in field ParserVerificationTestClass.f
Wrong RuntimeVisibleTypeAnnotations attribute length in field ParserVerificationTestClass.f
Multiple RuntimeInvisibleTypeAnnotations attributes in field ParserVerificationTestClass.f
Wrong RuntimeInvisibleTypeAnnotations attribute length in field ParserVerificationTestClass.f
Multiple Signature attributes in field ParserVerificationTestClass.f
Wrong Signature attribute length in field ParserVerificationTestClass.f
Wrong Synthetic attribute length in field ParserVerificationTestClass.f
Multiple AnnotationDefault attributes in method ParserVerificationTestClass::m()
Wrong AnnotationDefault attribute length in method ParserVerificationTestClass::m()
Wrong Deprecated attribute length in method ParserVerificationTestClass::m()
Multiple Exceptions attributes in method ParserVerificationTestClass::m()
Wrong Exceptions attribute length in method ParserVerificationTestClass::m()
Multiple MethodParameters attributes in method ParserVerificationTestClass::m()
Wrong MethodParameters attribute length in method ParserVerificationTestClass::m()
Multiple RuntimeVisibleAnnotations attributes in method ParserVerificationTestClass::m()
Wrong RuntimeVisibleAnnotations attribute length in method ParserVerificationTestClass::m()
Multiple RuntimeInvisibleAnnotations attributes in method ParserVerificationTestClass::m()
Wrong RuntimeInvisibleAnnotations attribute length in method ParserVerificationTestClass::m()
Multiple RuntimeVisibleParameterAnnotations attributes in method ParserVerificationTestClass::m()
Wrong RuntimeVisibleParameterAnnotations attribute length in method ParserVerificationTestClass::m()
Multiple RuntimeInvisibleParameterAnnotations attributes in method ParserVerificationTestClass::m()
Wrong RuntimeInvisibleParameterAnnotations attribute length in method ParserVerificationTestClass::m()
Multiple Signature attributes in method ParserVerificationTestClass::m()
Wrong Signature attribute length in method ParserVerificationTestClass::m()
Wrong Synthetic attribute length in method ParserVerificationTestClass::m()
Code attribute in native or abstract method ParserVerificationTestClass::m()
Wrong StackMapTable attribute length in Code attribute for method ParserVerificationTestClass::m()
Wrong CharacterRangeTable attribute length in Code attribute for method ParserVerificationTestClass::m()
Wrong LineNumberTable attribute length in Code attribute for method ParserVerificationTestClass::m()
Wrong LocalVariableTable attribute length in Code attribute for method ParserVerificationTestClass::m()
Wrong LocalVariableTypeTable attribute length in Code attribute for method ParserVerificationTestClass::m()
Multiple StackMapTable attributes in Code attribute for method ParserVerificationTestClass::m()
Multiple Signature attributes in Record component c of class ParserVerificationTestClass
Wrong Signature attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeVisibleAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeVisibleAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeInvisibleAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeInvisibleAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeVisibleTypeAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeVisibleTypeAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeInvisibleTypeAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeInvisibleTypeAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple Signature attributes in Record component c of class ParserVerificationTestClass
Wrong Signature attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeVisibleAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeVisibleAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeInvisibleAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeInvisibleAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeVisibleTypeAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeVisibleTypeAnnotations attribute length in Record component c of class ParserVerificationTestClass
Multiple RuntimeInvisibleTypeAnnotations attributes in Record component c of class ParserVerificationTestClass
Wrong RuntimeInvisibleTypeAnnotations attribute length in Record component c of class ParserVerificationTestClass
Missing Code attribute in ParserVerificationTestClass::<init>() @0
Missing Code attribute in ParserVerificationTestClass::<clinit>() @0
""".formatted(indexes).lines().filter(exp -> !found.remove(exp)).toList();
if (!found.isEmpty() || !expected.isEmpty()) {
ClassPrinter.toYaml(clm, ClassPrinter.Verbosity.TRACE_ALL, System.out::print);
fail("""
Expected:
%s
Found:
%s
""".formatted(expected.stream().collect(Collectors.joining("\n ")), found.stream().collect(Collectors.joining("\n "))));
}
}
private static class CloneAttribute extends CustomAttribute<CloneAttribute> {
CloneAttribute(Attribute a) {
super(new AttributeMapper<CloneAttribute>(){
@Override
public String name() {
return a.attributeName();
}
@Override
public CloneAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) {
throw new UnsupportedOperationException();
}
@Override
public void writeAttribute(BufWriter buf, CloneAttribute attr) {
int start = buf.size();
a.attributeMapper().writeAttribute(buf, a);
buf.writeU1(0); //writes additional byte to the attribute payload
buf.patchInt(start + 2, 4, buf.size() - start - 6);
}
@Override
public AttributeMapper.AttributeStability stability() {
return a.attributeMapper().stability();
}
});
}
}
private static <B extends ClassFileBuilder> B patch(B b, Attribute... attrs) {
for (var a : attrs) {
b.with(a).with(new CloneAttribute(a));
}
return b;
}
private static List<Attribute<?>> patch(Attribute... attrs) {
var lst = new ArrayList<Attribute<?>>(attrs.length * 2);
for (var a : attrs) {
lst.add(a);
lst.add(new CloneAttribute(a));
}
return lst;
}
}