From dcad6633da62241402f06077e26f7f7742077759 Mon Sep 17 00:00:00 2001 From: Hippo Date: Sun, 9 Aug 2020 02:39:10 -0500 Subject: [PATCH 1/8] BCEL-341: Oak class file patch --- pom.xml | 5 ++ .../org/apache/bcel/classfile/Attribute.java | 25 +++++++++- .../apache/bcel/classfile/ClassParser.java | 4 +- .../java/org/apache/bcel/classfile/Code.java | 50 ++++++++++++++++--- .../apache/bcel/classfile/FieldOrMethod.java | 13 ++++- .../org/apache/bcel/classfile/Method.java | 14 +++++- 6 files changed, 101 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 7f25bd83f8..b6871e1dd1 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,11 @@ Sam Yoon + + + Hippo + hippah at protonmail.com + diff --git a/src/main/java/org/apache/bcel/classfile/Attribute.java b/src/main/java/org/apache/bcel/classfile/Attribute.java index 72e5c4e897..feac9d5e4e 100644 --- a/src/main/java/org/apache/bcel/classfile/Attribute.java +++ b/src/main/java/org/apache/bcel/classfile/Attribute.java @@ -86,6 +86,7 @@ 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 @@ -103,6 +104,28 @@ protected static void println(final String msg) { */ public static Attribute readAttribute(final DataInput file, final ConstantPool constant_pool) throws IOException, ClassFormatException + { + return readAttribute(file, constant_pool, false); + } + + /** + * 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. + * + * @see Field + * @see Method + * + * @param file Input stream + * @param constant_pool Array of constants + * @param isOak If the the class file is oak + * @return Attribute + * @throws IOException + * @throws ClassFormatException + * @since 6.0 + */ + public static Attribute readAttribute(final DataInput file, final ConstantPool constant_pool, final boolean isOak) + throws IOException, ClassFormatException { byte tag = Const.ATTR_UNKNOWN; // Unknown attribute // Get class name from constant pool via `name_index' indirection @@ -138,7 +161,7 @@ public static Attribute readAttribute(final DataInput file, final ConstantPool c case Const.ATTR_SOURCE_FILE: return new SourceFile(name_index, length, file, constant_pool); case Const.ATTR_CODE: - return new Code(name_index, length, file, constant_pool); + return new Code(name_index, length, file, constant_pool, isOak); case Const.ATTR_EXCEPTIONS: return new ExceptionTable(name_index, length, file, constant_pool); case Const.ATTR_LINE_NUMBER_TABLE: diff --git a/src/main/java/org/apache/bcel/classfile/ClassParser.java b/src/main/java/org/apache/bcel/classfile/ClassParser.java index a592bfed06..79a3b29174 100644 --- a/src/main/java/org/apache/bcel/classfile/ClassParser.java +++ b/src/main/java/org/apache/bcel/classfile/ClassParser.java @@ -58,6 +58,7 @@ public final class ClassParser { private Attribute[] attributes; // attributes defined in the class private final boolean isZip; // Loaded from zip file private static final int BUFSIZE = 8192; + private boolean isOak; // Is oak class file /** @@ -290,7 +291,7 @@ private void readMethods() throws IOException, ClassFormatException { final int methods_count = dataInputStream.readUnsignedShort(); methods = new Method[methods_count]; for (int i = 0; i < methods_count; i++) { - methods[i] = new Method(dataInputStream, constantPool); + methods[i] = new Method(dataInputStream, constantPool, isOak); } } @@ -303,5 +304,6 @@ private void readMethods() throws IOException, ClassFormatException { private void readVersion() throws IOException, ClassFormatException { minor = dataInputStream.readUnsignedShort(); major = dataInputStream.readUnsignedShort(); + isOak = major < Const.MAJOR_1_1 || (major == Const.MAJOR_1_1 && minor < 3); } } diff --git a/src/main/java/org/apache/bcel/classfile/Code.java b/src/main/java/org/apache/bcel/classfile/Code.java index 5e81d21f3f..224c6990af 100644 --- a/src/main/java/org/apache/bcel/classfile/Code.java +++ b/src/main/java/org/apache/bcel/classfile/Code.java @@ -47,6 +47,7 @@ public final class Code extends Attribute { private byte[] code; // Actual byte code private CodeException[] exceptionTable; // Table of handled exceptions private Attribute[] attributes; // or LocalVariable + private boolean isOak; // If the class file follows oak format /** @@ -59,6 +60,7 @@ public Code(final Code c) { } + /** * @param name_index Index pointing to the name Code * @param length Content length in bytes @@ -67,10 +69,23 @@ public Code(final Code c) { */ Code(final int name_index, final int length, final DataInput file, final ConstantPool constant_pool) throws IOException { + this(name_index, length, file, constant_pool, false); + } + + /** + * @param name_index Index pointing to the name Code + * @param length Content length in bytes + * @param file Input stream + * @param constant_pool Array of constants + * @param isOak If the class file is oak + */ + Code(final int name_index, final int length, final DataInput file, final ConstantPool constant_pool, final boolean isOak) + throws IOException { // Initialize with some default values which will be overwritten later - this(name_index, length, file.readUnsignedShort(), file.readUnsignedShort(), (byte[]) null, + this(name_index, length, isOak ? file.readUnsignedByte() : file.readUnsignedShort(), isOak ? file.readUnsignedByte() : file.readUnsignedShort(), (byte[]) null, (CodeException[]) null, (Attribute[]) null, constant_pool); - final int code_length = file.readInt(); + this.isOak = isOak; + final int code_length = isOak ? file.readUnsignedShort() : file.readInt(); code = new byte[code_length]; // Read byte code file.readFully(code); /* Read exception table that contains all regions where an exception @@ -108,13 +123,30 @@ public Code(final Code c) { * @param constant_pool Array of constants */ public Code(final int name_index, final int length, final int maxStack, final int maxLocals, final byte[] code, - final CodeException[] exceptionTable, final Attribute[] attributes, final ConstantPool constant_pool) { + final CodeException[] exceptionTable, final Attribute[] attributes, final ConstantPool constant_pool) { + this(name_index, length, maxStack, maxLocals, code, exceptionTable, attributes, constant_pool, false); + } + + /** + * @param name_index Index pointing to the name Code + * @param length Content length in bytes + * @param maxStack Maximum size of stack + * @param maxLocals Number of local variables + * @param code Actual byte code + * @param exceptionTable of handled exceptions + * @param attributes Attributes of code: LineNumber or LocalVariable + * @param constant_pool Array of constants + * @param isOak If the class file follows the oak format + */ + public Code(final int name_index, final int length, final int maxStack, final int maxLocals, final byte[] code, + final CodeException[] exceptionTable, final Attribute[] attributes, final ConstantPool constant_pool, final boolean isOak) { super(Const.ATTR_CODE, name_index, length, constant_pool); this.maxStack = maxStack; this.maxLocals = maxLocals; this.code = code != null ? code : new byte[0]; this.exceptionTable = exceptionTable != null ? exceptionTable : new CodeException[0]; this.attributes = attributes != null ? attributes : new Attribute[0]; + this.isOak = isOak; super.setLength(calculateLength()); // Adjust length } @@ -141,9 +173,15 @@ public void accept( final Visitor v ) { @Override public void dump( final DataOutputStream file ) throws IOException { super.dump(file); - file.writeShort(maxStack); - file.writeShort(maxLocals); - file.writeInt(code.length); + if (isOak) { + 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(exceptionTable.length); for (final CodeException exception : exceptionTable) { diff --git a/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java b/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java index 6b70942185..8ed8e82d67 100644 --- a/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java +++ b/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java @@ -100,12 +100,23 @@ protected FieldOrMethod(final DataInputStream file, final ConstantPool constant_ * @throws ClassFormatException */ protected FieldOrMethod(final DataInput file, final ConstantPool constant_pool) throws IOException, ClassFormatException { + this(file, constant_pool, false); + } + + /** + * Construct object from file stream. + * @param file Input stream + * @param isOak If the class file is oak + * @throws IOException + * @throws ClassFormatException + */ + protected FieldOrMethod(final DataInput file, final ConstantPool constant_pool, final boolean isOak) throws IOException, ClassFormatException { this(file.readUnsignedShort(), file.readUnsignedShort(), file.readUnsignedShort(), null, constant_pool); final int attributes_count = file.readUnsignedShort(); attributes = new Attribute[attributes_count]; for (int i = 0; i < attributes_count; i++) { - attributes[i] = Attribute.readAttribute(file, constant_pool); + attributes[i] = Attribute.readAttribute(file, constant_pool, isOak); } this.attributes_count = attributes_count; // 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 8f9b8d6b5a..4a94d92af4 100644 --- a/src/main/java/org/apache/bcel/classfile/Method.java +++ b/src/main/java/org/apache/bcel/classfile/Method.java @@ -79,7 +79,19 @@ public Method(final Method c) { */ Method(final DataInput file, final ConstantPool constant_pool) throws IOException, ClassFormatException { - super(file, constant_pool); + this(file, constant_pool, false); + } + + /** + * Construct object from file stream. + * @param file Input stream + * @param isOak If the class file is oak + * @throws IOException + * @throws ClassFormatException + */ + Method(final DataInput file, final ConstantPool constant_pool, final boolean isOak) throws IOException, + ClassFormatException { + super(file, constant_pool, isOak); } From a049c5d89e938c9b3f0dcffd28d6c40936c871e5 Mon Sep 17 00:00:00 2001 From: Hippo Date: Mon, 10 Aug 2020 16:44:17 -0500 Subject: [PATCH 2/8] Checkstyle error fix --- src/main/java/org/apache/bcel/classfile/Code.java | 4 ++-- src/test/java/org/apache/bcel/BCELBenchmark.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/bcel/classfile/Code.java b/src/main/java/org/apache/bcel/classfile/Code.java index 224c6990af..c7504d5d8d 100644 --- a/src/main/java/org/apache/bcel/classfile/Code.java +++ b/src/main/java/org/apache/bcel/classfile/Code.java @@ -82,8 +82,8 @@ public Code(final Code c) { Code(final int name_index, final int length, final DataInput file, final ConstantPool constant_pool, final boolean isOak) throws IOException { // Initialize with some default values which will be overwritten later - this(name_index, length, isOak ? file.readUnsignedByte() : file.readUnsignedShort(), isOak ? file.readUnsignedByte() : file.readUnsignedShort(), (byte[]) null, - (CodeException[]) null, (Attribute[]) null, constant_pool); + this(name_index, length, isOak ? file.readUnsignedByte() : file.readUnsignedShort(), isOak ? file.readUnsignedByte() : file.readUnsignedShort(), + (byte[]) null, (CodeException[]) null, (Attribute[]) null, constant_pool); this.isOak = isOak; final int code_length = isOak ? file.readUnsignedShort() : file.readInt(); code = new byte[code_length]; // Read byte code diff --git a/src/test/java/org/apache/bcel/BCELBenchmark.java b/src/test/java/org/apache/bcel/BCELBenchmark.java index ad48cee6a3..8538209dc5 100644 --- a/src/test/java/org/apache/bcel/BCELBenchmark.java +++ b/src/test/java/org/apache/bcel/BCELBenchmark.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.bcel; +/*package org.apache.bcel; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -69,6 +69,7 @@ public boolean evaluate(JarEntry entry) { /** * Baseline benchmark. Read the classes but don't parse them. */ +/* @Benchmark public void baseline(Blackhole bh) throws IOException { JarFile jar = getJarFile(); @@ -123,4 +124,4 @@ public void generator(Blackhole bh) throws IOException { jar.close(); } -} +}*/ From 089d0ae6c1b5c55f04a81ac3af366582d62eae4e Mon Sep 17 00:00:00 2001 From: Hippo Date: Mon, 10 Aug 2020 16:44:47 -0500 Subject: [PATCH 3/8] Checkstyle error fix --- src/test/java/org/apache/bcel/BCELBenchmark.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/apache/bcel/BCELBenchmark.java b/src/test/java/org/apache/bcel/BCELBenchmark.java index 8538209dc5..ad48cee6a3 100644 --- a/src/test/java/org/apache/bcel/BCELBenchmark.java +++ b/src/test/java/org/apache/bcel/BCELBenchmark.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/*package org.apache.bcel; +package org.apache.bcel; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -69,7 +69,6 @@ public boolean evaluate(JarEntry entry) { /** * Baseline benchmark. Read the classes but don't parse them. */ -/* @Benchmark public void baseline(Blackhole bh) throws IOException { JarFile jar = getJarFile(); @@ -124,4 +123,4 @@ public void generator(Blackhole bh) throws IOException { jar.close(); } -}*/ +} From b40402d2cecaeb4c62c000d16bab0efaf84848a3 Mon Sep 17 00:00:00 2001 From: Hippo Date: Mon, 10 Aug 2020 22:55:03 -0500 Subject: [PATCH 4/8] Fixed calculation error on Code#getInternalLength() --- src/main/java/org/apache/bcel/classfile/Code.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/bcel/classfile/Code.java b/src/main/java/org/apache/bcel/classfile/Code.java index c7504d5d8d..ad968e4f53 100644 --- a/src/main/java/org/apache/bcel/classfile/Code.java +++ b/src/main/java/org/apache/bcel/classfile/Code.java @@ -267,7 +267,19 @@ public int getMaxStack() { * and excluding all its attributes */ private int getInternalLength() { - return 2 /*maxStack*/+ 2 /*maxLocals*/+ 4 /*code length*/ + final int maxStack; + final int maxLocals; + final int codeLength; + if (isOak) { + maxStack = 1; + maxLocals = 1; + codeLength = 2; + } else { + maxStack = 2; + maxLocals = 2; + codeLength = 4; + } + return maxStack + maxLocals + codeLength + code.length /*byte-code*/ + 2 /*exception-table length*/ + 8 * (exceptionTable == null ? 0 : exceptionTable.length) /* exception table */ From 60ae991c5d18ab0c9a5c3c6c8de34fbef372ec48 Mon Sep 17 00:00:00 2001 From: Hippo Date: Sat, 31 Oct 2026 17:23:36 -0500 Subject: [PATCH 5/8] Added test case for parsing Oak files --- .../apache/bcel/classfile/ClassParserTest.java | 8 ++++++++ src/test/resources/Java1OakExample.class | Bin 0 -> 477 bytes src/test/resources/Java1OakExample.java | 5 +++++ 3 files changed, 13 insertions(+) create mode 100644 src/test/resources/Java1OakExample.class create mode 100644 src/test/resources/Java1OakExample.java diff --git a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java b/src/test/java/org/apache/bcel/classfile/ClassParserTest.java index f16d0c91f1..785a236571 100644 --- a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java +++ b/src/test/java/org/apache/bcel/classfile/ClassParserTest.java @@ -20,9 +20,12 @@ package org.apache.bcel.classfile; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; +import java.io.IOException; + /** * Tests {@link ClassParser}. */ @@ -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 0000000000000000000000000000000000000000..2a6b7562754b60991c356ac895598e8aae0ac40d GIT binary patch literal 477 zcmZvZ%TB^T6o&sPm(p56QBb_IF<_#^tr#~%V+;`!Oc&bCfD;&OJ56bcK9(*tap42_ zP{va=AsA;dC+9!kxy^ihzP&@kKDKix=qT#g(2>`%Vju;>L>i`v4CXA9ux`OZ*}@vO z81x{Ne&qTL^DX6%Bko5wcVoe@G~yH9kg9Q&5Zx#g++#4$h3l%F4!^H56xz}km$BCq z;Wh8Mlw@{Q9QMVTOq{|6RX8N{^nrUpqU77E&t02`GQr0T>6W@9J*DD^p;T*s_jI{G zXi!7x51MtGVPUFvdLz*%H0^OoGnZ?(|EFY+r7xoss`(S2U86Zuv->!X1g*AvbL0$_ zC!C=S0_BV*p7UpcgJckzwpO>n=(#gUjs^p(sF1QuUR48(XJm`)1^N>S)QfJDFwkG* c$g$!ggA0@-|P;Q#;t literal 0 HcmV?d00001 diff --git a/src/test/resources/Java1OakExample.java b/src/test/resources/Java1OakExample.java new file mode 100644 index 0000000000..21206d0832 --- /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!"); + } +} \ No newline at end of file From ea802f5cadc4d0a1abdcf77ec31372bfa3deaac8 Mon Sep 17 00:00:00 2001 From: Hippo Date: Sat, 31 Oct 2026 18:05:27 -0500 Subject: [PATCH 6/8] Added Oak class file patch --- .../org/apache/bcel/classfile/Attribute.java | 17 +++++-- .../apache/bcel/classfile/ClassParser.java | 8 ++-- .../java/org/apache/bcel/classfile/Code.java | 47 +++++++++++++++---- .../java/org/apache/bcel/classfile/Field.java | 6 ++- .../apache/bcel/classfile/FieldOrMethod.java | 6 ++- .../org/apache/bcel/classfile/Method.java | 6 ++- .../org/apache/bcel/classfile/Record.java | 11 +++-- .../bcel/classfile/RecordComponentInfo.java | 6 ++- .../bcel/classfile/ClassParserTest.java | 6 +-- 9 files changed, 86 insertions(+), 27 deletions(-) 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..b8415a32fc 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 785a236571..1eb009ba0a 100644 --- a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java +++ b/src/test/java/org/apache/bcel/classfile/ClassParserTest.java @@ -19,13 +19,13 @@ package org.apache.bcel.classfile; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import org.junit.jupiter.api.Test; + /** * Tests {@link ClassParser}. */ From 641bf0f70a5a5a1cff4ec3602b337e9eb59038c6 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Sun, 1 Nov 2026 07:15:50 -0500 Subject: [PATCH 7/8] Fix missing newline in Java1OakExample.java Add missing newline at the end of the file. --- src/test/resources/Java1OakExample.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/Java1OakExample.java b/src/test/resources/Java1OakExample.java index 21206d0832..f10f107c77 100644 --- a/src/test/resources/Java1OakExample.java +++ b/src/test/resources/Java1OakExample.java @@ -2,4 +2,4 @@ public class Java1OakExample { public static void main(String args[]) { System.out.println("Hello Oak!"); } -} \ No newline at end of file +} From 4244c6e3ed8e5c6b8487900ea73ef29e7031e3e6 Mon Sep 17 00:00:00 2001 From: Hippo Date: Sun, 1 Nov 2026 13:40:58 -0600 Subject: [PATCH 8/8] Checkstyle fix --- src/main/java/org/apache/bcel/classfile/ClassParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/bcel/classfile/ClassParser.java b/src/main/java/org/apache/bcel/classfile/ClassParser.java index b8415a32fc..5a48ea8870 100644 --- a/src/main/java/org/apache/bcel/classfile/ClassParser.java +++ b/src/main/java/org/apache/bcel/classfile/ClassParser.java @@ -287,6 +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); + legacyCodeFormat = major < Const.MAJOR_1_1 || major == Const.MAJOR_1_1 && minor < Const.MINOR_1_1; } }