Skip to content

Commit acb8816

Browse files
committed
[GR-57395] Add support for weak symbols to avoid leaks with class unloading.
PullRequest: graal/19535
2 parents 950d08f + 2c8bd6d commit acb8816

22 files changed

+556
-216
lines changed

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileParser.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ public final class ClassfileParser {
135135
private static final DebugTimer CODE_PARSE = DebugTimer.create("code parsing", PARSE_SINGLE_METHOD);
136136
private static final DebugTimer CODE_READ = DebugTimer.create("code read", CODE_PARSE);
137137
private static final DebugTimer EXCEPTION_HANDLERS = DebugTimer.create("exception handlers", CODE_PARSE);
138+
private static final DebugTimer VALIDATION = DebugTimer.create("CP validation", KLASS_PARSE);
138139

139140
private static final DebugCounter UTF8_ENTRY_COUNT = DebugCounter.create("UTF8 Constant Pool entries");
140141

@@ -202,20 +203,20 @@ public final class ClassfileParser {
202203
private final boolean validate;
203204

204205
private ClassfileParser(ParsingContext parsingContext, ClassfileStream stream, boolean verifiable, boolean loaderIsBootOrPlatform, Symbol<Type> requestedClassType, boolean isHidden,
205-
boolean forceAllowVMAnnotations) {
206+
boolean forceAllowVMAnnotations, boolean validate) {
206207
this.requestedClassType = requestedClassType;
207208
this.parsingContext = parsingContext;
208209
this.stream = Objects.requireNonNull(stream);
209210
this.verifiable = verifiable;
210211
this.loaderIsBootOrPlatform = loaderIsBootOrPlatform;
211212
this.isHidden = isHidden;
212213
this.forceAllowVMAnnotations = forceAllowVMAnnotations;
213-
this.validate = true; // always validate
214+
this.validate = validate;
214215
}
215216

216217
// Note: only used for reading the class name from class bytes
217218
private ClassfileParser(ParsingContext parsingContext, ClassfileStream stream) {
218-
this(parsingContext, stream, false, false, null, false, false);
219+
this(parsingContext, stream, false, false, null, false, false, true);
219220
}
220221

221222
void handleBadConstant(Tag tag, ClassfileStream s) {
@@ -256,8 +257,8 @@ void checkDynamicConstantSupport(Tag tag) {
256257
}
257258

258259
public static ParserKlass parse(ParsingContext parsingContext, ClassfileStream stream, boolean verifiable, boolean loaderIsBootOrPlatform, Symbol<Type> requestedClassType, boolean isHidden,
259-
boolean forceAllowVMAnnotations) throws ValidationException {
260-
return new ClassfileParser(parsingContext, stream, verifiable, loaderIsBootOrPlatform, requestedClassType, isHidden, forceAllowVMAnnotations).parseClass();
260+
boolean forceAllowVMAnnotations, boolean validate) throws ValidationException {
261+
return new ClassfileParser(parsingContext, stream, verifiable, loaderIsBootOrPlatform, requestedClassType, isHidden, forceAllowVMAnnotations, validate).parseClass();
261262
}
262263

263264
private ParserKlass parseClass() throws ValidationException {
@@ -530,8 +531,10 @@ private ImmutableConstantPool parseConstantPool() throws ValidationException {
530531

531532
// Validation
532533
if (validate) {
533-
for (int j = 1; j < constantPool.length(); ++j) {
534-
entries[j].validate(constantPool);
534+
try (DebugCloseable handlers = VALIDATION.scope(parsingContext.getTimers())) {
535+
for (int j = 1; j < constantPool.length(); ++j) {
536+
entries[j].validate(constantPool);
537+
}
535538
}
536539
}
537540

@@ -661,16 +664,18 @@ private ParserMethod[] parseMethods(boolean isInterface) throws ValidationExcept
661664
* Classes in general have lots of methods: use a hash set rather than array lookup for dup
662665
* check.
663666
*/
664-
final HashSet<MethodKey> dup = new HashSet<>(methodCount);
667+
final HashSet<MethodKey> dup = validate ? new HashSet<>(methodCount) : null;
665668
for (int i = 0; i < methodCount; ++i) {
666669
ParserMethod method;
667670
try (DebugCloseable closeable = PARSE_SINGLE_METHOD.scope(parsingContext.getTimers())) {
668671
method = parseMethod(isInterface);
669672
}
670673
methods[i] = method;
671-
try (DebugCloseable closeable = NO_DUP_CHECK.scope(parsingContext.getTimers())) {
672-
if (!dup.add(new MethodKey(method))) {
673-
throw classFormatError("Duplicate method name and signature: " + method.getName() + " " + method.getSignature());
674+
if (validate) {
675+
try (DebugCloseable closeable = NO_DUP_CHECK.scope(parsingContext.getTimers())) {
676+
if (!dup.add(new MethodKey(method))) {
677+
throw classFormatError("Duplicate method name and signature: " + method.getName() + " " + method.getSignature());
678+
}
674679
}
675680
}
676681
}
@@ -1695,7 +1700,7 @@ private CodeAttribute parseCodeAttribute(Symbol<Name> name) throws ValidationExc
16951700
}
16961701
}
16971702

1698-
if (totalLocalTableCount > 0) {
1703+
if (validate && totalLocalTableCount > 0) {
16991704
validateLocalTables(codeAttributes);
17001705
}
17011706

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParsingContext.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ public interface ParsingContext {
4545

4646
Symbol<Name> getOrCreateName(ByteSequence byteSequence);
4747

48-
// symbolify(Types.nameToType(byteSequence))
4948
Symbol<Type> getOrCreateTypeFromName(ByteSequence byteSequence);
5049

51-
Utf8Constant getOrCreateUtf8Constant(ByteSequence bytes);
50+
Utf8Constant getOrCreateUtf8Constant(ByteSequence byteSequence);
5251

5352
interface Logger {
5453
void log(String message);

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/Local.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,16 @@ public int hashCode(Object o) {
6060
private final Symbol<Name> name;
6161
private final Symbol<Type> type;
6262
private final Symbol<?> typeSignature;
63-
private final int startBci;
64-
private final int endBci;
65-
private final int slot;
63+
private final char startBci;
64+
private final char endBci;
65+
private final char slot;
6666

6767
public Local(Symbol<Name> name, Symbol<Type> type, Symbol<?> typeSignature, int startBci, int endBci, int slot) {
6868
assert type != null || typeSignature != null;
6969
this.name = name;
70-
this.startBci = startBci;
71-
this.endBci = endBci;
72-
this.slot = slot;
70+
this.startBci = (char) startBci;
71+
this.endBci = (char) endBci;
72+
this.slot = (char) slot;
7373
this.type = type;
7474
this.typeSignature = typeSignature;
7575
}

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ByteSequence.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import java.io.IOException;
2626
import java.nio.ByteBuffer;
27+
import java.util.Arrays;
2728
import java.util.Objects;
2829

2930
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -137,11 +138,29 @@ public final int hashCode() {
137138
return hashCode;
138139
}
139140

140-
public final ByteSequence subSequence(int offset, int length) {
141-
if (offset == 0 && length == length()) {
141+
/**
142+
* Returns a subsequence of this symbol from the specified index to the end.
143+
*
144+
* @param from The starting index (inclusive)
145+
* @return A ByteSequence representing the subsequence
146+
*/
147+
public ByteSequence subSequence(int from) {
148+
return subSequence(from, length());
149+
}
150+
151+
/**
152+
* Returns a subsequence of this symbol between the specified indices.
153+
*
154+
* @param startInclusive The starting index (inclusive)
155+
* @param endExclusive The ending index (exclusive)
156+
* @return A ByteSequence representing the subsequence
157+
*/
158+
public ByteSequence subSequence(int startInclusive, int endExclusive) {
159+
assert 0 <= startInclusive && startInclusive <= endExclusive && endExclusive <= length();
160+
if (startInclusive == 0 && endExclusive == length()) {
142161
return this;
143162
}
144-
return wrap(getUnderlyingBytes(), offset() + offset, length);
163+
return wrap(getUnderlyingBytes(), offset() + startInclusive, endExclusive - startInclusive);
145164
}
146165

147166
public final boolean contentEquals(ByteSequence other) {
@@ -246,4 +265,17 @@ public ByteSequence concat(ByteSequence next) {
246265
next.writeTo(data, this.length());
247266
return wrap(data);
248267
}
268+
269+
@Override
270+
public boolean equals(Object other) {
271+
if (!(other instanceof ByteSequence that)) {
272+
return false;
273+
}
274+
if (this.hashCode != that.hashCode) {
275+
return false;
276+
}
277+
return Arrays.equals(
278+
this.value, this.offset(), this.offset() + this.length(),
279+
that.value, that.offset(), that.offset() + that.length());
280+
}
249281
}

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/NameSymbols.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,20 @@ public Symbol<Name> getOrCreate(String nameString) {
8181
* @return A new or existing Symbol representing the name
8282
*/
8383
public Symbol<Name> getOrCreate(ByteSequence nameBytes) {
84-
return symbols.symbolify(nameBytes);
84+
return getOrCreate(nameBytes, false);
85+
}
86+
87+
/**
88+
* Creates a new name symbol or retrieves an existing one from a byte sequence. If the symbol
89+
* doesn't exist, it will be created.
90+
*
91+
* @param ensureStrongReference if {@code true}, the returned symbol is guaranteed to be
92+
* strongly referenced by the symbol table
93+
* @param nameBytes The name as a byte sequence to create or retrieve
94+
* @return A new or existing Symbol representing the name
95+
*/
96+
public Symbol<Name> getOrCreate(ByteSequence nameBytes, boolean ensureStrongReference) {
97+
return symbols.getOrCreate(nameBytes, ensureStrongReference);
8598
}
8699

87100
/**

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ParserSymbols.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
* Predefined symbols used by the parser and shared with Espresso. Contains static definitions for
2929
* commonly used types, names and signatures.
3030
* <p>
31-
* This class is not meant for general symbol management - use {@link Symbols}, {@link TypeSymbols},
32-
* {@link NameSymbols} etc. for that purpose.
31+
* This class is not meant for general symbol management - use {@link SignatureSymbols},
32+
* {@link TypeSymbols}, {@link NameSymbols} etc. for that purpose.
3333
* <p>
3434
* The symbols are initialized in a specific order via the inner classes:
3535
* <ul>
@@ -42,7 +42,7 @@
4242
*/
4343
public final class ParserSymbols {
4444

45-
public static final StaticSymbols SYMBOLS = new StaticSymbols();
45+
public static final StaticSymbols SYMBOLS = new StaticSymbols(1 << 8);
4646

4747
static {
4848
ParserNames.ensureInitialized();

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/SignatureSymbols.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.Arrays;
2929
import java.util.Iterator;
3030
import java.util.List;
31-
import java.util.concurrent.ConcurrentHashMap;
3231

3332
import com.oracle.truffle.api.nodes.ExplodeLoop;
3433
import com.oracle.truffle.espresso.classfile.JavaKind;
@@ -54,8 +53,6 @@ public final class SignatureSymbols {
5453
private final TypeSymbols typeSymbols;
5554
private final Symbols symbols;
5655

57-
private final ConcurrentHashMap<Symbol<Signature>, Symbol<Type>[]> parsedSignatures = new ConcurrentHashMap<>();
58-
5956
/**
6057
* Creates a new SignatureSymbols instance.
6158
*
@@ -103,10 +100,24 @@ public Symbol<Signature> lookupValidSignature(ByteSequence signatureBytes) {
103100
* @see Validation#validSignatureDescriptor(ByteSequence)
104101
*/
105102
public Symbol<Signature> getOrCreateValidSignature(ByteSequence signatureBytes) {
103+
return getOrCreateValidSignature(signatureBytes, false);
104+
}
105+
106+
/**
107+
* Creates or retrieves a valid method signature symbol from a byte sequence.
108+
*
109+
* @param ensureStrongReference if {@code true}, the returned symbol is guaranteed to be
110+
* strongly referenced by the symbol table
111+
* @param signatureBytes The method signature bytes to create or retrieve
112+
* @return The signature Symbol if valid, null otherwise
113+
*
114+
* @see Validation#validSignatureDescriptor(ByteSequence)
115+
*/
116+
public Symbol<Signature> getOrCreateValidSignature(ByteSequence signatureBytes, boolean ensureStrongReference) {
106117
if (!Validation.validSignatureDescriptor(signatureBytes)) {
107118
return null;
108119
}
109-
return symbols.symbolify(signatureBytes);
120+
return symbols.getOrCreate(signatureBytes, ensureStrongReference);
110121
}
111122

112123
/**
@@ -121,13 +132,13 @@ public TypeSymbols getTypes() {
121132
/**
122133
* Gets or computes the parsed form of a method signature. The parsed form is an array of Type
123134
* symbols where the last element is the return type and all preceding elements are parameter
124-
* types. Results are cached for performance.
135+
* types.
125136
*
126137
* @param signature The method signature to parse
127138
* @return Array of Type symbols representing parameter types followed by return type
128139
*/
129140
public Symbol<Type>[] parsed(Symbol<Signature> signature) {
130-
return parsedSignatures.computeIfAbsent(signature, key -> parse(SignatureSymbols.this.getTypes(), signature, 0));
141+
return parse(SignatureSymbols.this.getTypes(), signature, 0);
131142
}
132143

133144
/**
@@ -340,7 +351,7 @@ public static Symbol<Type>[] makeParsedUncached(Symbol<Type> returnType, Symbol<
340351

341352
@SafeVarargs
342353
public final Symbol<Signature> makeRaw(Symbol<Type> returnType, Symbol<Type>... parameterTypes) {
343-
return symbols.symbolify(createSignature(returnType, parameterTypes));
354+
return symbols.getOrCreate(createSignature(returnType, parameterTypes));
344355
}
345356

346357
@SafeVarargs

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/StaticSymbols.java

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
*/
2323
package com.oracle.truffle.espresso.classfile.descriptors;
2424

25+
import java.util.HashSet;
26+
import java.util.Set;
27+
28+
import org.graalvm.collections.EconomicMap;
29+
2530
/**
2631
* To be populated in static initializers, always before the first runtime symbol table is spawned.
2732
*
@@ -32,56 +37,56 @@
3237
public final class StaticSymbols {
3338

3439
private boolean frozen = false;
35-
private final Symbols symbols;
40+
private final EconomicMap<ByteSequence, Symbol<?>> symbols;
3641

37-
public StaticSymbols(StaticSymbols seed) {
38-
this.symbols = new Symbols(seed.freeze());
42+
public StaticSymbols(StaticSymbols seed, int initialCapacity) {
43+
this.symbols = EconomicMap.create(initialCapacity);
44+
this.symbols.putAll(seed.symbols);
3945
}
4046

41-
public StaticSymbols() {
42-
this.symbols = new Symbols();
47+
public StaticSymbols(int initialCapacity) {
48+
this.symbols = EconomicMap.create(initialCapacity);
4349
}
4450

4551
public Symbol<Name> putName(String nameString) {
4652
ErrorUtil.guarantee(!nameString.isEmpty(), "empty name");
4753
ByteSequence byteSequence = ByteSequence.create(nameString);
48-
Symbol<Name> name = symbols.lookup(byteSequence);
49-
if (name != null) {
50-
return name;
51-
}
52-
ErrorUtil.guarantee(!isFrozen(), "static symbols are frozen");
53-
return symbols.symbolify(byteSequence);
54+
return getOrCreateSymbol(byteSequence);
5455
}
5556

5657
public Symbol<Type> putType(String internalName) {
5758
ByteSequence byteSequence = ByteSequence.create(internalName);
5859
ErrorUtil.guarantee(Validation.validTypeDescriptor(byteSequence, true), "invalid type");
59-
Symbol<Type> type = symbols.lookup(byteSequence);
60-
if (type != null) {
61-
return type;
62-
}
63-
ErrorUtil.guarantee(!isFrozen(), "static symbols are frozen");
64-
return symbols.symbolify(byteSequence);
60+
return getOrCreateSymbol(byteSequence);
6561
}
6662

6763
@SafeVarargs
6864
public final Symbol<Signature> putSignature(Symbol<Type> returnType, Symbol<Type>... parameterTypes) {
6965
ByteSequence signatureBytes = SignatureSymbols.createSignature(returnType, parameterTypes);
7066
ErrorUtil.guarantee(Validation.validSignatureDescriptor(signatureBytes, false), "invalid signature");
71-
Symbol<Signature> signature = symbols.lookup(signatureBytes);
72-
if (signature != null) {
73-
return signature;
74-
}
75-
ErrorUtil.guarantee(!isFrozen(), "static symbols are frozen");
76-
return symbols.symbolify(signatureBytes);
67+
return getOrCreateSymbol(signatureBytes);
7768
}
7869

7970
public boolean isFrozen() {
8071
return frozen;
8172
}
8273

83-
public Symbols freeze() {
74+
public Set<Symbol<?>> freeze() {
8475
frozen = true;
85-
return symbols;
76+
Set<Symbol<?>> result = new HashSet<>(symbols.size());
77+
symbols.getValues().forEach(result::add);
78+
return result;
79+
}
80+
81+
@SuppressWarnings("unchecked")
82+
private <T> Symbol<T> getOrCreateSymbol(ByteSequence byteSequence) {
83+
Symbol<T> symbol = (Symbol<T>) symbols.get(byteSequence);
84+
if (symbol != null) {
85+
return symbol;
86+
}
87+
ErrorUtil.guarantee(!isFrozen(), "static symbols are frozen");
88+
symbol = Symbols.createSymbolInstanceUnsafe(byteSequence);
89+
symbols.put(symbol, symbol);
90+
return symbol;
8691
}
8792
}

0 commit comments

Comments
 (0)