diff --git a/src/main/java/org/apache/bcel/classfile/Attribute.java b/src/main/java/org/apache/bcel/classfile/Attribute.java index 2cb0434ff6..84e5cf6171 100644 --- a/src/main/java/org/apache/bcel/classfile/Attribute.java +++ b/src/main/java/org/apache/bcel/classfile/Attribute.java @@ -133,6 +133,13 @@ protected static void println(final String msg) { * @since 6.0 */ public static Attribute readAttribute(final DataInput dataInput, final ConstantPool constantPool) throws IOException { + return readAttribute(dataInput, constantPool, false); + } + + /** + * Reads an attribute using the Code encoding selected by the enclosing class file's version. + */ + static Attribute readAttribute(final DataInput dataInput, final ConstantPool constantPool, final boolean legacyCodeFormat) 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; @@ -143,7 +150,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, legacyCodeFormat); } finally { if (depth == 1) { NESTING_DEPTH.remove(); @@ -169,9 +176,9 @@ public static Attribute readAttribute(final DataInputStream dataInputStream, fin } /** - * 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 legacyCodeFormat) throws IOException { byte tag = Const.ATTR_UNKNOWN; // Unknown attribute // Get class name from constant pool via 'name_index' indirection final int nameIndex = dataInput.readUnsignedShort(); @@ -201,7 +208,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, legacyCodeFormat); case Const.ATTR_EXCEPTIONS: return new ExceptionTable(nameIndex, length, dataInput, constantPool); case Const.ATTR_LINE_NUMBER_TABLE: @@ -256,7 +263,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, legacyCodeFormat); case Const.ATTR_PERMITTED_SUBCLASSES: return new PermittedSubclasses(nameIndex, length, dataInput, constantPool); case Const.ATTR_RUNTIME_VISIBLE_TYPE_ANNOTATIONS: diff --git a/src/main/java/org/apache/bcel/classfile/ClassParser.java b/src/main/java/org/apache/bcel/classfile/ClassParser.java index a5c8eece24..5a48ea8870 100644 --- a/src/main/java/org/apache/bcel/classfile/ClassParser.java +++ b/src/main/java/org/apache/bcel/classfile/ClassParser.java @@ -60,6 +60,7 @@ static int[] readU2U2Table(final DataInput dataInput) throws IOException { private int superclassNameIndex; private int major; // Compiler version private int minor; // Compiler version + private boolean legacyCodeFormat; // True if class file version is <=45.2 private int accessFlags; // Access rights of parsed class private int[] interfaces; // Names of implemented interfaces private ConstantPool constantPool; // collection of constants @@ -192,7 +193,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, legacyCodeFormat); } } @@ -237,7 +238,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, legacyCodeFormat); } } @@ -273,7 +274,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, legacyCodeFormat); } } @@ -286,5 +287,6 @@ private void readMethods() throws IOException { private void readVersion() throws IOException, ClassFormatException { minor = dataInputStream.readUnsignedShort(); major = dataInputStream.readUnsignedShort(); + legacyCodeFormat = major < Const.MAJOR_1_1 || major == Const.MAJOR_1_1 && minor < Const.MINOR_1_1; } } diff --git a/src/main/java/org/apache/bcel/classfile/Code.java b/src/main/java/org/apache/bcel/classfile/Code.java index 7b972c11ee..b88eb661f1 100644 --- a/src/main/java/org/apache/bcel/classfile/Code.java +++ b/src/main/java/org/apache/bcel/classfile/Code.java @@ -63,6 +63,10 @@ */ public final class Code extends Attribute { + /** + * Whether the Code header uses the smaller fields of class file versions up to 45.2. + */ + private final boolean legacyCodeFormat; private int maxStack; // Maximum size of stack used by this method // TODO this could be made final (setter is not used) private int maxLocals; // Number of local variables // TODO this could be made final (setter is not used) private byte[] code; // Actual byte code @@ -77,7 +81,7 @@ public final class Code extends Attribute { */ public Code(final Code code) { this(code.getNameIndex(), code.getLength(), code.getMaxStack(), code.getMaxLocals(), code.getCode(), code.getExceptionTable(), code.getAttributes(), - code.getConstantPool()); + code.getConstantPool(), code.legacyCodeFormat); } /** @@ -90,10 +94,18 @@ public Code(final Code code) { * @throws ClassFormatException Thrown if the code array read from {@code file} is greater than {@link Const#MAX_CODE_SIZE}. */ Code(final int nameIndex, final int length, final DataInput dataInput, final ConstantPool constantPool) throws IOException { + this(nameIndex, length, dataInput, constantPool, false); + } + + /** + * Reads a Code attribute using the header widths selected by the enclosing class file's version. + */ + Code(final int nameIndex, final int length, final DataInput dataInput, final ConstantPool constantPool, final boolean legacyCodeFormat) throws IOException { // Initialize with some default values which will be overwritten later - this(nameIndex, length, dataInput.readUnsignedShort(), dataInput.readUnsignedShort(), (byte[]) null, (CodeException[]) null, (Attribute[]) null, - constantPool); - final int codeLength = Args.requireU4(dataInput.readInt(), 0, Const.MAX_CODE_SIZE, "Code length attribute"); + this(nameIndex, length, legacyCodeFormat ? dataInput.readUnsignedByte() : dataInput.readUnsignedShort(), + legacyCodeFormat ? dataInput.readUnsignedByte() : dataInput.readUnsignedShort(), null, null, null, constantPool, legacyCodeFormat); + final int codeLength = Args.requireU4(legacyCodeFormat ? dataInput.readUnsignedShort() : dataInput.readInt(), 0, Const.MAX_CODE_SIZE, + "Code length attribute"); code = new byte[codeLength]; // Read byte code dataInput.readFully(code); /* @@ -110,7 +122,7 @@ public Code(final Code code) { final int attributesCount = dataInput.readUnsignedShort(); attributes = new Attribute[attributesCount]; for (int i = 0; i < attributesCount; i++) { - attributes[i] = readAttribute(dataInput, constantPool); + attributes[i] = readAttribute(dataInput, constantPool, legacyCodeFormat); } /* * Adjust length, because of setAttributes in this(), s.b. length is incorrect, because it didn't take the internal attributes into account yet! Very @@ -134,7 +146,13 @@ public Code(final Code code) { */ public Code(final int nameIndex, final int length, final int maxStack, final int maxLocals, final byte[] code, final CodeException[] exceptionTable, final Attribute[] attributes, final ConstantPool constantPool) { + this(nameIndex, length, maxStack, maxLocals, code, exceptionTable, attributes, constantPool, false); + } + + private Code(final int nameIndex, final int length, final int maxStack, final int maxLocals, final byte[] code, final CodeException[] exceptionTable, + final Attribute[] attributes, final ConstantPool constantPool, final boolean legacyCodeFormat) { super(Const.ATTR_CODE, nameIndex, length, constantPool); + this.legacyCodeFormat = legacyCodeFormat; this.maxStack = Args.requireU2(maxStack, "maxStack"); this.maxLocals = Args.requireU2(maxLocals, "maxLocals"); this.code = ArrayUtils.nullToEmpty(code); @@ -197,10 +215,21 @@ public Attribute copy(final ConstantPool constantPool) { */ @Override public void dump(final DataOutputStream file) throws IOException { + if (legacyCodeFormat) { + Args.requireU1(maxStack, "maxStack"); + Args.requireU1(maxLocals, "maxLocals"); + Args.requireU2(code.length, "Code length attribute"); + } super.dump(file); - file.writeShort(maxStack); - file.writeShort(maxLocals); - file.writeInt(code.length); + if (legacyCodeFormat) { + file.writeByte(maxStack); + file.writeByte(maxLocals); + file.writeShort(code.length); + } else { + file.writeShort(maxStack); + file.writeShort(maxLocals); + file.writeInt(code.length); + } file.write(code, 0, code.length); file.writeShort(Args.requireU2(exceptionTable.length, "exceptionTable.length")); for (final CodeException exception : exceptionTable) { @@ -247,7 +276,7 @@ public CodeException[] getExceptionTable() { * @return The internal length of this code attribute (minus the first 6 bytes) and excluding all its attributes. */ private int getInternalLength() { - return 2 /* maxStack */ + 2 /* maxLocals */ + 4 /* code length */ + return (legacyCodeFormat ? 4 : 8) /* maxStack, maxLocals, code length */ + code.length /* byte-code */ + 2 /* exception-table length */ + 8 * (exceptionTable == null ? 0 : exceptionTable.length) /* exception table */ diff --git a/src/main/java/org/apache/bcel/classfile/Field.java b/src/main/java/org/apache/bcel/classfile/Field.java index fca1bd8add..a3846a4a0b 100644 --- a/src/main/java/org/apache/bcel/classfile/Field.java +++ b/src/main/java/org/apache/bcel/classfile/Field.java @@ -77,7 +77,11 @@ public static void setComparator(final BCELComparator comparator) { * @param constantPool The constant pool. */ Field(final DataInput file, final ConstantPool constantPool) throws IOException, ClassFormatException { - super(file, constantPool); + this(file, constantPool, false); + } + + Field(final DataInput file, final ConstantPool constantPool, final boolean legacyCodeFormat) throws IOException { + super(file, constantPool, legacyCodeFormat); } /** diff --git a/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java b/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java index 77cbaa776f..895a647f41 100644 --- a/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java +++ b/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java @@ -78,11 +78,15 @@ public abstract class FieldOrMethod extends AccessFlags implements Cloneable, No * @throws IOException Thrown if an I/O error occurs. */ protected FieldOrMethod(final DataInput file, final ConstantPool constantPool) throws IOException { + this(file, constantPool, false); + } + + FieldOrMethod(final DataInput file, final ConstantPool constantPool, final boolean legacyCodeFormat) throws IOException { this(file.readUnsignedShort(), file.readUnsignedShort(), file.readUnsignedShort(), null, constantPool); final int attributesCount = file.readUnsignedShort(); attributes = new Attribute[attributesCount]; for (int i = 0; i < attributesCount; i++) { - attributes[i] = Attribute.readAttribute(file, constantPool); + attributes[i] = Attribute.readAttribute(file, constantPool, legacyCodeFormat); } this.attributes_count = attributesCount; // init deprecated field } diff --git a/src/main/java/org/apache/bcel/classfile/Method.java b/src/main/java/org/apache/bcel/classfile/Method.java index 235a320409..76566acfab 100644 --- a/src/main/java/org/apache/bcel/classfile/Method.java +++ b/src/main/java/org/apache/bcel/classfile/Method.java @@ -86,7 +86,11 @@ public Method() { * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. */ Method(final DataInput file, final ConstantPool constantPool) throws IOException, ClassFormatException { - super(file, constantPool); + this(file, constantPool, false); + } + + Method(final DataInput file, final ConstantPool constantPool, final boolean legacyCodeFormat) throws IOException { + super(file, constantPool, legacyCodeFormat); } /** diff --git a/src/main/java/org/apache/bcel/classfile/Record.java b/src/main/java/org/apache/bcel/classfile/Record.java index 327dee1bef..c51a3e14ac 100644 --- a/src/main/java/org/apache/bcel/classfile/Record.java +++ b/src/main/java/org/apache/bcel/classfile/Record.java @@ -39,12 +39,12 @@ public final class Record extends Attribute { private static final RecordComponentInfo[] EMPTY_RCI_ARRAY = {}; - private static RecordComponentInfo[] readComponents(final DataInput input, final ConstantPool constantPool) + private static RecordComponentInfo[] readComponents(final DataInput input, final ConstantPool constantPool, final boolean legacyCodeFormat) throws IOException { final int classCount = input.readUnsignedShort(); final RecordComponentInfo[] components = new RecordComponentInfo[classCount]; for (int i = 0; i < classCount; i++) { - components[i] = new RecordComponentInfo(input, constantPool); + components[i] = new RecordComponentInfo(input, constantPool, legacyCodeFormat); } return components; } @@ -62,7 +62,12 @@ private static RecordComponentInfo[] readComponents(final DataInput input, final */ Record(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException { - this(nameIndex, length, readComponents(input, constantPool), constantPool); + this(nameIndex, length, input, constantPool, false); + } + + Record(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool, final boolean legacyCodeFormat) + throws IOException { + this(nameIndex, length, readComponents(input, constantPool, legacyCodeFormat), constantPool); } /** diff --git a/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java b/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java index e32e1ac70b..06b8f4f11b 100644 --- a/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java +++ b/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java @@ -48,12 +48,16 @@ public class RecordComponentInfo implements Node { * @throws IOException Thrown if an I/O error occurs. */ public RecordComponentInfo(final DataInput input, final ConstantPool constantPool) throws IOException { + this(input, constantPool, false); + } + + RecordComponentInfo(final DataInput input, final ConstantPool constantPool, final boolean legacyCodeFormat) throws IOException { this.index = input.readUnsignedShort(); this.descriptorIndex = input.readUnsignedShort(); final int attributesCount = input.readUnsignedShort(); this.attributes = new Attribute[attributesCount]; for (int j = 0; j < attributesCount; j++) { - attributes[j] = Attribute.readAttribute(input, constantPool); + attributes[j] = Attribute.readAttribute(input, constantPool, legacyCodeFormat); } this.constantPool = constantPool; } diff --git a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java b/src/test/java/org/apache/bcel/classfile/ClassParserTest.java index f16d0c91f1..1eb009ba0a 100644 --- a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java +++ b/src/test/java/org/apache/bcel/classfile/ClassParserTest.java @@ -19,8 +19,11 @@ package org.apache.bcel.classfile; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.IOException; + import org.junit.jupiter.api.Test; /** @@ -32,4 +35,9 @@ class ClassParserTest { void testParseInvalidClass() { assertThrows(ClassFormatException.class, () -> new ClassParser("src/test/resources/org/apache/commons/bcel/classfile/CodeCtor.class").parse()); } + + @Test + void testParseOakClassFile() throws IOException { + assertNotNull(new ClassParser("src/test/resources/Java1OakExample.class").parse()); + } } diff --git a/src/test/resources/Java1OakExample.class b/src/test/resources/Java1OakExample.class new file mode 100644 index 0000000000..2a6b756275 Binary files /dev/null and b/src/test/resources/Java1OakExample.class differ diff --git a/src/test/resources/Java1OakExample.java b/src/test/resources/Java1OakExample.java new file mode 100644 index 0000000000..f10f107c77 --- /dev/null +++ b/src/test/resources/Java1OakExample.java @@ -0,0 +1,5 @@ +public class Java1OakExample { + public static void main(String args[]) { + System.out.println("Hello Oak!"); + } +}