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 f860fb4a1bd06..5c93d6da4c174 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 @@ -44,12 +44,11 @@ import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME; import static java.lang.constant.ConstantDescs.INIT_NAME; -/** - * 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() { 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 a7d97c7ea3741..8bd964650d3fd 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 13aac2c92c68d..07a3de21cc1cc 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 @@ -26,10 +26,7 @@ import java.util.Arrays; -/** - * @see hotspot/share/classfile/stackMapFrame.hpp - * @see hotspot/share/classfile/stackMapFrame.cpp - */ +/// From `stackMapFrame.cpp`. class VerificationFrame { public static final int FLAG_THIS_UNINIT = 0x01; 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 9fa4a377852ff..4c0b21b6a3719 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 0c4131dd1baee..85342e7106f78 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 @@ -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,19 +24,19 @@ */ package jdk.internal.classfile.impl.verifier; +import java.util.ArrayList; +import java.util.List; + 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 +44,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 +76,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 +113,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); @@ -151,6 +147,10 @@ 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; + boolean _first; void check_verification_type_array_size(int size, int max_size) { if (size < 0 || size > max_size) { @@ -167,25 +167,73 @@ 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, + 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; + _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) { @@ -232,36 +280,37 @@ 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 < 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, _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; @@ -270,11 +319,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, _verifier); + if (_first && locals != null) { + frame.copy_locals(_prev_frame); } + _first = false; return frame; } int offset_delta = _stream.get_u2(); @@ -282,14 +332,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; @@ -298,22 +348,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, _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]; @@ -330,24 +381,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, _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/VerifierImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java index 4c2104c5a786b..bb862578b3492 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 @@ -42,16 +42,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 +138,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) { @@ -323,7 +316,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, cp, this); + VerificationTable stackmap_table = new VerificationTable(reader, cp, this); var bcs = code.start(); boolean no_control_flow = false; @@ -1270,6 +1265,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 +1349,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 +1428,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 +1489,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]); } @@ -1643,12 +1641,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 +1832,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 0000000000000..1899f7b86a37f --- /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/jdk/jdk/classfile/VerifierSelfTest.java b/test/jdk/jdk/classfile/VerifierSelfTest.java index 80ee89d5fda06..7d1dae6f51944 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,18 @@ /* * @test * @summary Testing ClassFile Verifier. - * @bug 8333812 + * @bug 8333812 8361526 * @run junit 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 +45,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; @@ -413,4 +417,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()); + } + } }