Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Gary Gregory">Update ECJ versions and generate the SWAP fixture with BCEL (#537).</action>
<action type="fix" dev="ggregory" due-to="Mohd Irshad">Fix typo 'preceeding' in ConstantPool exception message (#541).</action>
<action type="fix" dev="ggregory" due-to="Gary Gregory">Clean up comments for spelling, grammar, and readability.</action>
<action issue="BCEL-341" type="fix" dev="ggregory" due-to="Gary Gregory, Hippo">Support explicit parsing and generation of compact Code headers in class file versions 45.0 through 45.2 (#58).</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary Gregory">Add and use ClassFormatException.ClassFormatException(String, Object...).</action>
<action type="add" dev="ggregory" due-to="Nicolas Baumann, Gary Gregory">Add support for the RuntimeVisibleTypeAnnotations and RuntimeInvisibleTypeAnnotations attributes.</action>
Expand Down
45 changes: 32 additions & 13 deletions src/main/java/org/apache/bcel/classfile/Attribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,39 @@ protected static void println(final String msg) {
}

/**
* Class method reads one attribute from the input data stream. This method must not be accessible from the outside. It
* is called by the Field and Method constructor methods.
* Reads one attribute using standard eight-byte Code headers.
*
* @see Field
* @see Method
* @param dataInput Input stream.
* @param constantPool Array of constants.
* @return Attribute.
* @throws IOException Thrown if an I/O error occurs.
* @since 6.0
* @see #readAttribute(DataInput, ConstantPool, boolean)
*/
public static Attribute readAttribute(final DataInput dataInput, final ConstantPool constantPool) throws IOException {
return readAttribute(dataInput, constantPool, false);
}

/**
* Reads one attribute using the selected Code header layout.
*
* <p>
* The compact layout uses {@code u1}, {@code u1}, and {@code u2} for {@code max_stack}, {@code max_locals}, and {@code code_length}, respectively. These
* fields occupy 4 bytes instead of the standard 8 bytes; the rest of the Code attribute layout is unchanged. This layout is intended for historical class
* files with version 45.0 through 45.2. This method has no enclosing class version to check, so the caller must select the layout. The selection also
* applies to Code attributes encountered while reading nested attributes.
* </p>
*
* @param dataInput Input stream.
* @param constantPool Constant pool.
* @param compactCodeHeader Whether to read compact four-byte Code headers ({@code true}) or standard eight-byte headers ({@code false}).
* @return The attribute.
* @throws IOException Thrown if an I/O error occurs.
* @throws ClassFormatException Thrown if the attribute is malformed or exceeds the nesting limit.
* @since 6.14.0
* @see Code#isCompactCodeHeader()
*/
public static Attribute readAttribute(final DataInput dataInput, final ConstantPool constantPool, final boolean compactCodeHeader) throws IOException {
// Track the nesting depth to guard against malicious class files that nest attributes (for example, a Code attribute inside a Code attribute, or
// mutually recursive Record component attributes) deeply enough to overflow the parser's stack (CWE-674).
final int depth = NESTING_DEPTH.get().intValue() + 1;
Expand All @@ -143,7 +164,7 @@ public static Attribute readAttribute(final DataInput dataInput, final ConstantP
}
NESTING_DEPTH.set(Integer.valueOf(depth));
try {
return readAttribute0(dataInput, constantPool);
return readAttribute0(dataInput, constantPool, compactCodeHeader);
} finally {
if (depth == 1) {
NESTING_DEPTH.remove();
Expand All @@ -154,24 +175,22 @@ public static Attribute readAttribute(final DataInput dataInput, final ConstantP
}

/**
* Class method reads one attribute from the input data stream. This method must not be accessible from the outside. It
* is called by the Field and Method constructor methods.
* Reads one attribute using standard eight-byte Code headers.
*
* @see Field
* @see Method
* @param dataInputStream Input stream.
* @param constantPool Array of constants.
* @return Attribute.
* @throws IOException Thrown if an I/O error occurs.
* @see #readAttribute(DataInput, ConstantPool, boolean)
*/
public static Attribute readAttribute(final DataInputStream dataInputStream, final ConstantPool constantPool) throws IOException {
return readAttribute((DataInput) dataInputStream, constantPool);
}

/**
* Reads one attribute without tracking the nesting depth; only to be called by {@link #readAttribute(DataInput, ConstantPool)}.
* Reads one attribute without tracking the nesting depth; only to be called by {@link #readAttribute(DataInput, ConstantPool, boolean)}.
*/
private static Attribute readAttribute0(final DataInput dataInput, final ConstantPool constantPool) throws IOException {
private static Attribute readAttribute0(final DataInput dataInput, final ConstantPool constantPool, final boolean compactCodeHeader) throws IOException {
byte tag = Const.ATTR_UNKNOWN; // Unknown attribute
// Get class name from constant pool via 'name_index' indirection
final int nameIndex = dataInput.readUnsignedShort();
Expand Down Expand Up @@ -201,7 +220,7 @@ private static Attribute readAttribute0(final DataInput dataInput, final Constan
case Const.ATTR_SOURCE_FILE:
return new SourceFile(nameIndex, length, dataInput, constantPool);
case Const.ATTR_CODE:
return new Code(nameIndex, length, dataInput, constantPool);
return new Code(nameIndex, length, dataInput, constantPool, compactCodeHeader);
case Const.ATTR_EXCEPTIONS:
return new ExceptionTable(nameIndex, length, dataInput, constantPool);
case Const.ATTR_LINE_NUMBER_TABLE:
Expand Down Expand Up @@ -256,7 +275,7 @@ private static Attribute readAttribute0(final DataInput dataInput, final Constan
case Const.ATTR_NEST_MEMBERS:
return new NestMembers(nameIndex, length, dataInput, constantPool);
case Const.ATTR_RECORD:
return new Record(nameIndex, length, dataInput, constantPool);
return new Record(nameIndex, length, dataInput, constantPool, compactCodeHeader);
case Const.ATTR_PERMITTED_SUBCLASSES:
return new PermittedSubclasses(nameIndex, length, dataInput, constantPool);
case Const.ATTR_RUNTIME_VISIBLE_TYPE_ANNOTATIONS:
Expand Down
51 changes: 48 additions & 3 deletions src/main/java/org/apache/bcel/classfile/ClassParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ static int[] readU2U2Table(final DataInput dataInput) throws IOException {
private int superclassNameIndex;
private int major; // Compiler version
private int minor; // Compiler version
// Actual Code layout selected after checking both the explicit option and the class file version.
private boolean compactCodeHeader;
// Versions 45.0 through 45.2 can also use standard Code headers, so the narrower layout requires an explicit option.
private boolean compactCodeHeaderEnabled;
private int accessFlags; // Access rights of parsed class
private int[] interfaces; // Names of implemented interfaces
private ConstantPool constantPool; // collection of constants
Expand Down Expand Up @@ -111,11 +115,27 @@ public ClassParser(final String zipFile, final String fileName) {
this.fileName = fileName;
}

/**
* Gets whether compact Code header parsing is enabled for class file versions 45.0 through 45.2.
*
* @return Whether compact Code header parsing is enabled.
* @since 6.14.0
* @see #setCompactCodeHeaderEnabled(boolean)
*/
public boolean isCompactCodeHeaderEnabled() {
return compactCodeHeaderEnabled;
}

/**
* Parses the given Java class file and return an object that represents the contained data, that is, constants, methods,
* fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
* not include verification of the byte code as it is performed by the Java interpreter).
*
* <p>
* Code attributes use the standard eight-byte header by default, including in class files with version 45.0 through 45.2.
* Use {@link #setCompactCodeHeaderEnabled(boolean)} to read the compact four-byte header used by historical files with those versions.
* </p>
*
* @return Class object representing the parsed class file.
* @throws IOException Thrown if an I/O error occurs.
* @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file.
Expand Down Expand Up @@ -192,7 +212,7 @@ private void readAttributes() throws IOException, ClassFormatException {
final int attributesCount = dataInputStream.readUnsignedShort();
attributes = new Attribute[attributesCount];
for (int i = 0; i < attributesCount; i++) {
attributes[i] = Attribute.readAttribute(dataInputStream, constantPool);
attributes[i] = Attribute.readAttribute(dataInputStream, constantPool, compactCodeHeader);
}
}

Expand Down Expand Up @@ -237,7 +257,7 @@ private void readFields() throws IOException, ClassFormatException {
final int fieldsCount = dataInputStream.readUnsignedShort();
fields = new Field[fieldsCount];
for (int i = 0; i < fieldsCount; i++) {
fields[i] = new Field(dataInputStream, constantPool);
fields[i] = new Field(dataInputStream, constantPool, compactCodeHeader);
}
}

Expand Down Expand Up @@ -273,7 +293,7 @@ private void readMethods() throws IOException {
final int methodsCount = dataInputStream.readUnsignedShort();
methods = new Method[methodsCount];
for (int i = 0; i < methodsCount; i++) {
methods[i] = new Method(dataInputStream, constantPool);
methods[i] = new Method(dataInputStream, constantPool, compactCodeHeader);
}
}

Expand All @@ -286,5 +306,30 @@ private void readMethods() throws IOException {
private void readVersion() throws IOException, ClassFormatException {
minor = dataInputStream.readUnsignedShort();
major = dataInputStream.readUnsignedShort();
// The option resolves the ambiguous layout for 45.0 through 45.2; versions 45.3 and later always use standard headers.
compactCodeHeader = compactCodeHeaderEnabled && major == Const.MAJOR_1_1 && minor < Const.MINOR_1_1;
}

/**
* Sets whether to read compact Code headers in class file versions 45.0 through 45.2.
*
* <p>
* "Compact" refers to the widths of {@code max_stack}, {@code max_locals}, and {@code code_length}: unsigned one-, one-, and two-byte fields,
* respectively, totaling 4 bytes. Standard headers use unsigned two-, two-, and four-byte fields, totaling 8 bytes. These sizes exclude the common
* six-byte attribute prefix. The layout of that prefix and the remaining Code fields is unchanged.
* </p>
*
* <p>
* This option is disabled by default because modern JVMs also accept versions 45.0 through 45.2 with standard headers. The version therefore
* cannot determine the layout by itself; enabling this option selects the historical compact layout. Other class file versions always use standard
* headers, regardless of this option. Set this option before calling {@link #parse()}.
* </p>
*
* @param enabled Whether to enable compact Code header parsing.
* @since 6.14.0
* @see Code#isCompactCodeHeader()
*/
public void setCompactCodeHeaderEnabled(final boolean enabled) {
compactCodeHeaderEnabled = enabled;
}
}
Loading
Loading