diff --git a/src/java.base/share/classes/java/lang/classfile/Attributes.java b/src/java.base/share/classes/java/lang/classfile/Attributes.java index b0a88dbd0ae..29d132f0908 100644 --- a/src/java.base/share/classes/java/lang/classfile/Attributes.java +++ b/src/java.base/share/classes/java/lang/classfile/Attributes.java @@ -250,7 +250,7 @@ public static AttributeMapper lineNumberTable() { /** * {@return Attribute mapper for the {@code LoadableDescriptors} attribute} - * @since 23 + * @since Valhalla */ public static AttributeMapper loadableDescriptors() { return LoadableDescriptorsMapper.INSTANCE; diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java b/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java index 22fc75e0dfb..1c5493848a1 100644 --- a/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java +++ b/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java @@ -24,6 +24,9 @@ */ package java.lang.classfile.attribute; +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; import java.lang.classfile.constantpool.Utf8Entry; import java.lang.constant.ClassDesc; import java.util.Arrays; @@ -32,18 +35,31 @@ import java.lang.classfile.Attribute; import java.lang.classfile.ClassElement; import java.lang.classfile.constantpool.ClassEntry; + +import jdk.internal.classfile.impl.AbstractPoolEntry; import jdk.internal.classfile.impl.BoundAttribute; import jdk.internal.classfile.impl.UnboundAttribute; import jdk.internal.classfile.impl.Util; import jdk.internal.javac.PreviewFeature; /** - *

This is NOT part of any supported API. - * If you write code that depends on this, you do so at your own risk. - * This code and its internal interfaces are subject to change or - * deletion without notice. + * Models the {@link Attributes#loadableDescriptors() LoadableDescriptors} + * attribute (JVMS {@jvms 4.7.32}), which suggests the JVM may load mentioned + * types before the {@code class} file carrying this attribute is loaded. + *

+ * This attribute only appears on classes, and does not permit {@linkplain + * AttributeMapper#allowMultiple multiple instances} in a class. It has a + * data dependency on the {@linkplain AttributeMapper.AttributeStability#CP_REFS + * constant pool}. + *

+ * The attribute was introduced in the Java SE Platform version XX, major + * version {@value ClassFile#JAVA_25_VERSION}. (FIXME) + * + * @see Attributes#loadableDescriptors() + * @jvms 4.7.32 The {@code LoadableDescriptors} Attribute + * @since Valhalla */ -@PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API) +@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) public sealed interface LoadableDescriptorsAttribute extends Attribute, ClassElement permits BoundAttribute.BoundLoadableDescriptorsAttribute, UnboundAttribute.UnboundLoadableDescriptorsAttribute { @@ -53,6 +69,13 @@ public sealed interface LoadableDescriptorsAttribute */ List loadableDescriptors(); + /** + * {@return the list of loadable descriptors, as nominal descriptors} + */ + default List loadableDescriptorSymbols() { + return Util.mappedList(loadableDescriptors(), Util::fieldTypeSymbol); + } + /** * {@return a {@code LoadableDescriptors} attribute} * @param loadableDescriptors the loadable descriptors diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java index 0775d800127..3b83b6d9625 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,15 +41,13 @@ import jdk.internal.classfile.impl.BoundAttribute; import jdk.internal.classfile.impl.Util; -import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME; -import static java.lang.constant.ConstantDescs.INIT_NAME; +import static java.lang.constant.ConstantDescs.*; -/** - * ParserVerifier performs selected checks of the class file format according to - * {@jvms 4.8 Format Checking} - * - * @see hotspot/share/classfile/classFileParser.cpp - */ +/// ParserVerifier performs selected checks of the class file format according to +/// {@jvms 4.8 Format Checking}. +/// +/// From `classFileParser.cpp`. +/// public record ParserVerifier(ClassModel classModel) { List verify() { @@ -277,8 +275,14 @@ private void verifyAttribute(AttributedElement ae, Attribute a, List 2 + 4 * lta.lineNumbers().size(); - case LoadableDescriptorsAttribute lda -> - 2 + 2 * lda.loadableDescriptors().size(); + case LoadableDescriptorsAttribute lda -> { + for (var desc : lda.loadableDescriptorSymbols()) { + if (desc.equals(CD_void)) { + errors.add(new VerifyError("illegal signature %s".formatted(desc))); + } + } + yield 2 + 2 * lda.loadableDescriptors().size(); + } case LocalVariableTableAttribute lvta -> 2 + 10 * lvta.localVariables().size(); case LocalVariableTypeTableAttribute lvta -> @@ -349,8 +353,8 @@ private void verifyAttribute(AttributedElement ae, Attribute a, List - 2 + subSize(smta.entries(), frame -> stackMapFrameSize(frame)); + case StackMapTableAttribute _ -> + -1; // Not sufficient info for assert unset size case SyntheticAttribute _ -> 0; case UnknownAttribute _ -> diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java index a7d97c7ea37..8bd964650d3 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,10 +29,7 @@ import static jdk.internal.classfile.impl.RawBytecodeHelper.*; import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.*; -/** - * @see hotspot/share/interpreter/bytecodes.hpp - * @see hotspot/share/interpreter/bytecodes.cpp - */ +/// From `bytecodes.cpp`. final class VerificationBytecodes { static final int _breakpoint = 202, diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java index 13aac2c92c6..d096b78a67d 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,12 +24,14 @@ */ package jdk.internal.classfile.impl.verifier; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.Utf8Entry; import java.util.Arrays; +import java.util.Set; -/** - * @see hotspot/share/classfile/stackMapFrame.hpp - * @see hotspot/share/classfile/stackMapFrame.cpp - */ +import jdk.internal.classfile.impl.TemporaryConstantPool; + +/// From `stackMapFrame.cpp`. class VerificationFrame { public static final int FLAG_THIS_UNINIT = 0x01; @@ -40,9 +42,11 @@ class VerificationFrame { private final int _max_locals, _max_stack; private int _flags; private final VerificationType[] _locals, _stack; + private Set _assert_unset_fields; private final VerifierImpl _verifier; - public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, VerificationType[] locals, VerificationType[] stack, VerifierImpl v) { + public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, + VerificationType[] locals, VerificationType[] stack, Set assert_unset_fields, VerifierImpl v) { this._offset = offset; this._locals_size = locals_size; this._stack_size = stack_size; @@ -52,6 +56,7 @@ public VerificationFrame(int offset, int flags, int locals_size, int stack_size, this._flags = flags; this._locals = locals; this._stack = stack; + this._assert_unset_fields = assert_unset_fields; this._verifier = v; } @@ -112,6 +117,50 @@ boolean flag_this_uninit() { return (_flags & FLAG_THIS_UNINIT) == FLAG_THIS_UNINIT; } + Set assert_unset_fields() { + return _assert_unset_fields; + } + + void set_assert_unset_fields(Set table) { + _assert_unset_fields = table; + } + + // Called when verifying putfields to mark strict instance fields as satisfied + boolean satisfy_unset_field(Utf8Entry name, Utf8Entry signature) { + var nat = TemporaryConstantPool.INSTANCE.nameAndTypeEntry(name, signature); + return _assert_unset_fields.remove(nat); + } + + // Verify that all strict fields have been initialized + // Strict fields must be initialized before the super constructor is called + boolean verify_unset_fields_satisfied() { + return _assert_unset_fields.isEmpty(); + } + + // Merge incoming unset strict fields from StackMapTable with + // initial strict instance fields + Set merge_unset_fields(Set new_fields) { + // ClassFile API: We track all strict fields in another structure, noop here + return new_fields; + } + + boolean verify_unset_fields_compatibility(Set target_table) { + for (var e : _assert_unset_fields) { + if (!target_table.contains(e)) + return false; + } + return true; + } + + void unsatisfied_strict_fields_error(VerificationWrapper klass, int bci) { + _verifier.verifyError("All strict final fields must be initialized before super(): %d field(s), %s" + .formatted(_assert_unset_fields.size(), _assert_unset_fields)); + } + + void print_strict_fields(Set table) { + // ignore, we don't do stdout/err + } + void reset() { for (int i = 0; i < _max_locals; i++) { _locals[i] = VerificationType.bogus_type; @@ -184,7 +233,7 @@ void pop_stack_2(VerificationType type1, VerificationType type2) { pop_stack_ex(type2); } - VerificationFrame(int max_locals, int max_stack, VerifierImpl verifier) { + VerificationFrame(int max_locals, int max_stack, Set initial_strict_fields, VerifierImpl verifier) { _offset = 0; _locals_size = 0; _stack_size = 0; @@ -201,12 +250,13 @@ void pop_stack_2(VerificationType type1, VerificationType type2) { for (int i = 0; i < max_stack; i++) { _stack[i] = VerificationType.bogus_type; } + _assert_unset_fields = initial_strict_fields; } VerificationFrame frame_in_exception_handler(int flags) { return new VerificationFrame(_offset, flags, _locals_size, 0, _max_locals, _max_stack, _locals, new VerificationType[1], - _verifier); + _assert_unset_fields, _verifier); } void initialize_object(VerificationType old_object, VerificationType new_object) { @@ -302,6 +352,16 @@ boolean is_assignable_to(VerificationFrame target) { _verifier.verifyError("Bad type", this, target); } + // Check that assert unset fields are compatible + boolean compatible = verify_unset_fields_compatibility(target.assert_unset_fields()); + if (!compatible) { + print_strict_fields(assert_unset_fields()); + print_strict_fields(target.assert_unset_fields()); + _verifier.verifyError("Strict fields mismatch from %s to %s".formatted( + assert_unset_fields(), target.assert_unset_fields()), this, target); + return false; + } + if ((_flags | target.flags()) == target.flags()) { return true; } else { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationSignature.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationSignature.java index 9fa4a377852..4c0b21b6a37 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationSignature.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationSignature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ package jdk.internal.classfile.impl.verifier; +/// Relevant parts from `signatures.cpp`, such as `SignatureStream`. final class VerificationSignature { enum BasicType { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java index d5447d74316..9ecce410eff 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java @@ -24,19 +24,22 @@ */ package jdk.internal.classfile.impl.verifier; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import static jdk.internal.classfile.impl.verifier.VerificationType.ITEM_Object; import static jdk.internal.classfile.impl.verifier.VerificationType.ITEM_Uninitialized; import static jdk.internal.classfile.impl.verifier.VerificationType.ITEM_UninitializedThis; -/** - * @see hotspot/share/classfile/stackMapTable.hpp - * @see hotspot/share/classfile/stackMapTable.cpp - */ +/// From `stackMapTable.cpp`. class VerificationTable { private final int _code_length; private final int _frame_count; - private final VerificationFrame[] _frame_array; + private final List _frame_array; private final VerifierImpl _verifier; int get_frame_count() { @@ -44,7 +47,7 @@ int get_frame_count() { } int get_offset(int index) { - return _frame_array[index].offset(); + return _frame_array.get(index).offset(); } static class StackMapStream { @@ -76,32 +79,28 @@ boolean at_end() { } } - VerificationTable(byte[] stackmap_data, VerificationFrame init_frame, int max_locals, int max_stack, byte[] code_data, int code_len, + VerificationTable(StackMapReader reader, VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl v) { _verifier = v; - var reader = new StackMapReader(stackmap_data, code_data, code_len, cp, v); - _code_length = code_len; - _frame_count = reader.get_frame_count(); - _frame_array = new VerificationFrame[_frame_count]; + _code_length = reader.code_length(); + int _frame_count = reader.get_frame_count(); + _frame_array = new ArrayList<>(_frame_count); if (_frame_count > 0) { - VerificationFrame pre_frame = init_frame; - for (int i = 0; i < _frame_count; i++) { - VerificationFrame frame = reader.next(pre_frame, i == 0, max_locals, max_stack); - _frame_array[i] = frame; - int offset = frame.offset(); - if (offset >= code_len || code_data[offset] == 0) { - _verifier.verifyError("StackMapTable error: bad offset"); + while (!reader.at_end()) { + VerificationFrame frame = reader.next(); + if (frame != null) { + _frame_array.add(frame); } - pre_frame = frame; } } reader.check_end(); + this._frame_count = _frame_array.size(); } int get_index_from_offset(int offset) { int i = 0; for (; i < _frame_count; i++) { - if (_frame_array[i].offset() == offset) { + if (_frame_array.get(i).offset() == offset) { return i; } } @@ -117,7 +116,7 @@ boolean match_stackmap(VerificationFrame frame, int target, int frame_index, boo if (frame_index < 0 || frame_index >= _frame_count) { _verifier.verifyError(String.format("Expecting a stackmap frame at branch target %d", target)); } - VerificationFrame stackmap_frame = _frame_array[frame_index]; + VerificationFrame stackmap_frame = _frame_array.get(frame_index); boolean result = true; if (match) { result = frame.is_assignable_to(stackmap_frame); @@ -133,6 +132,7 @@ boolean match_stackmap(VerificationFrame frame, int target, int frame_index, boo frame.set_stack_size(ssize); frame.copy_stack(stackmap_frame); frame.set_flags(stackmap_frame.flags()); + frame.set_assert_unset_fields(stackmap_frame.assert_unset_fields()); } return result; } @@ -151,6 +151,12 @@ static class StackMapReader { private final byte[] _code_data; private final int _code_length; private final int _frame_count; + private int _parsed_frame_count; + private VerificationFrame _prev_frame; + char _max_locals, _max_stack; + final Set strictFields; + Set _assert_unset_fields_buffer; + boolean _first; void check_verification_type_array_size(int size, int max_size) { if (size < 0 || size > max_size) { @@ -169,25 +175,76 @@ public int get_frame_count() { return _frame_count; } + public VerificationFrame prev_frame() { + return _prev_frame; + } + + public byte[] code_data() { + return _code_data; + } + + public int code_length() { + return _code_length; + } + + public boolean at_end() { + return _stream.at_end(); + } + + public VerificationFrame next() { + _parsed_frame_count++; + check_size(); + VerificationFrame frame = next_helper(); + if (frame != null) { + check_offset(frame); + _prev_frame = frame; + } + return frame; + } + public void check_end() { - if (!_stream.at_end()) { - _verifier.classError("wrong attribute size"); + if (_frame_count != _parsed_frame_count) { + _verifier.verifyError("wrong attribute size"); } } private final VerifierImpl _verifier; - public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl context) { + public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, + VerificationFrame init_frame, char max_locals, char max_stack, + Set initial_strict_fields, + VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl context) { this._verifier = context; _stream = new StackMapStream(stackmapData, _verifier); + _code_data = code_data; + _code_length = code_len; + _parsed_frame_count = 0; + _prev_frame = init_frame; + _max_locals = max_locals; + _max_stack = max_stack; + strictFields = Set.copyOf(initial_strict_fields); + _assert_unset_fields_buffer = initial_strict_fields; + _first = true; if (stackmapData != null) { + _cp = cp; _frame_count = _stream.get_u2(); } else { + _cp = null; _frame_count = 0; } - _code_data = code_data; - _code_length = code_len; - _cp = cp; + } + + void check_offset(VerificationFrame frame) { + int offset = frame.offset(); + if (offset >= _code_length || _code_data[offset] == 0) { + _verifier.verifyError("StackMapTable error: bad offset"); + } + } + + void check_size() { + if (_frame_count < _parsed_frame_count) { + _verifier.verifyError("wrong attribute size"); + } } int chop(VerificationType[] locals, int length, int chops) { @@ -234,36 +291,72 @@ VerificationType parse_verification_type(int[] flags) { return VerificationType.bogus_type; } - public VerificationFrame next(VerificationFrame pre_frame, boolean first, int max_locals, int max_stack) { + VerificationFrame next_helper() { VerificationFrame frame; int offset; VerificationType[] locals = null; int frame_type = _stream.get_u1(); + if (frame_type == EARLY_LARVAL) { + int num_unset_fields = _stream.get_u2(); + Set new_fields = new HashSet<>(); + for (int i = 0; i < num_unset_fields; i++) { + int index = _stream.get_u2(); + if (!_cp.is_within_bounds(index) || _cp.tagAt(index) != VerifierImpl.JVM_CONSTANT_NameAndType) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Invalid constant pool index in early larval frame: %d".formatted(index))); + } + var tmp = _cp.cp.entryByIndex(index, NameAndTypeEntry.class); + if (!strictFields.contains(tmp)) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Strict fields not a subset of initial strict instance fields: %s".formatted(tmp))); + } else { + new_fields.add(tmp); + } + } + // Only modify strict instance fields the frame has uninitialized this + if (_prev_frame.flag_this_uninit()) { + _assert_unset_fields_buffer = _prev_frame.merge_unset_fields(new_fields); + } else if (!new_fields.isEmpty()) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Cannot have uninitialized strict fields after class initialization")); + } + // Continue reading frame data + if (at_end()) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Early larval frame must be followed by a base frame")); + } + frame_type = _stream.get_u1(); + if (frame_type == EARLY_LARVAL) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Early larval frame must be followed by a base frame")); + } + } if (frame_type < 64) { - if (first) { + if (_first) { offset = frame_type; - if (pre_frame.locals_size() > 0) { - locals = new VerificationType[pre_frame.locals_size()]; + if (_prev_frame.locals_size() > 0) { + locals = new VerificationType[_prev_frame.locals_size()]; } } else { - offset = pre_frame.offset() + frame_type + 1; - locals = pre_frame.locals(); + offset = _prev_frame.offset() + frame_type + 1; + locals = _prev_frame.locals(); } - frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), 0, max_locals, max_stack, locals, null, _verifier); - if (first && locals != null) { - frame.copy_locals(pre_frame); + frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), 0, _max_locals, _max_stack, locals, null, _assert_unset_fields_buffer, _verifier); + if (_first && locals != null) { + frame.copy_locals(_prev_frame); } + _first = false; return frame; } if (frame_type < 128) { - if (first) { + if (_first) { offset = frame_type - 64; - if (pre_frame.locals_size() > 0) { - locals = new VerificationType[pre_frame.locals_size()]; + if (_prev_frame.locals_size() > 0) { + locals = new VerificationType[_prev_frame.locals_size()]; } } else { - offset = pre_frame.offset() + frame_type - 63; - locals = pre_frame.locals(); + offset = _prev_frame.offset() + frame_type - 63; + locals = _prev_frame.locals(); } VerificationType[] stack = new VerificationType[2]; int stack_size = 1; @@ -272,11 +365,12 @@ public VerificationFrame next(VerificationFrame pre_frame, boolean first, int ma stack[1] = stack[0].to_category2_2nd(_verifier); stack_size = 2; } - check_verification_type_array_size(stack_size, max_stack); - frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), stack_size, max_locals, max_stack, locals, stack, _verifier); - if (first && locals != null) { - frame.copy_locals(pre_frame); + check_verification_type_array_size(stack_size, _max_stack); + frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), stack_size, _max_locals, _max_stack, locals, stack, _assert_unset_fields_buffer, _verifier); + if (_first && locals != null) { + frame.copy_locals(_prev_frame); } + _first = false; return frame; } int offset_delta = _stream.get_u2(); @@ -284,14 +378,14 @@ public VerificationFrame next(VerificationFrame pre_frame, boolean first, int ma _verifier.classError("reserved frame type"); } if (frame_type == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { - if (first) { + if (_first) { offset = offset_delta; - if (pre_frame.locals_size() > 0) { - locals = new VerificationType[pre_frame.locals_size()]; + if (_prev_frame.locals_size() > 0) { + locals = new VerificationType[_prev_frame.locals_size()]; } } else { - offset = pre_frame.offset() + offset_delta + 1; - locals = pre_frame.locals(); + offset = _prev_frame.offset() + offset_delta + 1; + locals = _prev_frame.locals(); } VerificationType[] stack = new VerificationType[2]; int stack_size = 1; @@ -300,22 +394,23 @@ public VerificationFrame next(VerificationFrame pre_frame, boolean first, int ma stack[1] = stack[0].to_category2_2nd(_verifier); stack_size = 2; } - check_verification_type_array_size(stack_size, max_stack); - frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), stack_size, max_locals, max_stack, locals, stack, _verifier); - if (first && locals != null) { - frame.copy_locals(pre_frame); + check_verification_type_array_size(stack_size, _max_stack); + frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), stack_size, _max_locals, _max_stack, locals, stack, _assert_unset_fields_buffer, _verifier); + if (_first && locals != null) { + frame.copy_locals(_prev_frame); } + _first = false; return frame; } if (frame_type <= SAME_EXTENDED) { - locals = pre_frame.locals(); - int length = pre_frame.locals_size(); + locals = _prev_frame.locals(); + int length = _prev_frame.locals_size(); int chops = SAME_EXTENDED - frame_type; int new_length = length; - int flags = pre_frame.flags(); + int flags = _prev_frame.flags(); if (chops != 0) { new_length = chop(locals, length, chops); - check_verification_type_array_size(new_length, max_locals); + check_verification_type_array_size(new_length, _max_locals); flags = 0; for (int i=0; i 0) { locals = new VerificationType[new_length]; @@ -332,24 +427,25 @@ public VerificationFrame next(VerificationFrame pre_frame, boolean first, int ma locals = null; } } else { - offset = pre_frame.offset() + offset_delta + 1; + offset = _prev_frame.offset() + offset_delta + 1; } - frame = new VerificationFrame(offset, flags, new_length, 0, max_locals, max_stack, locals, null, _verifier); - if (first && locals != null) { - frame.copy_locals(pre_frame); + frame = new VerificationFrame(offset, flags, new_length, 0, _max_locals, _max_stack, locals, null, _assert_unset_fields_buffer, _verifier); + if (_first && locals != null) { + frame.copy_locals(_prev_frame); } + _first = false; return frame; } else if (frame_type < SAME_EXTENDED + 4) { int appends = frame_type - SAME_EXTENDED; - int real_length = pre_frame.locals_size(); + int real_length = _prev_frame.locals_size(); int new_length = real_length + appends*2; locals = new VerificationType[new_length]; - VerificationType[] pre_locals = pre_frame.locals(); + VerificationType[] pre_locals = _prev_frame.locals(); int i; - for (i=0; ihotspot/share/classfile/verificationType.hpp - * @see hotspot/share/classfile/verificationType.cpp - */ +/// From `verificationType.cpp`. class VerificationType { private static final int BitsPerByte = 8; @@ -332,7 +329,7 @@ private boolean _is_assignable_from(VerificationType from, VerifierImpl context) return from.is_integer(); default: if (is_reference() && from.is_reference()) { - return is_reference_assignable_from(from, context); + return is_reference_assignable_from(from, context, null); } else { return false; } @@ -379,18 +376,25 @@ static VerificationType from_tag(int tag, VerifierImpl context) { } } - boolean resolve_and_check_assignability(ClassHierarchyImpl assignResolver, String name, String from_name, boolean from_is_array, boolean from_is_object) { + boolean resolve_and_check_assignability(ClassHierarchyImpl assignResolver, String target_name, String from_name, + boolean from_is_array, boolean from_is_object, boolean[] target_is_interface) { //let's delegate assignability to SPI - var desc = Util.toClassDesc(name); - if (assignResolver.isInterface(desc)) { - return !from_is_array || "java/lang/Cloneable".equals(name) || "java/io/Serializable".equals(name); + var targetClass = Util.toClassDesc(target_name); + boolean isInterface = assignResolver.isInterface(targetClass); + + if (target_is_interface != null) { + target_is_interface[0] = isInterface; + } + + if (isInterface) { + return !from_is_array || "java/lang/Cloneable".equals(target_name) || "java/io/Serializable".equals(target_name); } else if (from_is_object) { - return assignResolver.isAssignableFrom(desc, Util.toClassDesc(from_name)); + return assignResolver.isAssignableFrom(targetClass, Util.toClassDesc(from_name)); } return false; } - boolean is_reference_assignable_from(VerificationType from, VerifierImpl context) { + boolean is_reference_assignable_from(VerificationType from, VerifierImpl context, boolean[] target_is_interface) { ClassHierarchyImpl clsTree = context.class_hierarchy(); if (from.is_null()) { return true; @@ -402,7 +406,7 @@ boolean is_reference_assignable_from(VerificationType from, VerifierImpl context if (VerifierImpl.java_lang_Object.equals(name())) { return true; } - return resolve_and_check_assignability(clsTree, name(), from.name(), from.is_array(), from.is_object()); + return resolve_and_check_assignability(clsTree, name(), from.name(), from.is_array(), from.is_object(), target_is_interface); } else if (is_array() && from.is_array()) { VerificationType comp_this = get_component(context); VerificationType comp_from = from.get_component(context); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java index c76d0789244..70dc4cd7a1f 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java @@ -27,6 +27,7 @@ import java.lang.classfile.Attributes; import java.lang.classfile.ClassModel; +import java.lang.classfile.FieldModel; import java.lang.classfile.MethodModel; import java.lang.classfile.attribute.LocalVariableInfo; import java.lang.classfile.constantpool.ClassEntry; @@ -45,7 +46,7 @@ import jdk.internal.classfile.impl.Util; public final class VerificationWrapper { - private final ClassModel clm; + final ClassModel clm; private final ConstantPoolWrapper cp; public VerificationWrapper(ClassModel clm) { @@ -73,11 +74,11 @@ Iterable methods() { return clm.methods().stream().map(m -> new MethodWrapper(m)).toList(); } - boolean findField(String name, String sig) { + FieldModel findField(String name, String sig) { for (var f : clm.fields()) if (f.fieldName().stringValue().equals(name) && f.fieldType().stringValue().equals(sig)) - return true; - return false; + return f; + return null; } class MethodWrapper { @@ -161,7 +162,7 @@ byte[] stackMapTableRawData() { static class ConstantPoolWrapper { - private final ConstantPool cp; + final ConstantPool cp; ConstantPoolWrapper(ConstantPool cp) { this.cp = cp; @@ -200,5 +201,9 @@ String refSignatureAt(int index) { int refClassIndexAt(int index) { return cp.entryByIndex(index, MemberRefEntry.class).owner().index(); } + + boolean is_within_bounds(int i) { + return i >= 1 && i <= cp.size(); + } } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java index 4c2104c5a78..bd3f062f569 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,16 +24,26 @@ */ package jdk.internal.classfile.impl.verifier; +import java.lang.classfile.ClassFile; import java.lang.classfile.ClassHierarchyResolver; import java.lang.classfile.ClassModel; import jdk.internal.classfile.components.ClassPrinter; + +import java.lang.classfile.FieldModel; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.constant.ConstantDescs; +import java.lang.reflect.AccessFlag; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import jdk.internal.classfile.impl.ClassHierarchyImpl; import jdk.internal.classfile.impl.RawBytecodeHelper; +import jdk.internal.classfile.impl.TemporaryConstantPool; import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType; import jdk.internal.classfile.impl.verifier.VerificationWrapper.ConstantPoolWrapper; @@ -42,16 +52,12 @@ import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.T_BOOLEAN; import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.T_LONG; -/** - * 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 java.base/share/native/include/classfile_constants.h.template - * @see hotspot/share/classfile/verifier.hpp - * @see hotspot/share/classfile/verifier.cpp - */ +/// 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} +/// +/// From `verifier.cpp`. public final class VerifierImpl { static final int JVM_CONSTANT_Utf8 = 1, @@ -142,11 +148,8 @@ public static List verify(ClassModel classModel, ClassHierarchyReso } public static boolean is_eligible_for_verification(VerificationWrapper klass) { - String name = klass.thisClassName(); - return !java_lang_Object.equals(name) && - !java_lang_Class.equals(name) && - !java_lang_String.equals(name) && - !java_lang_Throwable.equals(name); + // 8330606 Not applicable here + return true; } static List inference_verify(VerificationWrapper klass) { @@ -249,6 +252,11 @@ private VerificationType object_type() { return VerificationType.reference_type(java_lang_Object); } + static boolean supports_strict_fields(VerificationWrapper klass) { + int ver = klass.majorVersion(); + return ver >= 67 && klass.clm.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION; + } + List verify_class() { log_info("Verifying class %s with new format", _klass.thisClassName()); var errors = new ArrayList(); @@ -310,7 +318,19 @@ void verify_method(VerificationWrapper.MethodWrapper m) { 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); + + // Collect the initial strict instance fields + Set strict_fields = new HashSet<>(); + if (m.name().equals(ConstantDescs.INIT_NAME)) { + for (var fs : current_class().clm.fields()) { + if (fs.flags().has(AccessFlag.STRICT_INIT) && !fs.flags().has(AccessFlag.STATIC)) { + var new_field = TemporaryConstantPool.INSTANCE.nameAndTypeEntry(fs.fieldName(), fs.fieldType()); + strict_fields.add(new_field); + } + } + } + + VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, strict_fields, this); VerificationType return_type = current_frame.set_locals_from_arg(m, current_type()); int stackmap_index = 0; int code_length = m.codeLength(); @@ -323,7 +343,9 @@ void verify_method(VerificationWrapper.MethodWrapper m) { verify_exception_handler_table(code_length, code_data, ex_minmax); verify_local_variable_table(code_length, code_data); - VerificationTable stackmap_table = new VerificationTable(stackmap_data, current_frame, max_locals, max_stack, code_data, code_length, cp, this); + var reader = new VerificationTable.StackMapReader(stackmap_data, code_data, code_length, current_frame, + (char) max_locals, (char) max_stack, strict_fields, cp, this); + VerificationTable stackmap_table = new VerificationTable(reader, cp, this); var bcs = code.start(); boolean no_control_flow = false; @@ -1270,6 +1292,7 @@ void verify_exception_handler_table(int code_length, byte[] code_data, int[] min if (catch_type_index != 0) { VerificationType catch_type = cp_index_to_type(catch_type_index, cp); VerificationType throwable = VerificationType.reference_type(java_lang_Throwable); + // 8267118 Not applicable here boolean is_subclass = throwable.is_assignable_from(catch_type, this); if (!is_subclass) { verifyError(String.format("Catch type is not a subclass of Throwable in exception handler %d", handler_pc)); @@ -1353,7 +1376,7 @@ void verify_cp_index(int bci, ConstantPoolWrapper cp, int index) { void verify_cp_type(int bci, int index, ConstantPoolWrapper cp, int types) { verify_cp_index(bci, cp, index); int tag = cp.tagAt(index); - if ((types & (1 << tag))== 0) { + if (tag > JVM_CONSTANT_ExternalMax || (types & (1 << tag))== 0) { verifyError(String.format("Illegal type at constant pool entry %d", index)); } } @@ -1432,10 +1455,11 @@ void verify_switch(RawBytecodeHelper bcs, int code_length, byte[] code_data, Ver if (low > high) { verifyError("low must be less than or equal to high in tableswitch"); } - keys = high - low + 1; - if (keys < 0) { + long keys64 = ((long) high - low) + 1; + if (keys64 > 65535) { // Max code length verifyError("too many keys in tableswitch"); } + keys = (int) keys64; delta = 1; } else { // Make sure that the lookupswitch items are sorted @@ -1492,6 +1516,7 @@ void verify_field_instructions(RawBytecodeHelper bcs, VerificationFrame current_ case GETFIELD -> { stack_object_type = current_frame.pop_stack( target_class_type); + // 8270398 Not applicable here for (int i = 0; i < n; i++) { current_frame.push_stack(field_type[i]); } @@ -1501,10 +1526,24 @@ void verify_field_instructions(RawBytecodeHelper bcs, VerificationFrame current_ current_frame.pop_stack(field_type[i]); } stack_object_type = current_frame.pop_stack(); - if (stack_object_type.is_uninitialized_this(this) && - target_class_type.equals(current_type()) && - _klass.findField(field_name, field_sig)) { - stack_object_type = current_type(); + FieldModel fd = _klass.findField(field_name, field_sig); + boolean is_local_field = fd != null && + target_class_type.equals(current_type()); + if (stack_object_type.is_uninitialized_this(this)) { + if (is_local_field) { + // Set the type to the current type so the is_assignable check passes. + stack_object_type = current_type(); + + if (fd.flags().has(AccessFlag.STRICT_INIT)) { + current_frame.satisfy_unset_field(fd.fieldName(), fd.fieldType()); + } + } + } else if (supports_strict_fields(_klass)) { + // `strict` fields are not writable, but only local fields produce verification errors + if (is_local_field && fd.flags().has(AccessFlag.STRICT_INIT) && fd.flags().has(AccessFlag.FINAL)) { + verifyError("Bad code %d %s".formatted(bci, + "Illegal use of putfield on a strict field: %s:%s".formatted(fd.fieldName(), fd.fieldType()))); + } } is_assignable = target_class_type.is_assignable_from(stack_object_type, this); if (!is_assignable) { @@ -1532,6 +1571,11 @@ boolean verify_invoke_init(RawBytecodeHelper bcs, int ref_class_index, Verificat if (!current_class().thisClassName().equals(ref_class_type.name()) && !superk_name.equals(ref_class_type.name())) { verifyError("Bad method call"); + } else if (ref_class_type.name().equals(superk_name)) { + // Strict final fields must be satisfied by this point + if (!current_frame.verify_unset_fields_satisfied()) { + current_frame.unsatisfied_strict_fields_error(current_class(), bci); + } } if (in_try_block) { for(var exhandler : _method.exceptionTable()) { @@ -1643,12 +1687,29 @@ boolean verify_invoke_instructions(RawBytecodeHelper bcs, int code_length, Verif && !is_same_or_direct_interface(current_class(), current_type(), ref_class_type) && !ref_class_type.equals(VerificationType.reference_type( current_class().superclassName()))) { - boolean have_imr_indirect = cp.tagAt(index) == JVM_CONSTANT_InterfaceMethodref; - boolean subtype = ref_class_type.is_assignable_from(current_type(), this); - if (!subtype) { + + // We know it is not current class, direct superinterface or immediate superclass. That means it + // could be: + // - a totally unrelated class or interface + // - an indirect superinterface + // - an indirect superclass (including Object) + // We use the assignability test to see if it is a superclass, or else an interface, and keep track + // of the latter. Note that subtype can be true if we are dealing with an interface that is not actually + // implemented as assignability treats all interfaces as Object. + + boolean[] is_interface = {false}; // This can only be set true if the assignability check will return true + // and we loaded the class. For any other "true" returns (e.g. same class + // or Object) we either can't get here (same class already excluded above) + // or we know it is not an interface (i.e. Object). + boolean subtype = ref_class_type.is_reference_assignable_from(current_type(), this, is_interface); + if (!subtype) { // Totally unrelated class verifyError("Bad invokespecial instruction: current class isn't assignable to reference class."); - } else if (have_imr_indirect) { - verifyError("Bad invokespecial instruction: interface method reference is in an indirect superinterface."); + } else { + // Indirect superclass (including Object), indirect interface, or unrelated interface. + // Any interface use is an error. + if (is_interface[0]) { + verifyError("Bad invokespecial instruction: interface method to invoke is not in a direct superinterface."); + } } } @@ -1817,7 +1878,7 @@ void verify_iinc(int index, VerificationFrame current_frame) { void verify_return_value(VerificationType return_type, VerificationType type, int bci, VerificationFrame current_frame) { if (return_type.is_bogus()) { - verifyError("Method expects a return value"); + verifyError("Method does not expect a return value"); } boolean match = return_type.is_assignable_from(type, this); if (!match) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/verifier.md b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/verifier.md new file mode 100644 index 00000000000..1899f7b86a3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/verifier.md @@ -0,0 +1,20 @@ +The Class-File Verifier +=== +The Class-File API provides a verifier, a debug utility that reports as many +verification errors as possible in a class file. + +Currently, the verifier closely follows the C++ code that implements the hotspot +verifier. However, there are a few differences: +- The Class-File API verifier tries to collect as many errors as possible, while + the hotspot verifier fails fast. +- The hotspot verifier has access to other classes and can check access control; + the Class-File API verifier cannot. + +Thus, this verifier cannot serve as a complete implementation of the verifier +specified in the JVMS because it has no access to other class files or loaded +classes. However, it is still in our interest to make this verifier up to date: +for example, this should not fail upon encountering new language features, and +should at best include all new checks hotspot has as long as the required +information are accessible to the Class-File API. + +Last sync: jdk-26+5, July 3rd 2025 diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java index 60e2a29225d..f3d5726d234 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java @@ -148,6 +148,7 @@ static Class buildClass(String className, System.out.println(vererrs); var cm = ClassFile.of().parse(classBytes); System.out.println(cm.toDebugString()); + throw new AssertionError(); } ++COUNT; try { diff --git a/test/jdk/jdk/classfile/StrictStackMapsTest.java b/test/jdk/jdk/classfile/StrictStackMapsTest.java index fac7517cbc1..3f3dbb7e05c 100644 --- a/test/jdk/jdk/classfile/StrictStackMapsTest.java +++ b/test/jdk/jdk/classfile/StrictStackMapsTest.java @@ -360,5 +360,7 @@ private static void runtimeVerify(String className, byte[] classBytes) { var clazz = assertDoesNotThrow(() -> ByteCodeLoader.load(className, classBytes)); var lookup = assertDoesNotThrow(() -> MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())); assertDoesNotThrow(() -> lookup.ensureInitialized(clazz)); // forces verification + var errors = ClassFile.of().verify(classBytes); + assertEquals(List.of(), errors, "Errors detected"); } } diff --git a/test/jdk/jdk/classfile/VerifierSelfTest.java b/test/jdk/jdk/classfile/VerifierSelfTest.java index 80ee89d5fda..d009f2351fe 100644 --- a/test/jdk/jdk/classfile/VerifierSelfTest.java +++ b/test/jdk/jdk/classfile/VerifierSelfTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,15 +24,19 @@ /* * @test * @summary Testing ClassFile Verifier. - * @bug 8333812 + * @bug 8333812 8361526 * @run junit VerifierSelfTest + * @run junit/othervm --enable-preview VerifierSelfTest */ import java.io.IOException; import java.lang.classfile.constantpool.PoolEntry; import java.lang.constant.ClassDesc; +import static java.lang.classfile.ClassFile.ACC_STATIC; +import static java.lang.classfile.ClassFile.JAVA_8_VERSION; import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; import java.lang.invoke.MethodHandleInfo; import java.net.URI; import java.nio.file.FileSystem; @@ -42,6 +46,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -207,7 +212,6 @@ void testParserVerification() { 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()))) @@ -327,12 +331,10 @@ Duplicate method name <> with signature ()V in class ParserVerificationTestClass 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 @@ -413,4 +415,27 @@ private static List> patch(Attribute... attrs) { } return lst; } + + @Test // JDK-8350029 + void testInvokeSpecialInterfacePatch() { + var runClass = ClassDesc.of("Run"); + var testClass = ClassDesc.of("Test"); + var runnableClass = Runnable.class.describeConstable().orElseThrow(); + var chr = ClassHierarchyResolver.of(List.of(), Map.of(runClass, CD_Object)) + .orElse(ClassHierarchyResolver.defaultResolver()).cached(); + var context = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(chr)); + + for (var isInterface : new boolean[] {true, false}) { + var bytes = context.build(testClass, clb -> clb + .withVersion(JAVA_8_VERSION, 0) + .withSuperclass(runClass) + .withMethodBody("test", MethodTypeDesc.of(CD_void, testClass), ACC_STATIC, cob -> cob + .aload(0) + .invokespecial(runnableClass, "run", MTD_void, isInterface) + .return_())); + var errors = context.verify(bytes); + assertNotEquals(List.of(), errors, "invokespecial, isInterface = " + isInterface); + assertTrue(errors.getFirst().getMessage().contains("interface method to invoke is not in a direct superinterface"), errors.getFirst().getMessage()); + } + } }