oracle/graal · error · ParserException.ClassFormatError
invalid type descriptor: {}
Error message
invalid type descriptor: {} What it means
skipValidTypeDescriptor verifies that a valid type descriptor starts at beginIndex. The first check is bounds: if beginIndex >= descriptor.length() there is nothing to parse, and it throws ParserException.ClassFormatError 'invalid type descriptor: <desc>'.
Source
Thrown at espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/TypeSymbols.java:217
return forPrimitive(JavaKind.fromPrimitiveOrVoidTypeChar((char) descriptor.byteAt(beginIndex)));
} catch (IllegalStateException e) {
throw new ParserException.ClassFormatError("invalid descriptor: " + descriptor);
}
}
return symbols.getOrCreate(descriptor.subSequence(beginIndex, endIndex));
}
/**
* Verifies that a valid type descriptor is at {@code beginIndex} in {@code type}.
*
* @param slashes specifies if package components are separated by {@code '/'} or {@code '.'}
* @return the index one past the valid type descriptor starting at {@code beginIndex}
* @throws ParserException.ClassFormatError if there is no valid type descriptor
*/
@TruffleBoundary
static int skipValidTypeDescriptor(Symbol<? extends Descriptor> descriptor, int beginIndex, boolean slashes) throws ParserException.ClassFormatError {
if (beginIndex >= descriptor.length()) {
throw new ParserException.ClassFormatError("invalid type descriptor: " + descriptor);
}
char ch = (char) descriptor.byteAt(beginIndex);
if (ch != '[' && ch != 'L') {
return beginIndex + 1;
}
switch (ch) {
case 'L': {
final int endIndex = skipClassName(descriptor, beginIndex + 1, slashes ? '/' : '.');
if (endIndex > beginIndex + 1 && endIndex < descriptor.length() && descriptor.byteAt(endIndex) == ';') {
return endIndex + 1;
}
throw new ParserException.ClassFormatError("Invalid Java name " + descriptor.subSequence(beginIndex));
}
case '[': {
// compute the number of dimensions
int index = beginIndex;
while (index < descriptor.length() && descriptor.byteAt(index) == '[') {
index++;View on GitHub (pinned to a66e9ccd1d)
Solutions
- Always bound-check beginIndex < descriptor.length() before each parse/skip step in your own loop
- Fix the driver logic that produced the out-of-range index (usually the preceding descriptor's length was wrong)
- Pre-validate full signatures with a complete parser rather than manual index arithmetic
Example fix
// before
int i = ...; // may equal descriptor.length()
TypeSymbols.skipValidTypeDescriptor(desc, i, true);
// after
if (i >= desc.length()) throw new ClassFormatException("signature ended unexpectedly");
TypeSymbols.skipValidTypeDescriptor(desc, i, true); Defensive patterns
Strategy: validation
Validate before calling
if (beginIndex < 0 || beginIndex >= descriptor.length()) throw new IllegalArgumentException("beginIndex out of range: " + beginIndex); Try / catch
catch (ParserException.ClassFormatError e) { dump descriptor plus index to pinpoint the scanning bug } Prevention
- Re-check bounds after every index advance in descriptor-walking loops
- Prefer whole-signature parsers over manual index arithmetic
When it happens
Trigger: Calling parse/skip with a beginIndex equal to or beyond the descriptor length — typically a scanner that already consumed the string (e.g. after ')' in a malformed signature) or an off-by-one loop bound.
Common situations: Custom signature-walking loops that advance past the end before re-checking; empty descriptor strings from failed lookups; sliced substrings that dropped the last character.
Related errors
- unknown primitive or void type character:
- invalid descriptor: {}
- Invalid Java name {}
- Array with more than 255 dimensions {}
- Invalid type descriptor {}
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/265e00c26334b57a.
Report an issue: GitHub.