From 40bec05cb875dea7a8c24f1111c5625966f05e1c Mon Sep 17 00:00:00 2001 From: liach Date: Sat, 5 Jul 2025 15:19:56 -0500 Subject: [PATCH 1/5] Sync classfile verifier wip --- .../impl/verifier/ParserVerifier.java | 13 +- .../impl/verifier/VerificationBytecodes.java | 7 +- .../impl/verifier/VerificationFrame.java | 7 +- .../impl/verifier/VerificationSignature.java | 3 +- .../impl/verifier/VerificationTable.java | 197 +++++++++++------- .../impl/verifier/VerificationType.java | 30 +-- .../classfile/impl/verifier/VerifierImpl.java | 67 +++--- .../classfile/impl/verifier/verifier.md | 34 +++ 8 files changed, 225 insertions(+), 133 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/classfile/impl/verifier/verifier.md 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..dea4eea671a4a 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,26 @@ 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"); - } - pre_frame = frame; + VerificationFrame frame = reader.next(); + if (frame != null) { + _frame_array.add(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 +111,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 +145,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,15 +165,44 @@ 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); if (stackmapData != null) { @@ -185,9 +212,27 @@ public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, Verif } _code_data = code_data; _code_length = code_len; + _parsed_frame_count = 0; + _prev_frame = init_frame; + _max_locals = max_locals; + _max_stack = max_stack; + // additional _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) { if (locals == null) return -1; int pos = length - 1; @@ -232,36 +277,36 @@ 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); } 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,10 +315,10 @@ 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); } return frame; } @@ -282,14 +327,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 +343,22 @@ 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); } 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 +375,24 @@ 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); } 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..50914cae79251 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 reference is in an indirect 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..9c106680f312c --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/verifier.md @@ -0,0 +1,34 @@ +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: f2d2eef988c57cc9f6194a8fd5b2b422035ee68f Jul 4 2025 + +This patch: +8355029 +8340110 +8330606 +8314295 +8270398 +8267118 +8262368 +mar 3 2021 + +# +8272096 +8349923 \ No newline at end of file From 76a7423ae9a95ff195ed5cc2094e3dbf28ddc188 Mon Sep 17 00:00:00 2001 From: liach Date: Mon, 7 Jul 2025 12:57:59 -0500 Subject: [PATCH 2/5] Fix bad sync --- .../impl/verifier/VerificationTable.java | 29 ++++++++++++------- .../classfile/impl/verifier/verifier.md | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) 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 dea4eea671a4a..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 @@ -83,9 +83,11 @@ boolean at_end() { int _frame_count = reader.get_frame_count(); _frame_array = new ArrayList<>(_frame_count); if (_frame_count > 0) { - VerificationFrame frame = reader.next(); - if (frame != null) { - _frame_array.add(frame); + while (!reader.at_end()) { + VerificationFrame frame = reader.next(); + if (frame != null) { + _frame_array.add(frame); + } } } reader.check_end(); @@ -205,19 +207,20 @@ public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl context) { this._verifier = context; _stream = new StackMapStream(stackmapData, _verifier); - if (stackmapData != null) { - _frame_count = _stream.get_u2(); - } else { - _frame_count = 0; - } _code_data = code_data; _code_length = code_len; _parsed_frame_count = 0; _prev_frame = init_frame; _max_locals = max_locals; _max_stack = max_stack; - // additional - _cp = cp; + _first = true; + if (stackmapData != null) { + _cp = cp; + _frame_count = _stream.get_u2(); + } else { + _cp = null; + _frame_count = 0; + } } void check_offset(VerificationFrame frame) { @@ -296,6 +299,7 @@ VerificationFrame next_helper() { if (_first && locals != null) { frame.copy_locals(_prev_frame); } + _first = false; return frame; } if (frame_type < 128) { @@ -320,6 +324,7 @@ VerificationFrame next_helper() { if (_first && locals != null) { frame.copy_locals(_prev_frame); } + _first = false; return frame; } int offset_delta = _stream.get_u2(); @@ -348,6 +353,7 @@ VerificationFrame next_helper() { if (_first && locals != null) { frame.copy_locals(_prev_frame); } + _first = false; return frame; } if (frame_type <= SAME_EXTENDED) { @@ -381,6 +387,7 @@ VerificationFrame next_helper() { 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; @@ -408,6 +415,7 @@ VerificationFrame next_helper() { offset = _prev_frame.offset() + offset_delta + 1; } frame = new VerificationFrame(offset, flags[0], real_length, 0, _max_locals, _max_stack, locals, null, _verifier); + _first = false; return frame; } if (frame_type == FULL) { @@ -449,6 +457,7 @@ VerificationFrame next_helper() { offset = _prev_frame.offset() + offset_delta + 1; } frame = new VerificationFrame(offset, flags[0], real_locals_size, real_stack_size, _max_locals, _max_stack, locals, stack, _verifier); + _first = false; return frame; } _verifier.classError("reserved frame type"); 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 index 9c106680f312c..0b55121ea2acc 100644 --- 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 @@ -20,7 +20,7 @@ information are accessible to the Class-File API. Last sync: f2d2eef988c57cc9f6194a8fd5b2b422035ee68f Jul 4 2025 This patch: -8355029 +8350029 8340110 8330606 8314295 From d3be2040811b4b37bf8b7863d848b26d66638967 Mon Sep 17 00:00:00 2001 From: liach Date: Mon, 7 Jul 2025 18:17:07 -0500 Subject: [PATCH 3/5] More sync --- .../classfile/impl/verifier/VerifierImpl.java | 2 +- .../classfile/impl/verifier/verifier.md | 16 +--------- test/jdk/jdk/classfile/VerifierSelfTest.java | 29 ++++++++++++++++++- 3 files changed, 30 insertions(+), 17 deletions(-) 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 50914cae79251..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 @@ -1662,7 +1662,7 @@ boolean verify_invoke_instructions(RawBytecodeHelper bcs, int code_length, Verif // 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 reference is in an indirect superinterface."); + verifyError("Bad invokespecial instruction: interface method to invoke is not in a direct superinterface."); } } 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 index 0b55121ea2acc..42e3ee0e3729e 100644 --- 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 @@ -17,18 +17,4 @@ 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: f2d2eef988c57cc9f6194a8fd5b2b422035ee68f Jul 4 2025 - -This patch: -8350029 -8340110 -8330606 -8314295 -8270398 -8267118 -8262368 -mar 3 2021 - -# -8272096 -8349923 \ No newline at end of file +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..41957815761e4 100644 --- a/test/jdk/jdk/classfile/VerifierSelfTest.java +++ b/test/jdk/jdk/classfile/VerifierSelfTest.java @@ -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()); + } + } } From 52d4271c4eba5f99aae7985b947854eee1fad355 Mon Sep 17 00:00:00 2001 From: liach Date: Mon, 7 Jul 2025 18:21:23 -0500 Subject: [PATCH 4/5] Year --- test/jdk/jdk/classfile/VerifierSelfTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/jdk/classfile/VerifierSelfTest.java b/test/jdk/jdk/classfile/VerifierSelfTest.java index 41957815761e4..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 From 1ef867628f27204c4a67f77f9a7f549eadd53e7c Mon Sep 17 00:00:00 2001 From: liach Date: Mon, 7 Jul 2025 18:25:12 -0500 Subject: [PATCH 5/5] File was created on windows --- .../classfile/impl/verifier/verifier.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) 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 index 42e3ee0e3729e..1899f7b86a37f 100644 --- 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 @@ -1,20 +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 +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