mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8320396: Class-File API ClassModel::verify should include checks from hotspot/share/classfile/classFileParser.cpp
Reviewed-by: liach, mcimadamore
This commit is contained in:
parent
2ab8ab5613
commit
22ef827e2c
6 changed files with 862 additions and 34 deletions
|
@ -28,6 +28,7 @@ import java.lang.constant.ConstantDesc;
|
||||||
import java.lang.constant.DirectMethodHandleDesc;
|
import java.lang.constant.DirectMethodHandleDesc;
|
||||||
import java.lang.reflect.AccessFlag;
|
import java.lang.reflect.AccessFlag;
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -35,7 +36,6 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
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 Style style;
|
||||||
private final ConstantDesc name;
|
private final ConstantDesc name;
|
||||||
private final Node[] nodes;
|
protected final List<Node> nodes;
|
||||||
|
|
||||||
public ListNodeImpl(Style style, ConstantDesc name, Stream<Node> nodes) {
|
public ListNodeImpl(Style style, ConstantDesc name, Stream<Node> nodes) {
|
||||||
this.style = style;
|
this.style = style;
|
||||||
this.name = name;
|
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
|
@Override
|
||||||
|
@ -103,18 +109,23 @@ public final class ClassPrinterImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node get(int index) {
|
public Node get(int index) {
|
||||||
Objects.checkIndex(index, nodes.length);
|
return nodes.get(index);
|
||||||
return nodes[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return nodes.length;
|
return nodes.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class MapNodeImpl implements MapNode {
|
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 Style style;
|
||||||
private final ConstantDesc name;
|
private final ConstantDesc name;
|
||||||
private final Map<ConstantDesc, Node> map;
|
private final Map<ConstantDesc, Node> map;
|
||||||
|
@ -198,9 +209,19 @@ public final class ClassPrinterImpl {
|
||||||
|
|
||||||
|
|
||||||
MapNodeImpl with(Node... nodes) {
|
MapNodeImpl with(Node... nodes) {
|
||||||
for (var n : nodes)
|
for (var n : nodes) {
|
||||||
if (n != null && map.put(n.name(), n) != null)
|
if (n != null) {
|
||||||
throw new AssertionError("Double entry of " + n.name() + " into " + name);
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -975,7 +996,7 @@ public final class ClassPrinterImpl {
|
||||||
nodes.add(leaf("module main class", mmca.mainClass().name().stringValue()));
|
nodes.add(leaf("module main class", mmca.mainClass().name().stringValue()));
|
||||||
case RecordAttribute ra ->
|
case RecordAttribute ra ->
|
||||||
nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream()
|
nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream()
|
||||||
.map(rc -> new MapNodeImpl(BLOCK, "record")
|
.map(rc -> new MapNodeImpl(BLOCK, "component")
|
||||||
.with(leafs(
|
.with(leafs(
|
||||||
"name", rc.name().stringValue(),
|
"name", rc.name().stringValue(),
|
||||||
"type", rc.descriptor().stringValue()))
|
"type", rc.descriptor().stringValue()))
|
||||||
|
|
|
@ -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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
*/
|
*/
|
||||||
package jdk.internal.classfile.impl.verifier;
|
package jdk.internal.classfile.impl.verifier;
|
||||||
|
|
||||||
|
import java.lang.constant.ClassDesc;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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.MemberRefEntry;
|
||||||
import java.lang.classfile.constantpool.NameAndTypeEntry;
|
import java.lang.classfile.constantpool.NameAndTypeEntry;
|
||||||
import java.lang.reflect.AccessFlag;
|
import java.lang.reflect.AccessFlag;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.lang.classfile.ClassModel;
|
import java.lang.classfile.ClassModel;
|
||||||
import java.lang.classfile.constantpool.ConstantPool;
|
import java.lang.classfile.constantpool.ConstantPool;
|
||||||
import java.lang.classfile.MethodModel;
|
import java.lang.classfile.MethodModel;
|
||||||
|
@ -129,6 +131,10 @@ public final class VerificationWrapper {
|
||||||
return m.methodType().stringValue();
|
return m.methodType().stringValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String parameters() {
|
||||||
|
return m.methodTypeSymbol().parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
int codeLength() {
|
int codeLength() {
|
||||||
return c == null ? 0 : c.codeLength();
|
return c == null ? 0 : c.codeLength();
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,11 @@ import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType;
|
||||||
import static jdk.internal.classfile.impl.verifier.VerificationFrame.FLAG_THIS_UNINIT;
|
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/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.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>
|
* @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) {
|
public static List<VerifyError> verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer<String> logger) {
|
||||||
var klass = new VerificationWrapper(classModel);
|
var klass = new VerificationWrapper(classModel);
|
||||||
if (!is_eligible_for_verification(klass)) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
log_info(logger, "Start class verification for: %s", klass.thisClassName());
|
log_info(logger, "Start class verification for: %s", klass.thisClassName());
|
||||||
try {
|
try {
|
||||||
if (klass.majorVersion() >= STACKMAP_ATTRIBUTE_MAJOR_VERSION) {
|
var errors = new ArrayList<VerifyError>();
|
||||||
var errors = new VerifierImpl(klass, classHierarchyResolver, logger).verify_class();
|
errors.addAll(new ParserVerifier(classModel).verify());
|
||||||
if (!errors.isEmpty() && klass.majorVersion() < NOFAILOVER_MAJOR_VERSION) {
|
if (is_eligible_for_verification(klass)) {
|
||||||
log_info(logger, "Fail over class verification to old verifier for: %s", klass.thisClassName());
|
if (klass.majorVersion() >= STACKMAP_ATTRIBUTE_MAJOR_VERSION) {
|
||||||
return inference_verify(klass);
|
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 {
|
} else {
|
||||||
return errors;
|
errors.addAll(inference_verify(klass));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return inference_verify(klass);
|
|
||||||
}
|
}
|
||||||
|
return errors;
|
||||||
} finally {
|
} finally {
|
||||||
log_info(logger, "End class verification for: %s", klass.thisClassName());
|
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) {
|
void verify_method(VerificationWrapper.MethodWrapper m, List<VerifyError> errorsCollector) {
|
||||||
try {
|
try {
|
||||||
verify_method(m, m.maxLocals(), m.maxStack(), m.stackMapTableRawData());
|
verify_method(m);
|
||||||
} catch (VerifyError err) {
|
} catch (VerifyError err) {
|
||||||
errorsCollector.add(err);
|
errorsCollector.add(err);
|
||||||
} catch (Error | Exception e) {
|
} catch (Error | Exception e) {
|
||||||
|
@ -290,9 +297,14 @@ public final class VerifierImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("fallthrough")
|
@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;
|
_method = m;
|
||||||
log_info(_logger, "Verifying method %s%s", m.name(), m.descriptor());
|
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();
|
var cp = m.constantPool();
|
||||||
if (!VerificationSignature.isValidMethodSignature(m.descriptor())) verifyError("Invalid method signature");
|
if (!VerificationSignature.isValidMethodSignature(m.descriptor())) verifyError("Invalid method signature");
|
||||||
VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, this);
|
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) {
|
if (code_length < 1 || code_length > MAX_CODE_SIZE) {
|
||||||
verifyError(String.format("Invalid method Code length %d", code_length));
|
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);
|
byte[] code_data = generate_code_data(code, code_length);
|
||||||
int ex_minmax[] = new int[] {code_length, -1};
|
int ex_minmax[] = new int[] {code_length, -1};
|
||||||
verify_exception_handler_table(code_length, code_data, ex_minmax);
|
verify_exception_handler_table(code_length, code_data, ex_minmax);
|
||||||
|
@ -1816,16 +1828,16 @@ public final class VerifierImpl {
|
||||||
|
|
||||||
void verifyError(String msg) {
|
void verifyError(String msg) {
|
||||||
dumpMethod();
|
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) {
|
void verifyError(String msg, VerificationFrame from, VerificationFrame target) {
|
||||||
dumpMethod();
|
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) {
|
void classError(String msg) {
|
||||||
dumpMethod();
|
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -730,13 +730,13 @@ class ClassPrinterTest {
|
||||||
<nest_host>Phee</nest_host>
|
<nest_host>Phee</nest_host>
|
||||||
<nest_members><member>Phoo</member><member>Boo</member><member>Bee</member></nest_members>
|
<nest_members><member>Phoo</member><member>Boo</member><member>Bee</member></nest_members>
|
||||||
<record_components>
|
<record_components>
|
||||||
<record>
|
<component>
|
||||||
<name>fee</name>
|
<name>fee</name>
|
||||||
<type>LPhoo;</type>
|
<type>LPhoo;</type>
|
||||||
<attributes><attribute>Signature</attribute><attribute>RuntimeInvisibleTypeAnnotations</attribute></attributes>
|
<attributes><attribute>Signature</attribute><attribute>RuntimeInvisibleTypeAnnotations</attribute></attributes>
|
||||||
<signature>LPhoo;</signature>
|
<signature>LPhoo;</signature>
|
||||||
<invisible_type_annotations>
|
<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>
|
<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>
|
<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>
|
<permitted_subclasses><subclass>Boo</subclass><subclass>Phoo</subclass></permitted_subclasses>
|
||||||
|
|
|
@ -24,20 +24,30 @@
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @summary Testing ClassFile Verifier.
|
* @summary Testing ClassFile Verifier.
|
||||||
|
* @enablePreview
|
||||||
* @run junit VerifierSelfTest
|
* @run junit VerifierSelfTest
|
||||||
*/
|
*/
|
||||||
import java.io.IOException;
|
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.net.URI;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
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.util.stream.Stream;
|
||||||
import java.lang.classfile.ClassHierarchyResolver;
|
import java.lang.classfile.*;
|
||||||
import java.lang.classfile.ClassFile;
|
import java.lang.classfile.attribute.*;
|
||||||
import java.lang.classfile.CodeModel;
|
import java.lang.classfile.components.ClassPrinter;
|
||||||
import java.lang.classfile.MethodModel;
|
import java.lang.constant.ModuleDesc;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
class VerifierSelfTest {
|
class VerifierSelfTest {
|
||||||
|
|
||||||
|
@ -83,4 +93,289 @@ class VerifierSelfTest {
|
||||||
throw new AssertionError("expected verification failure");
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue