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());
+ }
+ }
}