mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8367585: Prevent creation of unrepresentable Utf8Entry
Reviewed-by: asotona
This commit is contained in:
parent
e439909b7d
commit
3b1eb76231
@ -32,9 +32,8 @@
|
||||
* including {@link Attribute}, {@link AttributedElement}, {@link AttributeMapper}, and {@link CustomAttribute}, which
|
||||
* do not reside in this package.
|
||||
* <p>
|
||||
* Unless otherwise specified, passing {@code null} or an array or collection containing a {@code null} element as an
|
||||
* argument to a constructor or method of any Class-File API class or interface will cause a {@link NullPointerException}
|
||||
* to be thrown.
|
||||
* APIs in this package perform {@linkplain java.lang.classfile##checks null and unrepresentable argument checks},
|
||||
* unless otherwise noted.
|
||||
*
|
||||
* <h2 id="reading">Reading Attributes</h2>
|
||||
* The general way to obtain attributes is through {@link AttributedElement}. In addition to that, many attributes
|
||||
|
||||
@ -30,9 +30,8 @@
|
||||
* {@code class} file format. Constant pool entries are low-level models to faithfully represent the exact structure
|
||||
* of a {@code class} file.
|
||||
* <p>
|
||||
* Unless otherwise specified, passing {@code null} or an array or collection containing a {@code null} element as an
|
||||
* argument to a constructor or method of any Class-File API class or interface will cause a {@link NullPointerException}
|
||||
* to be thrown.
|
||||
* APIs in this package perform {@linkplain java.lang.classfile##checks null and unrepresentable argument checks},
|
||||
* unless otherwise noted.
|
||||
*
|
||||
* <h2 id="reading">Reading the constant pool entries</h2>
|
||||
* When read from {@code class} files, the pool entries are lazily inflated; the contents of these entries, besides the
|
||||
|
||||
@ -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
|
||||
@ -29,9 +29,8 @@
|
||||
* The {@code java.lang.classfile.instruction} package contains interfaces describing code instructions.
|
||||
* Implementations of these interfaces are immutable.
|
||||
* <p>
|
||||
* Unless otherwise specified, passing {@code null} or an array or collection containing a {@code null} element as an
|
||||
* argument to a constructor or method of any Class-File API class or interface will cause a {@link NullPointerException}
|
||||
* to be thrown.
|
||||
* APIs in this package perform {@linkplain java.lang.classfile##checks null and unrepresentable argument checks},
|
||||
* unless otherwise noted.
|
||||
*
|
||||
* <h2 id="reading">Reading of instructions</h2>
|
||||
* Instructions and pseudo-instructions are usually accessed from a {@link CodeModel}, such as {@link CodeModel#forEach
|
||||
|
||||
@ -250,12 +250,6 @@
|
||||
* the convenience method {@code CodeBuilder.invoke}, which in turn behaves
|
||||
* as if it calls method {@code CodeBuilder.with}. This composing of method calls on the
|
||||
* builder enables the composing of transforms (as described later).
|
||||
* <p>
|
||||
* Unless otherwise noted, passing a {@code null} argument to a constructor
|
||||
* or method of any Class-File API class or interface will cause a {@link
|
||||
* NullPointerException} to be thrown. Additionally,
|
||||
* invoking a method with an array or collection containing a {@code null} element
|
||||
* will cause a {@code NullPointerException}, unless otherwise specified.
|
||||
*
|
||||
* <h3>Symbolic information</h3>
|
||||
* To describe symbolic information for classes and types, the API uses the
|
||||
@ -272,14 +266,22 @@
|
||||
* symbolic information, one accepting nominal descriptors, and the other
|
||||
* accepting constant pool entries.
|
||||
*
|
||||
* <h3>Consistency checks, syntax checks and verification</h3>
|
||||
* The Class-File API performs checks to ensure arguments are representable in
|
||||
* the {@code class} file format. A value that is lost when it is built to a
|
||||
* {@code class} file and re-parsed to a model is rejected with an {@link
|
||||
* IllegalArgumentException}. For example, a negative value or a value over
|
||||
* {@code 65535} is lost when built to a {@link ##u2 u2} item, with
|
||||
* the range {@code [0, 65535]}. In particular, any variable-sized table
|
||||
* exceeding its maximum representable size is rejected.
|
||||
* <h3 id="checks">Consistency checks, syntax checks and verification</h3>
|
||||
* The Class-File API performs checks to ensure arguments to construct {@code
|
||||
* class} file structures are representable in the {@code class} file format.
|
||||
* An argument value that cannot be representable by its data type is rejected
|
||||
* with an {@link IllegalArgumentException}. For example, an {@code int} value
|
||||
* cannot be out of the range of its {@linkplain java.lang.classfile##data-types
|
||||
* data type}; a {@code List} cannot exceed the maximum representable size of
|
||||
* its table data type, or contain an unrepresentable element. Restrictions
|
||||
* based on underlying data type, such as the {@code int} and {@code List} ones
|
||||
* before, are specified on the corresponding APIs. Unless otherwise noted, in
|
||||
* all structures, a {@code String} cannot exceed {@code 65535} bytes when
|
||||
* represented in modified UTF-8 format.
|
||||
* <p>
|
||||
* Unless otherwise noted, passing null or an array or collection that contains
|
||||
* null as an element to a constructor or method of any Class-File API class or
|
||||
* interface will cause a {@link NullPointerException} to be thrown.
|
||||
* <p>
|
||||
* No consistency checks are performed while building or transforming classfiles
|
||||
* (except for null and representable arguments checks). All builders and
|
||||
|
||||
@ -35,6 +35,7 @@ import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.constant.ClassOrInterfaceDescImpl;
|
||||
import jdk.internal.constant.PrimitiveClassDescImpl;
|
||||
import jdk.internal.util.ArraysSupport;
|
||||
import jdk.internal.util.ModifiedUtf;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@ -141,7 +142,7 @@ public abstract sealed class AbstractPoolEntry {
|
||||
@Stable TypeDescriptor typeSym;
|
||||
|
||||
Utf8EntryImpl(ConstantPool cpm, int index,
|
||||
byte[] rawBytes, int offset, int rawLen) {
|
||||
byte[] rawBytes, int offset, int rawLen) {
|
||||
super(cpm, index, 0);
|
||||
this.rawBytes = rawBytes;
|
||||
this.offset = offset;
|
||||
@ -154,6 +155,10 @@ public abstract sealed class AbstractPoolEntry {
|
||||
}
|
||||
|
||||
Utf8EntryImpl(ConstantPool cpm, int index, String s, int contentHash) {
|
||||
// Prevent creation of unwritable entries
|
||||
if (!ModifiedUtf.isValidLengthInConstantPool(s)) {
|
||||
throw new IllegalArgumentException("utf8 length out of range of u2: " + ModifiedUtf.utfLen(s));
|
||||
}
|
||||
super(cpm, index, 0);
|
||||
this.rawBytes = null;
|
||||
this.offset = 0;
|
||||
|
||||
@ -277,10 +277,9 @@ public final class BufWriterImpl implements BufWriter {
|
||||
int strlen = str.length();
|
||||
int countNonZeroAscii = JLA.countNonZeroAscii(str);
|
||||
long utflenLong = utfLen(str, countNonZeroAscii);
|
||||
if (!ExactConversionsSupport.isLongToCharExact(utflenLong)) {
|
||||
throw new IllegalArgumentException("utf8 length out of range of u2: " + utflenLong);
|
||||
}
|
||||
int utflen = (int)utflenLong;
|
||||
// Utf8Entry should always be writable
|
||||
assert ExactConversionsSupport.isLongToCharExact(utflenLong) : utflenLong;
|
||||
int utflen = (int) utflenLong;
|
||||
reserveSpace(utflen + 3);
|
||||
|
||||
int offset = this.offset;
|
||||
|
||||
@ -28,19 +28,17 @@ package jdk.internal.util;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
/**
|
||||
* Helper to JDK UTF putChar and Calculate length
|
||||
*
|
||||
* @since 24
|
||||
*/
|
||||
public abstract class ModifiedUtf {
|
||||
// Maximum number of bytes allowed for a Modified UTF-8 encoded string
|
||||
// in a ClassFile constant pool entry (CONSTANT_Utf8_info).
|
||||
/// Utilities for string encoding and decoding with the
|
||||
/// [Modified UTF-8][java.io.DataInput##modified-utf-8] format.
|
||||
public final class ModifiedUtf {
|
||||
/// Maximum number of bytes allowed for a Modified UTF-8 encoded string
|
||||
/// in a [java.lang.classfile.constantpool.Utf8Entry] or a hotspot `Symbol`.
|
||||
public static final int CONSTANT_POOL_UTF8_MAX_BYTES = 65535;
|
||||
|
||||
private ModifiedUtf() {
|
||||
}
|
||||
|
||||
/// Writes a char to the pre-sized modified UTF buffer.
|
||||
@ForceInline
|
||||
public static int putChar(byte[] buf, int offset, char c) {
|
||||
if (c != 0 && c < 0x80) {
|
||||
@ -58,11 +56,23 @@ public abstract class ModifiedUtf {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the utf length of a string
|
||||
* @param str input string
|
||||
* @param countNonZeroAscii the number of non-zero ascii characters in the prefix calculated by JLA.countNonZeroAscii(str)
|
||||
*/
|
||||
/// Calculate the encoded length of an input String.
|
||||
/// For many workloads that have fast paths for ASCII-only prefixes,
|
||||
/// [#utfLen(String, int)] skips scanning that prefix.
|
||||
///
|
||||
/// @param str input string
|
||||
public static long utfLen(String str) {
|
||||
return utfLen(str, 0);
|
||||
}
|
||||
|
||||
/// Calculate the encoded length of trailing parts of an input String,
|
||||
/// after [jdk.internal.access.JavaLangAccess#countNonZeroAscii(String)]
|
||||
/// calculates the number of contiguous single-byte characters in the
|
||||
/// beginning of the string.
|
||||
///
|
||||
/// @param str input string
|
||||
/// @param countNonZeroAscii the number of non-zero ascii characters in the
|
||||
/// prefix calculated by JLA.countNonZeroAscii(str)
|
||||
@ForceInline
|
||||
public static long utfLen(String str, int countNonZeroAscii) {
|
||||
long utflen = str.length();
|
||||
@ -74,11 +84,11 @@ public abstract class ModifiedUtf {
|
||||
return utflen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Modified UTF-8 encoded length of the given string
|
||||
* fits within the ClassFile constant pool limit (u2 length = 65535 bytes).
|
||||
* @param str the string to check
|
||||
*/
|
||||
/// Checks whether an input String can be encoded in a
|
||||
/// [java.lang.classfile.constantpool.Utf8Entry], or represented as a
|
||||
/// hotspot `Symbol` (which has the same length limit).
|
||||
///
|
||||
/// @param str input string
|
||||
@ForceInline
|
||||
public static boolean isValidLengthInConstantPool(String str) {
|
||||
// Quick approximation: each char can be at most 3 bytes in Modified UTF-8.
|
||||
@ -91,7 +101,7 @@ public abstract class ModifiedUtf {
|
||||
return false;
|
||||
}
|
||||
// Check exact Modified UTF-8 length.
|
||||
long utfLen = utfLen(str, 0);
|
||||
long utfLen = utfLen(str);
|
||||
return utfLen <= CONSTANT_POOL_UTF8_MAX_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
/*
|
||||
* @test
|
||||
* @bug 8320360 8330684 8331320 8331655 8331940 8332486 8335820 8336833 8361635
|
||||
* 8367585
|
||||
* @summary Testing ClassFile limits.
|
||||
* @run junit LimitsTest
|
||||
*/
|
||||
@ -52,17 +53,23 @@ import java.lang.classfile.instruction.SwitchCase;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.constant.ModuleDesc;
|
||||
import java.lang.constant.PackageDesc;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.classfile.impl.BufWriterImpl;
|
||||
import jdk.internal.classfile.impl.DirectCodeBuilder;
|
||||
import jdk.internal.classfile.impl.DirectMethodBuilder;
|
||||
import jdk.internal.classfile.impl.LabelContext;
|
||||
import jdk.internal.classfile.impl.TemporaryConstantPool;
|
||||
import jdk.internal.classfile.impl.UnboundAttribute;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static java.lang.classfile.ClassFile.ACC_STATIC;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
@ -447,4 +454,73 @@ class LimitsTest {
|
||||
var cpb = ConstantPoolBuilder.of();
|
||||
cpb.intEntry(-cpb.intEntry(0).hashCode());
|
||||
}
|
||||
|
||||
static List<String> legalStrings() {
|
||||
var empty = "";
|
||||
var allAscii = "e".repeat(0xFFFF);
|
||||
// 3-byte utf8 characters
|
||||
var largeChars = String.valueOf((char) 0x800).repeat(0xFFFF / 3);
|
||||
return List.of(empty, allAscii, largeChars);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("legalStrings")
|
||||
void testStringLengthInLimit(String st) {
|
||||
TemporaryConstantPool.INSTANCE.utf8Entry(st);
|
||||
ConstantPoolBuilder.of().utf8Entry(st);
|
||||
}
|
||||
|
||||
static List<String> oversizedStrings() {
|
||||
var allAscii = "e".repeat(0x10000);
|
||||
// 3-byte utf8 characters
|
||||
var largeChars = String.valueOf((char) 0x800).repeat(0xFFFF / 3 + 1);
|
||||
return List.of(allAscii, largeChars);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("oversizedStrings")
|
||||
void testStringLengthOverLimit(String st) {
|
||||
assertThrows(IllegalArgumentException.class, () -> TemporaryConstantPool.INSTANCE.utf8Entry(st));
|
||||
assertThrows(IllegalArgumentException.class, () -> ConstantPoolBuilder.of().utf8Entry(st));
|
||||
}
|
||||
|
||||
static Stream<ConstantPoolBuilder> pools() {
|
||||
return Stream.of(ConstantPoolBuilder.of(), TemporaryConstantPool.INSTANCE);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("pools")
|
||||
void testSingleReferenceNominalDescriptorOverLimit(ConstantPoolBuilder cpb) {
|
||||
var fittingName = "A" + "a".repeat(65532); // fits "enveloped" L ;
|
||||
var borderName = "B" + "b".repeat(65534); // fits only "not enveloped"
|
||||
var overflowName = "C" + "b".repeat(65535); // nothing fits
|
||||
|
||||
var fittingClassDesc = ClassDesc.of(fittingName);
|
||||
var borderClassDesc = ClassDesc.of(borderName);
|
||||
var overflowClassDesc = ClassDesc.of(overflowName);
|
||||
cpb.classEntry(fittingClassDesc);
|
||||
cpb.utf8Entry(fittingClassDesc);
|
||||
cpb.classEntry(borderClassDesc);
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.utf8Entry(borderClassDesc));
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.classEntry(overflowClassDesc));
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.utf8Entry(overflowClassDesc));
|
||||
|
||||
cpb.packageEntry(PackageDesc.of(borderName));
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.packageEntry(PackageDesc.of(overflowName)));
|
||||
cpb.moduleEntry(ModuleDesc.of(borderName));
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.moduleEntry(ModuleDesc.of(overflowName)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("pools")
|
||||
void testMethodTypeDescOverLimit(ConstantPoolBuilder cpb) {
|
||||
var borderReturnMtd = MethodTypeDesc.of(ClassDesc.of("R" + "r".repeat(65530)));
|
||||
var overflowReturnMtd = MethodTypeDesc.of(ClassDesc.of("R" + "r".repeat(65531)));
|
||||
var borderParamMtd = MethodTypeDesc.of(CD_void, ClassDesc.of("P" + "p".repeat(65529)));
|
||||
var overflowParamMtd = MethodTypeDesc.of(CD_void, ClassDesc.of("P" + "p".repeat(65530)));
|
||||
cpb.utf8Entry(borderParamMtd);
|
||||
cpb.utf8Entry(borderReturnMtd);
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.utf8Entry(overflowReturnMtd));
|
||||
assertThrows(IllegalArgumentException.class, () -> cpb.utf8Entry(overflowParamMtd));
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,10 +24,11 @@
|
||||
/*
|
||||
* @test
|
||||
* @summary Testing Signatures.
|
||||
* @bug 8321540 8319463 8357955 8368050 8368331
|
||||
* @bug 8321540 8319463 8357955 8368050 8367585 8368331
|
||||
* @run junit SignaturesTest
|
||||
*/
|
||||
import java.io.IOException;
|
||||
import java.lang.classfile.attribute.SignatureAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
@ -35,6 +36,7 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -52,9 +54,8 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static helpers.ClassRecord.assertEqualsDeep;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
class SignaturesTest {
|
||||
|
||||
@ -270,114 +271,134 @@ class SignaturesTest {
|
||||
"ClassDesc derived from signature");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBadTypeSignatures() {
|
||||
"""
|
||||
LObject
|
||||
LObject;B
|
||||
LIterable<LFoo>
|
||||
LIterable<<
|
||||
TBar
|
||||
TBar<LFoo;>
|
||||
B<LFoo;>
|
||||
B<LFoo;>;V
|
||||
X
|
||||
[LObject
|
||||
[LIterable<LFoo>
|
||||
[LIterable<<
|
||||
[TBar
|
||||
[TBar<LFoo;>
|
||||
[B<LFoo;>
|
||||
[X
|
||||
LSet<+Kind<**>;>;
|
||||
LSet<?Kind<*>;>;
|
||||
()V
|
||||
Ljava/util/Opt<Ljava/lang/Integer;>ional;
|
||||
Lcom/example/Outer<Ljava/lang/String;>.package/Inner<[I>;
|
||||
LSample>;
|
||||
LSample:Other;
|
||||
LOuter<[JTT;>.[Inner;
|
||||
TA:J;
|
||||
LEmpty<>;
|
||||
L
|
||||
Lcom
|
||||
Lcom/example/
|
||||
Lcom/example/Outer<
|
||||
Lcom/example/Outer<Ljava/
|
||||
Lcom/example/Outer<Ljava/lang/String
|
||||
Lcom/example/Outer<Ljava/lang/String;
|
||||
Lcom/example/Outer<Ljava/lang/String;>
|
||||
Lcom/example/Outer<Ljava/lang/String;>.
|
||||
Lcom/example/Outer<Ljava/lang/String;>.Inner<[I>
|
||||
[V
|
||||
""".lines().forEach(assertThrows(Signature::parseFrom));
|
||||
static Stream<String> badTypeSignatures() {
|
||||
return """
|
||||
LObject
|
||||
LObject;B
|
||||
LIterable<LFoo>
|
||||
LIterable<<
|
||||
TBar
|
||||
TBar<LFoo;>
|
||||
B<LFoo;>
|
||||
B<LFoo;>;V
|
||||
X
|
||||
[LObject
|
||||
[LIterable<LFoo>
|
||||
[LIterable<<
|
||||
[TBar
|
||||
[TBar<LFoo;>
|
||||
[B<LFoo;>
|
||||
[X
|
||||
LSet<+Kind<**>;>;
|
||||
LSet<?Kind<*>;>;
|
||||
()V
|
||||
Ljava/util/Opt<Ljava/lang/Integer;>ional;
|
||||
Lcom/example/Outer<Ljava/lang/String;>.package/Inner<[I>;
|
||||
LSample>;
|
||||
LSample:Other;
|
||||
LOuter<[JTT;>.[Inner;
|
||||
TA:J;
|
||||
LEmpty<>;
|
||||
L
|
||||
Lcom
|
||||
Lcom/example/
|
||||
Lcom/example/Outer<
|
||||
Lcom/example/Outer<Ljava/
|
||||
Lcom/example/Outer<Ljava/lang/String
|
||||
Lcom/example/Outer<Ljava/lang/String;
|
||||
Lcom/example/Outer<Ljava/lang/String;>
|
||||
Lcom/example/Outer<Ljava/lang/String;>.
|
||||
Lcom/example/Outer<Ljava/lang/String;>.Inner<[I>
|
||||
[V
|
||||
""".lines();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGoodTypeSignatures() {
|
||||
"""
|
||||
Ljava/util/Optional<Ljava/lang/Integer;>;
|
||||
Lcom/example/Outer<Ljava/lang/Integer;>.Inner<[I>;
|
||||
LSample;
|
||||
LOuter<[JTT;>.Inner;
|
||||
LOuter.Inner;
|
||||
""".lines().forEach(Signature::parseFrom);
|
||||
@ParameterizedTest
|
||||
@MethodSource("badTypeSignatures")
|
||||
void testBadTypeSignatures(String s) {
|
||||
assertThrows(IllegalArgumentException.class, () -> Signature.parseFrom(s));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBadClassSignatures() {
|
||||
"""
|
||||
Ljava/lang/Object;Ljava/lang/Iterable<LFoo;>
|
||||
LObject
|
||||
LObject;B
|
||||
LIterable<LFoo>
|
||||
LIterable<<
|
||||
TBar
|
||||
TBar<LFoo;>
|
||||
B<LFoo;>
|
||||
B<LFoo;>;V
|
||||
X
|
||||
LFoo<TK;>.It;L
|
||||
<K+LObject;>LFoo<TK;;>;LFoo<TK;>;LBar;
|
||||
<K:LObject;>>LFoo<TK;>;
|
||||
<K:LObject;>LFoo<+>;
|
||||
()V
|
||||
<K:Ljava/lang/Object;>Ljava/lang/Object;TK;
|
||||
Ljava/lang/Object;[Ljava/lang/Object;
|
||||
[Ljava/util/Optional<[I>;
|
||||
[I
|
||||
<K:Ljava/lang/Object;>TK;
|
||||
<K;Q:Ljava/lang/Object;>Ljava/lang/Object;
|
||||
<:Ljava/lang/Object;>Ljava/lang/Object;
|
||||
<>Ljava/lang/Object;
|
||||
""".lines().forEach(assertThrows(ClassSignature::parseFrom));
|
||||
static Stream<String> goodTypeSignatures() {
|
||||
return """
|
||||
Ljava/util/Optional<Ljava/lang/Integer;>;
|
||||
Lcom/example/Outer<Ljava/lang/Integer;>.Inner<[I>;
|
||||
LSample;
|
||||
LOuter<[JTT;>.Inner;
|
||||
LOuter.Inner;
|
||||
""".lines();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBadMethodSignatures() {
|
||||
"""
|
||||
LObject;
|
||||
B
|
||||
()V^
|
||||
()V^B
|
||||
()V^X
|
||||
(LObject;)
|
||||
(LObject)V
|
||||
()LIterable<LFoo>
|
||||
()LIterable<<
|
||||
()TBar
|
||||
()TBar;B
|
||||
(TBar<LFoo;>)V
|
||||
(B<LFoo;>)V
|
||||
(X)
|
||||
()X
|
||||
()VB
|
||||
()LSet<+Kind<**>;>;
|
||||
(LSet<?Kind<*>;>;)V
|
||||
<T::LA>()V
|
||||
(TT;I)VI
|
||||
(V)V
|
||||
""".lines().forEach(assertThrows(MethodSignature::parseFrom));
|
||||
@ParameterizedTest
|
||||
@MethodSource("goodTypeSignatures")
|
||||
void testGoodTypeSignature(String s) {
|
||||
Signature.parseFrom(s);
|
||||
}
|
||||
|
||||
static Stream<String> badClassSignatures() {
|
||||
return """
|
||||
Ljava/lang/Object;Ljava/lang/Iterable<LFoo;>
|
||||
LObject
|
||||
LObject;B
|
||||
LIterable<LFoo>
|
||||
LIterable<<
|
||||
TBar
|
||||
TBar<LFoo;>
|
||||
B<LFoo;>
|
||||
B<LFoo;>;V
|
||||
X
|
||||
LFoo<TK;>.It;L
|
||||
<K+LObject;>LFoo<TK;;>;LFoo<TK;>;LBar;
|
||||
<K:LObject;>>LFoo<TK;>;
|
||||
<K:LObject;>LFoo<+>;
|
||||
()V
|
||||
<K:Ljava/lang/Object;>Ljava/lang/Object;TK;
|
||||
Ljava/lang/Object;[Ljava/lang/Object;
|
||||
[Ljava/util/Optional<[I>;
|
||||
[I
|
||||
<K:Ljava/lang/Object;>TK;
|
||||
<K;Q:Ljava/lang/Object;>Ljava/lang/Object;
|
||||
<:Ljava/lang/Object;>Ljava/lang/Object;
|
||||
<>Ljava/lang/Object;
|
||||
""".lines();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("badClassSignatures")
|
||||
void testBadClassSignature(String s) {
|
||||
assertThrows(IllegalArgumentException.class, () -> ClassSignature.parseFrom(s));
|
||||
}
|
||||
|
||||
static Stream<String> badMethodSignatures() {
|
||||
return """
|
||||
LObject;
|
||||
B
|
||||
()V^
|
||||
()V^B
|
||||
()V^X
|
||||
(LObject;)
|
||||
(LObject)V
|
||||
()LIterable
|
||||
()LIterable
|
||||
()TBar
|
||||
()TBar;B
|
||||
(TBar<LFoo;
|
||||
(B<LFoo;>)V
|
||||
(X)
|
||||
()X
|
||||
()VB
|
||||
()LSet<+Kin
|
||||
(LSet<?Kind
|
||||
<T::LA>()V
|
||||
(TT;I)VI
|
||||
(V)V
|
||||
""".lines();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("badMethodSignatures")
|
||||
void testBadMethodSignature(String s) {
|
||||
assertThrows(IllegalArgumentException.class, () -> MethodSignature.parseFrom(s));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -385,16 +406,76 @@ class SignaturesTest {
|
||||
var sig = Signature.parseFrom("I");
|
||||
var arrSig = Signature.parseFrom("[I");
|
||||
for (int dim : List.of(256, -1, 0))
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> Signature.ArrayTypeSig.of(dim, sig));
|
||||
assertThrows(IllegalArgumentException.class, () -> Signature.ArrayTypeSig.of(dim, sig));
|
||||
for (int dim : List.of(255, -1, 0))
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> Signature.ArrayTypeSig.of(dim, arrSig));
|
||||
assertThrows(IllegalArgumentException.class, () -> Signature.ArrayTypeSig.of(dim, arrSig));
|
||||
for (int dim : List.of(255, 1))
|
||||
Signature.ArrayTypeSig.of(dim, sig);
|
||||
for (int dim : List.of(254, 1))
|
||||
Signature.ArrayTypeSig.of(dim, arrSig);
|
||||
}
|
||||
|
||||
private Consumer<String> assertThrows(Function<String, ?> parser) {
|
||||
return s -> Assertions.assertThrows(IllegalArgumentException.class, () -> parser.apply(s), s);
|
||||
static Stream<Signature> longTypeSignatures() {
|
||||
var longAsciiName = "A" + "a".repeat(65536);
|
||||
var longCharName = "§".repeat(32768);
|
||||
var simpleClassSig = ClassTypeSig.of(longAsciiName);
|
||||
var nestedSig = ClassTypeSig.of(simpleClassSig, longCharName);
|
||||
var typeVarSig = TypeVarSig.of(longCharName);
|
||||
var parameterizedSig = ClassTypeSig.of(longCharName, TypeArg.of(nestedSig), TypeArg.unbounded());
|
||||
var parameterizedNestedSig = ClassTypeSig.of(nestedSig, longAsciiName, TypeArg.superOf(simpleClassSig));
|
||||
return Stream.of(simpleClassSig, nestedSig, typeVarSig, parameterizedSig, parameterizedNestedSig);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("longTypeSignatures")
|
||||
void testLongTypeSignature(Signature sig) {
|
||||
var st = sig.signatureString();
|
||||
Signature.parseFrom(st); // Valid signature
|
||||
assertThrows(IllegalArgumentException.class, () -> SignatureAttribute.of(sig)); // Cannot write to class
|
||||
}
|
||||
|
||||
static Stream<ClassSignature> longClassSignatures() {
|
||||
var longAsciiName = "A" + "a".repeat(65536);
|
||||
var longCharName = "§".repeat(32768);
|
||||
var simpleClassSig = ClassTypeSig.of(longAsciiName);
|
||||
var longSuperClass = ClassSignature.of(simpleClassSig);
|
||||
var longNameParam = TypeParam.of(longCharName, ClassTypeSig.of(CD_String));
|
||||
var longBoundParam = TypeParam.of("T", simpleClassSig);
|
||||
var longNameParamClass = ClassSignature.of(List.of(longNameParam), ClassTypeSig.of(CD_Object));
|
||||
var longBoundParamClass = ClassSignature.of(List.of(longBoundParam), ClassTypeSig.of(CD_Number));
|
||||
return Stream.of(longSuperClass, longNameParamClass, longBoundParamClass);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("longClassSignatures")
|
||||
void testLongClassSignature(ClassSignature sig) {
|
||||
var st = sig.signatureString();
|
||||
ClassSignature.parseFrom(st); // Valid signature
|
||||
assertThrows(IllegalArgumentException.class, () -> SignatureAttribute.of(sig)); // Cannot write to class
|
||||
}
|
||||
|
||||
static Stream<MethodSignature> longMethodSignatures() {
|
||||
var longAsciiName = "A" + "a".repeat(65536);
|
||||
var longCharName = "§".repeat(32768);
|
||||
var simpleClassSig = ClassTypeSig.of(longAsciiName);
|
||||
var longNameTypeVar = TypeVarSig.of(longCharName);
|
||||
var longReturnMethod = MethodSignature.of(simpleClassSig);
|
||||
var longNameParam = TypeParam.of(longCharName, ClassTypeSig.of(CD_String));
|
||||
var longNameParamMethod = MethodSignature.of(List.of(longNameParam), List.of(), BaseTypeSig.of(CD_void));
|
||||
var longThrowMethod = MethodSignature.of(List.of(), List.of(longNameTypeVar), ClassTypeSig.of(CD_Number));
|
||||
var longParameterMethod = MethodSignature.of(BaseTypeSig.of(CD_int), simpleClassSig);
|
||||
|
||||
var eachParameter = ClassTypeSig.of("A" + "a".repeat(250));
|
||||
var parameterArray = Collections.nCopies(300, eachParameter).toArray(Signature[]::new);
|
||||
var manyParameterMethod = MethodSignature.of(BaseTypeSig.of(CD_void), parameterArray);
|
||||
return Stream.of(longReturnMethod, longNameParamMethod, longThrowMethod, longParameterMethod, manyParameterMethod);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("longMethodSignatures")
|
||||
void testLongMethodSignature(MethodSignature sig) {
|
||||
var st = sig.signatureString();
|
||||
MethodSignature.parseFrom(st); // Valid signature
|
||||
assertThrows(IllegalArgumentException.class, () -> SignatureAttribute.of(sig)); // Cannot write to class
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,19 +104,6 @@ public class ModifiedUtfTest {
|
||||
} catch (UTFDataFormatException e) {
|
||||
}
|
||||
|
||||
BufWriterImpl bufWriter = new BufWriterImpl(ConstantPoolBuilder.of(), (ClassFileImpl) ClassFile.of());
|
||||
Method writeUtfEntry = bufWriter.getClass().getDeclaredMethod("writeUtfEntry", String.class);
|
||||
writeUtfEntry.setAccessible(true);
|
||||
try {
|
||||
writeUtfEntry.invoke(bufWriter, largeString);
|
||||
throw new RuntimeException("Expected IllegalArgumentException was not thrown.");
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (!(cause instanceof IllegalArgumentException)) {
|
||||
throw new RuntimeException("Expected IllegalArgumentException was not thrown.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In the writeUTF function, utfLen is used to calculate the length of the string to be written
|
||||
* and store it in the stream header. This test uses the HeaderCaptureOutputStream inner class
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user