Skip to content

8362181: [lworld] Include verification for strict fields in ClassFile API #1506

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: lworld
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public static AttributeMapper<LineNumberTableAttribute> lineNumberTable() {

/**
* {@return Attribute mapper for the {@code LoadableDescriptors} attribute}
* @since 23
* @since Valhalla
*/
public static AttributeMapper<LoadableDescriptorsAttribute> loadableDescriptors() {
return LoadableDescriptorsMapper.INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
*/
package java.lang.classfile.attribute;

import java.lang.classfile.AttributeMapper;
import java.lang.classfile.Attributes;
import java.lang.classfile.ClassFile;
import java.lang.classfile.constantpool.Utf8Entry;
import java.lang.constant.ClassDesc;
import java.util.Arrays;
Expand All @@ -32,18 +35,31 @@
import java.lang.classfile.Attribute;
import java.lang.classfile.ClassElement;
import java.lang.classfile.constantpool.ClassEntry;

import jdk.internal.classfile.impl.AbstractPoolEntry;
import jdk.internal.classfile.impl.BoundAttribute;
import jdk.internal.classfile.impl.UnboundAttribute;
import jdk.internal.classfile.impl.Util;
import jdk.internal.javac.PreviewFeature;

/**
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
* Models the {@link Attributes#loadableDescriptors() LoadableDescriptors}
* attribute (JVMS {@jvms 4.7.32}), which suggests the JVM may load mentioned
* types before the {@code class} file carrying this attribute is loaded.
* <p>
* This attribute only appears on classes, and does not permit {@linkplain
* AttributeMapper#allowMultiple multiple instances} in a class. It has a
* data dependency on the {@linkplain AttributeMapper.AttributeStability#CP_REFS
* constant pool}.
* <p>
* The attribute was introduced in the Java SE Platform version XX, major
* version {@value ClassFile#JAVA_25_VERSION}. (FIXME)
*
* @see Attributes#loadableDescriptors()
* @jvms 4.7.32 The {@code LoadableDescriptors} Attribute
* @since Valhalla
*/
@PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS)
public sealed interface LoadableDescriptorsAttribute
extends Attribute<LoadableDescriptorsAttribute>, ClassElement
permits BoundAttribute.BoundLoadableDescriptorsAttribute, UnboundAttribute.UnboundLoadableDescriptorsAttribute {
Expand All @@ -53,6 +69,13 @@ public sealed interface LoadableDescriptorsAttribute
*/
List<Utf8Entry> loadableDescriptors();

/**
* {@return the list of loadable descriptors, as nominal descriptors}
*/
default List<ClassDesc> loadableDescriptorSymbols() {
return Util.mappedList(loadableDescriptors(), Util::fieldTypeSymbol);
}

/**
* {@return a {@code LoadableDescriptors} attribute}
* @param loadableDescriptors the loadable descriptors
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -41,15 +41,13 @@
import jdk.internal.classfile.impl.BoundAttribute;
import jdk.internal.classfile.impl.Util;

import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME;
import static java.lang.constant.ConstantDescs.INIT_NAME;
import static java.lang.constant.ConstantDescs.*;

/**
* ParserVerifier performs selected checks of the class file format according to
* {@jvms 4.8 Format Checking}
*
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/classfile/classFileParser.cpp">hotspot/share/classfile/classFileParser.cpp</a>
*/
/// 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<VerifyError> verify() {
Expand Down Expand Up @@ -277,8 +275,14 @@ private void verifyAttribute(AttributedElement ae, Attribute<?> a, List<VerifyEr
}
case LineNumberTableAttribute lta ->
2 + 4 * lta.lineNumbers().size();
case LoadableDescriptorsAttribute lda ->
2 + 2 * lda.loadableDescriptors().size();
case LoadableDescriptorsAttribute lda -> {
for (var desc : lda.loadableDescriptorSymbols()) {
if (desc.equals(CD_void)) {
errors.add(new VerifyError("illegal signature %s".formatted(desc)));
}
}
yield 2 + 2 * lda.loadableDescriptors().size();
}
case LocalVariableTableAttribute lvta ->
2 + 10 * lvta.localVariables().size();
case LocalVariableTypeTableAttribute lvta ->
Expand Down Expand Up @@ -349,8 +353,8 @@ private void verifyAttribute(AttributedElement ae, Attribute<?> a, List<VerifyEr
sida.sourceId();
yield 2;
}
case StackMapTableAttribute smta ->
2 + subSize(smta.entries(), frame -> stackMapFrameSize(frame));
case StackMapTableAttribute _ ->
-1; // Not sufficient info for assert unset size
case SyntheticAttribute _ ->
0;
case UnknownAttribute _ ->
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -29,10 +29,7 @@
import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.*;

/**
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/interpreter/bytecodes.hpp">hotspot/share/interpreter/bytecodes.hpp</a>
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/interpreter/bytecodes.cpp">hotspot/share/interpreter/bytecodes.cpp</a>
*/
/// From `bytecodes.cpp`.
final class VerificationBytecodes {

static final int _breakpoint = 202,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,12 +24,14 @@
*/
package jdk.internal.classfile.impl.verifier;

import java.lang.classfile.constantpool.NameAndTypeEntry;
import java.lang.classfile.constantpool.Utf8Entry;
import java.util.Arrays;
import java.util.Set;

/**
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/classfile/stackMapFrame.hpp">hotspot/share/classfile/stackMapFrame.hpp</a>
* @see <a href="https://raw.githubusercontent.com/openjdk/jdk/master/src/hotspot/share/classfile/stackMapFrame.cpp">hotspot/share/classfile/stackMapFrame.cpp</a>
*/
import jdk.internal.classfile.impl.TemporaryConstantPool;

/// From `stackMapFrame.cpp`.
class VerificationFrame {

public static final int FLAG_THIS_UNINIT = 0x01;
Expand All @@ -40,9 +42,11 @@ class VerificationFrame {
private final int _max_locals, _max_stack;
private int _flags;
private final VerificationType[] _locals, _stack;
private Set<NameAndTypeEntry> _assert_unset_fields;
private final VerifierImpl _verifier;

public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, VerificationType[] locals, VerificationType[] stack, VerifierImpl v) {
public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack,
VerificationType[] locals, VerificationType[] stack, Set<NameAndTypeEntry> assert_unset_fields, VerifierImpl v) {
this._offset = offset;
this._locals_size = locals_size;
this._stack_size = stack_size;
Expand All @@ -52,6 +56,7 @@ public VerificationFrame(int offset, int flags, int locals_size, int stack_size,
this._flags = flags;
this._locals = locals;
this._stack = stack;
this._assert_unset_fields = assert_unset_fields;
this._verifier = v;
}

Expand Down Expand Up @@ -112,6 +117,50 @@ boolean flag_this_uninit() {
return (_flags & FLAG_THIS_UNINIT) == FLAG_THIS_UNINIT;
}

Set<NameAndTypeEntry> assert_unset_fields() {
return _assert_unset_fields;
}

void set_assert_unset_fields(Set<NameAndTypeEntry> table) {
_assert_unset_fields = table;
}

// Called when verifying putfields to mark strict instance fields as satisfied
boolean satisfy_unset_field(Utf8Entry name, Utf8Entry signature) {
var nat = TemporaryConstantPool.INSTANCE.nameAndTypeEntry(name, signature);
return _assert_unset_fields.remove(nat);
}

// Verify that all strict fields have been initialized
// Strict fields must be initialized before the super constructor is called
boolean verify_unset_fields_satisfied() {
return _assert_unset_fields.isEmpty();
}

// Merge incoming unset strict fields from StackMapTable with
// initial strict instance fields
Set<NameAndTypeEntry> merge_unset_fields(Set<NameAndTypeEntry> new_fields) {
// ClassFile API: We track all strict fields in another structure, noop here
return new_fields;
}

boolean verify_unset_fields_compatibility(Set<NameAndTypeEntry> target_table) {
for (var e : _assert_unset_fields) {
if (!target_table.contains(e))
return false;
}
return true;
}

void unsatisfied_strict_fields_error(VerificationWrapper klass, int bci) {
_verifier.verifyError("All strict final fields must be initialized before super(): %d field(s), %s"
.formatted(_assert_unset_fields.size(), _assert_unset_fields));
}

void print_strict_fields(Set<NameAndTypeEntry> table) {
// ignore, we don't do stdout/err
}

void reset() {
for (int i = 0; i < _max_locals; i++) {
_locals[i] = VerificationType.bogus_type;
Expand Down Expand Up @@ -184,7 +233,7 @@ void pop_stack_2(VerificationType type1, VerificationType type2) {
pop_stack_ex(type2);
}

VerificationFrame(int max_locals, int max_stack, VerifierImpl verifier) {
VerificationFrame(int max_locals, int max_stack, Set<NameAndTypeEntry> initial_strict_fields, VerifierImpl verifier) {
_offset = 0;
_locals_size = 0;
_stack_size = 0;
Expand All @@ -201,12 +250,13 @@ void pop_stack_2(VerificationType type1, VerificationType type2) {
for (int i = 0; i < max_stack; i++) {
_stack[i] = VerificationType.bogus_type;
}
_assert_unset_fields = initial_strict_fields;
}

VerificationFrame frame_in_exception_handler(int flags) {
return new VerificationFrame(_offset, flags, _locals_size, 0,
_max_locals, _max_stack, _locals, new VerificationType[1],
_verifier);
_assert_unset_fields, _verifier);
}

void initialize_object(VerificationType old_object, VerificationType new_object) {
Expand Down Expand Up @@ -302,6 +352,16 @@ boolean is_assignable_to(VerificationFrame target) {
_verifier.verifyError("Bad type", this, target);
}

// Check that assert unset fields are compatible
boolean compatible = verify_unset_fields_compatibility(target.assert_unset_fields());
if (!compatible) {
print_strict_fields(assert_unset_fields());
print_strict_fields(target.assert_unset_fields());
_verifier.verifyError("Strict fields mismatch from %s to %s".formatted(
assert_unset_fields(), target.assert_unset_fields()), this, target);
return false;
}

if ((_flags | target.flags()) == target.flags()) {
return true;
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,6 +24,7 @@
*/
package jdk.internal.classfile.impl.verifier;

/// Relevant parts from `signatures.cpp`, such as `SignatureStream`.
final class VerificationSignature {

enum BasicType {
Expand Down
Loading