This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git


The following commit(s) were added to refs/heads/master by this push:
     new bb86e554 [BCEL-341] Support compact Code headers in historical class 
files (#542)
bb86e554 is described below

commit bb86e5547a5371485fb266972c66b2b0e6d29b03
Author: Gary Gregory <[email protected]>
AuthorDate: Sun Nov 1 15:49:05 2026 -0500

    [BCEL-341] Support compact Code headers in historical class files (#542)
    
    Add explicit support for compact Code headers in versions 45.0 through
    45.2 while retaining standard headers by default.
    
    Preserve header settings during copying and code generation, and select
    a consistent layout when dumping classes. Document field widths,
    validation, and version handling, and add regression tests.
---
 src/changes/changes.xml                            |   1 +
 .../java/org/apache/bcel/classfile/Attribute.java  |  45 ++-
 .../org/apache/bcel/classfile/ClassParser.java     |  51 +++-
 src/main/java/org/apache/bcel/classfile/Code.java  | 116 +++++++-
 src/main/java/org/apache/bcel/classfile/Field.java |  21 +-
 .../org/apache/bcel/classfile/FieldOrMethod.java   |  61 +++-
 .../java/org/apache/bcel/classfile/JavaClass.java  |  13 +-
 .../java/org/apache/bcel/classfile/Method.java     |  24 +-
 .../java/org/apache/bcel/classfile/Record.java     |  35 ++-
 .../apache/bcel/classfile/RecordComponentInfo.java |  26 +-
 .../java/org/apache/bcel/generic/MethodGen.java    |  59 +++-
 src/main/java/org/apache/bcel/util/BCELifier.java  |   4 +
 .../org/apache/bcel/Java1OakExampleCreator.java    |  85 ++++++
 .../bcel/classfile/AttributeNestingTest.java       |  37 ++-
 .../org/apache/bcel/classfile/ClassParserTest.java |  10 +
 .../bcel/classfile/CompactCodeHeaderTest.java      | 328 +++++++++++++++++++++
 .../java/org/apache/bcel/util/BCELifierTest.java   |  32 ++
 src/test/resources/Java1OakExample.class           | Bin 0 -> 493 bytes
 .../Java1OakExample.java}                          |  16 +-
 19 files changed, 888 insertions(+), 76 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index dd6ca031..d3ee4716 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -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>
diff --git a/src/main/java/org/apache/bcel/classfile/Attribute.java 
b/src/main/java/org/apache/bcel/classfile/Attribute.java
index 2cb0434f..e1017cbd 100644
--- a/src/main/java/org/apache/bcel/classfile/Attribute.java
+++ b/src/main/java/org/apache/bcel/classfile/Attribute.java
@@ -121,18 +121,39 @@ public abstract class Attribute implements Cloneable, 
Node {
     }
 
     /**
-     * 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;
@@ -143,7 +164,7 @@ public abstract class Attribute implements Cloneable, Node {
         }
         NESTING_DEPTH.set(Integer.valueOf(depth));
         try {
-            return readAttribute0(dataInput, constantPool);
+            return readAttribute0(dataInput, constantPool, compactCodeHeader);
         } finally {
             if (depth == 1) {
                 NESTING_DEPTH.remove();
@@ -154,24 +175,22 @@ public abstract class Attribute implements Cloneable, 
Node {
     }
 
     /**
-     * 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();
@@ -201,7 +220,7 @@ public abstract class Attribute implements Cloneable, Node {
         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:
@@ -256,7 +275,7 @@ public abstract class Attribute implements Cloneable, Node {
         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:
diff --git a/src/main/java/org/apache/bcel/classfile/ClassParser.java 
b/src/main/java/org/apache/bcel/classfile/ClassParser.java
index a5c8eece..521e3a2d 100644
--- a/src/main/java/org/apache/bcel/classfile/ClassParser.java
+++ b/src/main/java/org/apache/bcel/classfile/ClassParser.java
@@ -60,6 +60,10 @@ public final class ClassParser {
     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
@@ -111,11 +115,27 @@ public final class ClassParser {
         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.
@@ -192,7 +212,7 @@ public final class ClassParser {
         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);
         }
     }
 
@@ -237,7 +257,7 @@ public final class ClassParser {
         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);
         }
     }
 
@@ -273,7 +293,7 @@ public final class ClassParser {
         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);
         }
     }
 
@@ -286,5 +306,30 @@ public final class ClassParser {
     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;
     }
 }
diff --git a/src/main/java/org/apache/bcel/classfile/Code.java 
b/src/main/java/org/apache/bcel/classfile/Code.java
index 7b972c11..473d2974 100644
--- a/src/main/java/org/apache/bcel/classfile/Code.java
+++ b/src/main/java/org/apache/bcel/classfile/Code.java
@@ -56,6 +56,21 @@ import org.apache.commons.lang3.ArrayUtils;
  * }
  * </pre>
  *
+ * <p>
+ * The layout above uses the standard Code header. This class also supports 
compact Code headers found in historical class file versions 45.0 through 45.2.
+ * Here, "compact" describes the narrower encoding of three fields: {@code 
max_stack} and {@code max_locals} each use {@code u1}, and {@code code_length}
+ * uses {@code u2}, instead of {@code u2}, {@code u2}, and {@code u4}, 
respectively. The notation {@code u1}, {@code u2}, and {@code u4} denotes 
unsigned
+ * one-, two-, and four-byte values. These three fields therefore occupy 4 
bytes instead of 8, excluding the common six-byte attribute prefix.
+ * The layout of that prefix and the remaining Code fields is unchanged; the 
bytecode itself is not compressed.
+ * {@link #isCompactCodeHeader()} identifies this narrower layout.
+ * </p>
+ *
+ * <p>
+ * Class file versions 45.0 through 45.2 also occur with standard Code headers 
accepted by modern JVMs, so the version alone does not identify the layout.
+ * {@link ClassParser} uses standard headers by default; {@link 
ClassParser#setCompactCodeHeaderEnabled(boolean)} explicitly selects compact 
headers
+ * for those versions.
+ * </p>
+ *
  * @see Attribute
  * @see CodeException
  * @see LineNumberTable
@@ -63,6 +78,11 @@ import org.apache.commons.lang3.ArrayUtils;
  */
 public final class Code extends Attribute {
 
+    /**
+     * Whether {@code max_stack}, {@code max_locals}, and {@code code_length} 
use {@code u1/u1/u2} (4 bytes) instead of {@code u2/u2/u4} (8 bytes).
+     * Compact describes these field widths, not compression of the bytecode 
or the rest of the attribute.
+     */
+    private boolean compactCodeHeader;
     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 +97,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.compactCodeHeader);
     }
 
     /**
@@ -90,10 +110,19 @@ public final class Code extends Attribute {
      * @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 {
-        // 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, dataInput, constantPool, false);
+    }
+
+    /**
+     * Reads a Code attribute using the selected header layout.
+     */
+    Code(final int nameIndex, final int length, final DataInput dataInput, 
final ConstantPool constantPool, final boolean compactCodeHeader) throws 
IOException {
+        // Compact headers encode max_stack/max_locals/code_length as 
u1/u1/u2; standard headers use u2/u2/u4.
+        // Initialize the remaining fields with default values, then read them 
below.
+        this(nameIndex, length, compactCodeHeader ? 
dataInput.readUnsignedByte() : dataInput.readUnsignedShort(),
+                compactCodeHeader ? dataInput.readUnsignedByte() : 
dataInput.readUnsignedShort(), null, null, null, constantPool, 
compactCodeHeader);
+        final int codeLength = Args.requireU4(compactCodeHeader ? 
dataInput.readUnsignedShort() : dataInput.readInt(), 0, Const.MAX_CODE_SIZE,
+                "Code length attribute");
         code = new byte[codeLength]; // Read byte code
         dataInput.readFully(code);
         /*
@@ -110,7 +139,7 @@ public final class Code extends Attribute {
         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, 
compactCodeHeader);
         }
         /*
          * Adjust length, because of setAttributes in this(), s.b. length is 
incorrect, because it didn't take the internal attributes into account yet! Very
@@ -120,7 +149,11 @@ public final class Code extends Attribute {
     }
 
     /**
-     * Constructs a Code attribute.
+     * Constructs a Code attribute using a standard eight-byte header.
+     *
+     * <p>
+     * Use {@link #setCompactCodeHeader(boolean)} to select the compact 
four-byte header after construction.
+     * </p>
      *
      * @param nameIndex Index pointing to the name <em>Code</em>.
      * @param length Content length in bytes.
@@ -134,7 +167,13 @@ public final class Code extends Attribute {
      */
     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 compactCodeHeader) {
         super(Const.ATTR_CODE, nameIndex, length, constantPool);
+        this.compactCodeHeader = compactCodeHeader;
         this.maxStack = Args.requireU2(maxStack, "maxStack");
         this.maxLocals = Args.requireU2(maxLocals, "maxLocals");
         this.code = ArrayUtils.nullToEmpty(code);
@@ -194,13 +233,26 @@ public final class Code extends Attribute {
      *
      * @param file Output file stream.
      * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if a compact header's {@code 
max_stack} or {@code max_locals} is outside 0 through 255,
+     *         or its code length exceeds 65535 bytes.
      */
     @Override
     public void dump(final DataOutputStream file) throws IOException {
+        if (compactCodeHeader) {
+            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 (compactCodeHeader) {
+            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 +299,8 @@ public final class Code extends Attribute {
      * @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 */
+        // Only max_stack, max_locals, and code_length differ: 1 + 1 + 2 
compact bytes versus 2 + 2 + 4 standard bytes.
+        return (compactCodeHeader ? 4 : 8)
             + code.length /* byte-code */
             + 2 /* exception-table length */
             + 8 * (exceptionTable == null ? 0 : exceptionTable.length) /* 
exception table */
@@ -330,6 +383,18 @@ public final class Code extends Attribute {
         return null;
     }
 
+    /**
+     * Gets whether this attribute uses the compact four-byte Code header.
+     *
+     * @return Whether {@code max_stack}, {@code max_locals}, and {@code 
code_length} use {@code u1}, {@code u1}, and {@code u2}, respectively,
+     *         instead of the standard {@code u2}, {@code u2}, and {@code u4}.
+     * @since 6.14.0
+     * @see #setCompactCodeHeader(boolean)
+     */
+    public boolean isCompactCodeHeader() {
+        return compactCodeHeader;
+    }
+
     /**
      * Sets the attributes for this Code.
      *
@@ -352,6 +417,35 @@ public final class Code extends Attribute {
         super.setLength(calculateLength()); // Adjust length
     }
 
+    /**
+     * Sets the Code header layout and recalculates the attribute length.
+     *
+     * <p>
+     * Compact headers encode {@code max_stack}, {@code max_locals}, and 
{@code code_length} as {@code u1}, {@code u1}, and {@code u2}, respectively,
+     * totaling 4 bytes instead of the standard 8. This limits {@code 
max_stack} and {@code max_locals} to 0 through 255 and the code length to 65535 
bytes.
+     * The remaining attribute layout is unchanged. Compact headers are 
intended for historical class file versions 45.0 through 45.2.
+     * Enabling compact headers validates these values before changing the 
layout. Subsequent changes to them are checked by {@link 
#dump(DataOutputStream)}.
+     * </p>
+     *
+     * <p>
+     * This setting controls this attribute's own dump. When dumping an entire 
class, {@link JavaClass#dump(DataOutputStream)} selects one layout for
+     * all method Code attributes based on the class version and the methods' 
header settings, without changing this attribute's setting.
+     * </p>
+     *
+     * @param compactCodeHeader Whether to use a compact four-byte header 
({@code true}) or a standard eight-byte header ({@code false}).
+     * @throws ClassFormatException Thrown if {@code compactCodeHeader} is 
{@code true} and the current values do not fit in a compact header.
+     * @since 6.14.0
+     */
+    public void setCompactCodeHeader(final boolean compactCodeHeader) {
+        if (compactCodeHeader) {
+            Args.requireU1(maxStack, "maxStack");
+            Args.requireU1(maxLocals, "maxLocals");
+            Args.requireU2(code.length, "Code length attribute");
+        }
+        this.compactCodeHeader = compactCodeHeader;
+        super.setLength(calculateLength());
+    }
+
     /**
      * Sets the 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 fca1bd8a..3ebd9c46 100644
--- a/src/main/java/org/apache/bcel/classfile/Field.java
+++ b/src/main/java/org/apache/bcel/classfile/Field.java
@@ -71,13 +71,30 @@ public final class Field extends FieldOrMethod {
     }
 
     /**
-     * Constructs object from file stream.
+     * Constructs a Field from a data input using standard Code headers.
      *
      * @param file Input stream.
      * @param constantPool The constant pool.
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the field or one of its 
attributes is malformed.
      */
     Field(final DataInput file, final ConstantPool constantPool) throws 
IOException, ClassFormatException {
-        super(file, constantPool);
+        this(file, constantPool, false);
+    }
+
+    /**
+     * Constructs a Field from a data input using the selected Code header 
layout.
+     *
+     * @param file Input stream.
+     * @param constantPool The constant pool.
+     * @param compactCodeHeader Whether to read any Code attributes 
encountered with compact four-byte headers ({@code true})
+     *        or standard eight-byte headers ({@code false}).
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the field or one of its 
attributes is malformed.
+     * @see Attribute#readAttribute(DataInput, ConstantPool, boolean)
+     */
+    Field(final DataInput file, final ConstantPool constantPool, final boolean 
compactCodeHeader) throws IOException {
+        super(file, constantPool, compactCodeHeader);
     }
 
     /**
diff --git a/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java 
b/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java
index 77cbaa77..d47d2b42 100644
--- a/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java
+++ b/src/main/java/org/apache/bcel/classfile/FieldOrMethod.java
@@ -67,32 +67,52 @@ public abstract class FieldOrMethod extends AccessFlags 
implements Cloneable, No
     private String signatureAttributeString;
     private boolean searchedForSignatureAttribute;
 
+    /**
+     * Constructs an empty field or method whose properties must be set before 
use.
+     */
     FieldOrMethod() {
     }
 
     /**
-     * Constructs object from file stream.
+     * Constructs a field or method from a data input using standard Code 
headers.
      *
      * @param file Input stream.
      * @param constantPool The constant pool.
      * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the field or method, or one of 
its attributes, is malformed.
      */
     protected FieldOrMethod(final DataInput file, final ConstantPool 
constantPool) throws IOException {
+        this(file, constantPool, false);
+    }
+
+    /**
+     * Constructs a field or method from a data input using the selected Code 
header layout.
+     *
+     * @param file Input stream.
+     * @param constantPool The constant pool.
+     * @param compactCodeHeader Whether to read any Code attributes 
encountered with compact four-byte headers ({@code true})
+     *        or standard eight-byte headers ({@code false}).
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the field or method, or one of 
its attributes, is malformed.
+     * @see Attribute#readAttribute(DataInput, ConstantPool, boolean)
+     */
+    FieldOrMethod(final DataInput file, final ConstantPool constantPool, final 
boolean compactCodeHeader) 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, 
compactCodeHeader);
         }
         this.attributes_count = attributesCount; // init deprecated field
     }
 
     /**
-     * Constructs object from file stream.
+     * Constructs a field or method from a data input stream using standard 
Code headers.
      *
      * @param file Input stream.
      * @param constantPool The constant pool.
      * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the field or method, or one of 
its attributes, is malformed.
      * @deprecated (6.0) Use {@link #FieldOrMethod(java.io.DataInput, 
ConstantPool)} instead.
      */
     @java.lang.Deprecated
@@ -113,8 +133,8 @@ public abstract class FieldOrMethod extends AccessFlags 
implements Cloneable, No
     /**
      * Constructs a FieldOrMethod.
      *
-     * @param accessFlags Access rights of method.
-     * @param nameIndex Points to field name in constant pool.
+     * @param accessFlags Access rights of the field or method.
+     * @param nameIndex Points to the field or method name in the constant 
pool.
      * @param signatureIndex Points to encoded signature.
      * @param attributes Collection of attributes.
      * @param constantPool Array of constants.
@@ -154,15 +174,44 @@ public abstract class FieldOrMethod extends AccessFlags 
implements Cloneable, No
      * @throws IOException Thrown if an I/O error occurs.
      */
     public final void dump(final DataOutputStream file) throws IOException {
+        dump(file, attributes);
+    }
+
+    private void dump(final DataOutputStream file, final Attribute[] 
outputAttributes) throws IOException {
         file.writeShort(super.getAccessFlags());
         file.writeShort(name_index);
         file.writeShort(signature_index);
         file.writeShort(Args.requireU2(attributes_count, "attributes_count"));
-        for (final Attribute attribute : attributes) {
+        for (final Attribute attribute : outputAttributes) {
             attribute.dump(file);
         }
     }
 
+    /**
+     * Dumps a member with the Code encoding selected by its enclosing class, 
without modifying its attributes.
+     *
+     * @param file Output stream.
+     * @param compactCodeHeader Whether to write Code attributes with compact 
four-byte headers ({@code true})
+     *        or standard eight-byte headers ({@code false}).
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if an attribute fails validation.
+     */
+    final void dump(final DataOutputStream file, final boolean 
compactCodeHeader) throws IOException {
+        final Attribute[] sourceAttributes = getAttributes();
+        Attribute[] outputAttributes = sourceAttributes;
+        for (int i = 0; i < sourceAttributes.length; i++) {
+            if (sourceAttributes[i] instanceof Code && ((Code) 
sourceAttributes[i]).isCompactCodeHeader() != compactCodeHeader) {
+                if (outputAttributes == sourceAttributes) {
+                    outputAttributes = sourceAttributes.clone();
+                }
+                final Code code = new Code((Code) sourceAttributes[i]);
+                code.setCompactCodeHeader(compactCodeHeader);
+                outputAttributes[i] = code;
+            }
+        }
+        dump(file, outputAttributes);
+    }
+
     /**
      * Gets annotations on the field or method.
      *
diff --git a/src/main/java/org/apache/bcel/classfile/JavaClass.java 
b/src/main/java/org/apache/bcel/classfile/JavaClass.java
index 94f1e566..cdeb8c74 100644
--- a/src/main/java/org/apache/bcel/classfile/JavaClass.java
+++ b/src/main/java/org/apache/bcel/classfile/JavaClass.java
@@ -338,10 +338,21 @@ public class JavaClass extends AccessFlags implements 
Cloneable, Node, Comparabl
     /**
      * Dumps Java class to output stream in binary format.
      *
+     * <p>
+     * For versions 45.0 through 45.2, all method Code attributes use compact 
four-byte headers if any method's Code attribute returns {@code true} from
+     * {@link Code#isCompactCodeHeader()}. Otherwise, all method Code 
attributes use standard eight-byte headers. Versions 45.3 and later always use 
standard
+     * headers, including after a version change. The header size refers only 
to {@code max_stack}, {@code max_locals}, and {@code code_length}.
+     * These encoding adjustments do not modify the methods or their 
attributes.
+     * </p>
+     *
      * @param file Output stream.
      * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if a method's values do not fit the 
selected Code header layout.
      */
     public void dump(final DataOutputStream file) throws IOException {
+        // Use one layout for all method Code headers. A version change can 
require widening headers without modifying shared attributes.
+        final boolean compactCodeHeader = major == Const.MAJOR_1_1 && minor < 
Const.MINOR_1_1 && Arrays.stream(methods)
+            
.map(Method::getCode).filter(Objects::nonNull).anyMatch(Code::isCompactCodeHeader);
         file.writeInt(Const.JVM_CLASSFILE_MAGIC);
         file.writeShort(minor);
         file.writeShort(major);
@@ -359,7 +370,7 @@ public class JavaClass extends AccessFlags implements 
Cloneable, Node, Comparabl
         }
         file.writeShort(Args.requireU2(methods.length, "methods.length"));
         for (final Method method : methods) {
-            method.dump(file);
+            method.dump(file, compactCodeHeader);
         }
         if (attributes != null) {
             file.writeShort(Args.requireU2(attributes.length, 
"attributes.length"));
diff --git a/src/main/java/org/apache/bcel/classfile/Method.java 
b/src/main/java/org/apache/bcel/classfile/Method.java
index 235a3204..d78f8dc5 100644
--- a/src/main/java/org/apache/bcel/classfile/Method.java
+++ b/src/main/java/org/apache/bcel/classfile/Method.java
@@ -79,21 +79,37 @@ public final class Method extends FieldOrMethod {
     }
 
     /**
-     * Constructs object from file stream.
+     * Constructs a Method from a data input using standard Code headers.
      *
      * @param file Input stream.
+     * @param constantPool The constant pool.
      * @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.
+     * @throws ClassFormatException Thrown if the method or one of its 
attributes is malformed.
      */
     Method(final DataInput file, final ConstantPool constantPool) throws 
IOException, ClassFormatException {
-        super(file, constantPool);
+        this(file, constantPool, false);
+    }
+
+    /**
+     * Constructs a Method from a data input using the selected Code header 
layout.
+     *
+     * @param file Input stream.
+     * @param constantPool The constant pool.
+     * @param compactCodeHeader Whether to read any Code attributes 
encountered with compact four-byte headers ({@code true})
+     *        or standard eight-byte headers ({@code false}).
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the method or one of its 
attributes is malformed.
+     * @see Attribute#readAttribute(DataInput, ConstantPool, boolean)
+     */
+    Method(final DataInput file, final ConstantPool constantPool, final 
boolean compactCodeHeader) throws IOException {
+        super(file, constantPool, compactCodeHeader);
     }
 
     /**
      * Constructs a Method.
      *
      * @param accessFlags Access rights of method.
-     * @param nameIndex Points to field name in constant pool.
+     * @param nameIndex Points to method name in constant pool.
      * @param signatureIndex Points to encoded signature.
      * @param attributes Collection of attributes.
      * @param constantPool Array of constants.
diff --git a/src/main/java/org/apache/bcel/classfile/Record.java 
b/src/main/java/org/apache/bcel/classfile/Record.java
index 327dee1b..0117f850 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 compactCodeHeader)
             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, 
compactCodeHeader);
         }
         return components;
     }
@@ -52,17 +52,40 @@ public final class Record extends Attribute {
     private RecordComponentInfo[] components;
 
     /**
-     * Constructs object from input stream.
+     * Constructs a Record attribute from a data input using standard Code 
headers for nested attributes.
      *
-     * @param nameIndex    Index in constant pool.
+     * @param nameIndex    Constant pool index of the attribute name.
      * @param length       Content length in bytes.
      * @param input        Input stream.
-     * @param constantPool Array of constants.
+     * @param constantPool The constant pool.
      * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the attribute or one of its 
components is malformed.
      */
     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);
+    }
+
+    /**
+     * Constructs a Record attribute from a data input using the selected Code 
header layout for nested attributes.
+     *
+     * <p>
+     * The header selection is forwarded when reading component attributes; it 
does not change the Record attribute or record component layout.
+     * </p>
+     *
+     * @param nameIndex Constant pool index of the attribute name.
+     * @param length Content length in bytes.
+     * @param input Input stream.
+     * @param constantPool The constant pool.
+     * @param compactCodeHeader Whether to read any Code attributes 
encountered with compact four-byte headers ({@code true})
+     *        or standard eight-byte headers ({@code false}).
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if the attribute or one of its 
components is malformed.
+     * @see Attribute#readAttribute(DataInput, ConstantPool, boolean)
+     */
+    Record(final int nameIndex, final int length, final DataInput input, final 
ConstantPool constantPool, final boolean compactCodeHeader)
+            throws IOException {
+        this(nameIndex, length, readComponents(input, constantPool, 
compactCodeHeader), constantPool);
     }
 
     /**
diff --git a/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java 
b/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java
index e32e1ac7..e58a1c6d 100644
--- a/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java
+++ b/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java
@@ -41,19 +41,39 @@ public class RecordComponentInfo implements Node {
     private final ConstantPool constantPool;
 
     /**
-     * Constructs a new instance from an input stream.
+     * Constructs record component information from a data input using 
standard Code headers for nested attributes.
      *
      * @param input        Input stream.
-     * @param constantPool Array of constants.
+     * @param constantPool The constant pool.
      * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if a component attribute is 
malformed.
      */
     public RecordComponentInfo(final DataInput input, final ConstantPool 
constantPool) throws IOException {
+        this(input, constantPool, false);
+    }
+
+    /**
+     * Constructs record component information from a data input using the 
selected Code header layout for nested attributes.
+     *
+     * <p>
+     * The header selection is forwarded when reading component attributes; it 
does not change the record component layout.
+     * </p>
+     *
+     * @param input Input stream.
+     * @param constantPool The constant pool.
+     * @param compactCodeHeader Whether to read any Code attributes 
encountered with compact four-byte headers ({@code true})
+     *        or standard eight-byte headers ({@code false}).
+     * @throws IOException Thrown if an I/O error occurs.
+     * @throws ClassFormatException Thrown if a component attribute is 
malformed.
+     * @see Attribute#readAttribute(DataInput, ConstantPool, boolean)
+     */
+    RecordComponentInfo(final DataInput input, final ConstantPool 
constantPool, final boolean compactCodeHeader) 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, 
compactCodeHeader);
         }
         this.constantPool = constantPool;
     }
diff --git a/src/main/java/org/apache/bcel/generic/MethodGen.java 
b/src/main/java/org/apache/bcel/generic/MethodGen.java
index 0e0e4aeb..c411b01b 100644
--- a/src/main/java/org/apache/bcel/generic/MethodGen.java
+++ b/src/main/java/org/apache/bcel/generic/MethodGen.java
@@ -45,6 +45,7 @@ import org.apache.bcel.classfile.ParameterAnnotationEntry;
 import org.apache.bcel.classfile.ParameterAnnotations;
 import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations;
 import org.apache.bcel.classfile.Utility;
+import org.apache.bcel.util.Args;
 import org.apache.bcel.util.BCELComparator;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.stream.Streams;
@@ -225,6 +226,7 @@ public class MethodGen extends FieldGenOrMethodGen {
     private InstructionList il;
 
     private boolean stripAttributes;
+    private boolean compactCodeHeader;
     private LocalVariableTypeTable localVariableTypeTable;
     private final List<LocalVariableGen> variableList = new ArrayList<>();
 
@@ -327,6 +329,7 @@ public class MethodGen extends FieldGenOrMethodGen {
             Attribute a = attribute;
             if (a instanceof Code) {
                 final Code c = (Code) a;
+                compactCodeHeader = c.isCompactCodeHeader();
                 setMaxStack(c.getMaxStack());
                 setMaxLocals(c.getMaxLocals());
                 final CodeException[] ces = c.getExceptionTable();
@@ -881,7 +884,12 @@ public class MethodGen extends FieldGenOrMethodGen {
      * Gets method object. Never forget to call setMaxStack() or 
setMaxStack(max), respectively, before calling this method
      * (the same applies for max locals).
      *
+     * <p>
+     * When initialized from an existing method, the Code header layout is 
preserved. Use {@link #setCompactCodeHeader(boolean)} to change it.
+     * </p>
+     *
      * @return method object.
+     * @throws org.apache.bcel.classfile.ClassFormatException Thrown if Code 
values do not fit the selected header layout.
      */
     public Method getMethod() {
         final String signature = getSignature();
@@ -899,7 +907,16 @@ public class MethodGen extends FieldGenOrMethodGen {
          */
         if (!variableList.isEmpty() && !stripAttributes) {
             updateLocalVariableTable(getLocalVariableTable(cp));
-            addCodeAttribute(lvt = getLocalVariableTable(cp));
+            lvt = getLocalVariableTable(cp);
+        }
+        // Updating the local variable table can increase maxLocals. Validate 
before adding temporary attributes.
+        if (compactCodeHeader && byteCode != null && !isAbstract() && 
!isNative()) {
+            Args.requireU1(maxStack, "maxStack");
+            Args.requireU1(maxLocals, "maxLocals");
+            Args.requireU2(byteCode.length, "Code length attribute");
+        }
+        if (lvt != null) {
+            addCodeAttribute(lvt);
         }
         if (localVariableTypeTable != null) {
             // LocalVariable length in LocalVariableTypeTable is not updated 
automatically. It's a difference with
@@ -935,6 +952,7 @@ public class MethodGen extends FieldGenOrMethodGen {
                 2 + excLen + // exceptions
                 2 + attrsLen, // attributes
                 maxStack, maxLocals, byteCode, cExc, codeAttrs, 
cp.getConstantPool());
+            code.setCompactCodeHeader(compactCodeHeader);
             addAttribute(code);
         }
         final Attribute[] annotations = addRuntimeAnnotationsAsAttribute(cp);
@@ -991,6 +1009,17 @@ public class MethodGen extends FieldGenOrMethodGen {
         return bcelComparator.hashCode(this);
     }
 
+    /**
+     * Gets whether generated Code attributes use compact four-byte headers.
+     *
+     * @return Whether generated Code attributes use compact headers.
+     * @since 6.14.0
+     * @see Code#isCompactCodeHeader()
+     */
+    public boolean isCompactCodeHeader() {
+        return compactCodeHeader;
+    }
+
     private List<AnnotationEntryGen> makeMutableVersion(final 
AnnotationEntry[] mutableArray) {
         return Streams.of(mutableArray).map(ae -> new AnnotationEntryGen(ae, 
getConstantPool(), false)).collect(Collectors.toList());
     }
@@ -1182,6 +1211,34 @@ public class MethodGen extends FieldGenOrMethodGen {
         this.className = className;
     }
 
+    /**
+     * Sets the header layout of generated Code attributes.
+     *
+     * <p>
+     * Compact headers encode {@code max_stack}, {@code max_locals}, and 
{@code code_length} as unsigned one-, one-, and two-byte fields, respectively,
+     * totaling 4 bytes instead of the standard 8. This changes only these 
field widths, not the bytecode or the remaining attribute layout.
+     * Compact headers are intended for historical class file versions 45.0 
through 45.2.
+     * </p>
+     *
+     * <p>
+     * Newly created methods use standard headers by default. Methods 
initialized from an existing method preserve its header layout.
+     * {@link #getMethod()} checks that compact headers have {@code max_stack} 
and {@code max_locals} from 0 through 255 and a code length
+     * of at most 65535 bytes.
+     * </p>
+     *
+     * <p>
+     * When dumping the enclosing class, {@link 
org.apache.bcel.classfile.JavaClass#dump(java.io.DataOutputStream)} may select 
a different layout
+     * for consistency with the class version and its other methods. This does 
not change the generated Code attribute's header setting.
+     * </p>
+     *
+     * @param compactCodeHeader Whether to generate compact four-byte Code 
headers ({@code true}) or standard eight-byte headers ({@code false}).
+     * @since 6.14.0
+     * @see Code#setCompactCodeHeader(boolean)
+     */
+    public void setCompactCodeHeader(final boolean compactCodeHeader) {
+        this.compactCodeHeader = compactCodeHeader;
+    }
+
     /**
      * Sets the instruction list.
      *
diff --git a/src/main/java/org/apache/bcel/util/BCELifier.java 
b/src/main/java/org/apache/bcel/util/BCELifier.java
index 4872a11e..34f759e5 100644
--- a/src/main/java/org/apache/bcel/util/BCELifier.java
+++ b/src/main/java/org/apache/bcel/util/BCELifier.java
@@ -374,6 +374,10 @@ public class BCELifier extends 
org.apache.bcel.classfile.EmptyVisitor {
         }
         final Code code = method.getCode();
         if (code != null) {
+            // Preserve this method's compact header setting only for versions 
that support it; JavaClass.dump selects the class-wide layout.
+            if (code.isCompactCodeHeader() && clazz.getMajor() == 
Const.MAJOR_1_1 && clazz.getMinor() < Const.MINOR_1_1) {
+                printWriter.println("    method.setCompactCodeHeader(true);");
+            }
             final StackMap stackMap = code.getStackMap();
             if (stackMap != null) {
                 stackMap.accept(this);
diff --git a/src/test/java/org/apache/bcel/Java1OakExampleCreator.java 
b/src/test/java/org/apache/bcel/Java1OakExampleCreator.java
new file mode 100644
index 00000000..9a749c23
--- /dev/null
+++ b/src/test/java/org/apache/bcel/Java1OakExampleCreator.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.bcel;
+
+import java.io.IOException;
+
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.classfile.Method;
+import org.apache.bcel.generic.ArrayType;
+import org.apache.bcel.generic.ClassGen;
+import org.apache.bcel.generic.InstructionConst;
+import org.apache.bcel.generic.InstructionFactory;
+import org.apache.bcel.generic.InstructionList;
+import org.apache.bcel.generic.MethodGen;
+import org.apache.bcel.generic.Type;
+
+/**
+ * Creates the version 45.2 fixture corresponding to {@code 
src/test/resources/Java1OakExample.java}, using compact Code headers.
+ * Each header stores {@code max_stack}, {@code max_locals}, and {@code 
code_length} in one, one, and two bytes, respectively.
+ *
+ * <p>
+ * To regenerate the fixture from the project directory, run:
+ * </p>
+ * <pre>
+ * mvn test-compile exec:java -Dexec.classpathScope=test 
-Dexec.mainClass=org.apache.bcel.Java1OakExampleCreator
+ * </pre>
+ */
+public final class Java1OakExampleCreator {
+
+    /**
+     * Creates the example class with compact Code headers.
+     *
+     * @return The example class.
+     */
+    public static JavaClass create() {
+        final ClassGen cg = new ClassGen("Java1OakExample", 
"java.lang.Object", "Java1OakExample.java",
+            Const.ACC_PUBLIC | Const.ACC_SUPER, new String[0]);
+        cg.setMinor(2);
+        cg.addEmptyConstructor(Const.ACC_PUBLIC);
+        final InstructionList il = new 
InstructionFactory(cg).createPrintln("Hello Oak!");
+        il.append(InstructionConst.RETURN);
+        final MethodGen main = new MethodGen(Const.ACC_PUBLIC | 
Const.ACC_STATIC, Type.VOID, new Type[] {new ArrayType(Type.STRING, 1)},
+            new String[] {"args"}, "main", cg.getClassName(), il, 
cg.getConstantPool());
+        main.setMaxStack();
+        main.setMaxLocals();
+        cg.addMethod(main.getMethod());
+        il.dispose();
+        final JavaClass result = cg.getJavaClass();
+        // Select the historical four-byte Code header explicitly; version 
45.2 can also use the standard eight-byte layout.
+        for (final Method method : result.getMethods()) {
+            method.getCode().setCompactCodeHeader(true);
+        }
+        return result;
+    }
+
+    /**
+     * Writes the fixture to its resource path or the given file.
+     *
+     * @param args Optional output file name.
+     * @throws IOException Thrown if the output cannot be written.
+     */
+    public static void main(final String[] args) throws IOException {
+        create().dump(args.length == 0 ? 
"src/test/resources/Java1OakExample.class" : args[0]);
+    }
+
+    private Java1OakExampleCreator() {
+    }
+}
diff --git a/src/test/java/org/apache/bcel/classfile/AttributeNestingTest.java 
b/src/test/java/org/apache/bcel/classfile/AttributeNestingTest.java
index 5595e368..2799a99b 100644
--- a/src/test/java/org/apache/bcel/classfile/AttributeNestingTest.java
+++ b/src/test/java/org/apache/bcel/classfile/AttributeNestingTest.java
@@ -29,7 +29,8 @@ import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
 
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Tests that {@link Attribute#readAttribute(DataInput, ConstantPool)} bounds 
the attribute nesting depth instead of recursing until a
@@ -40,17 +41,23 @@ class AttributeNestingTest {
     /**
      * Builds a Code attribute nested {@code depth} times inside itself: each 
level declares one sub-attribute, which is again a Code attribute.
      */
-    private static byte[] nestedCodeAttribute(final int depth) throws 
IOException {
+    private static byte[] nestedCodeAttribute(final int depth, final boolean 
compactCodeHeader) throws IOException {
         byte[] inner = {};
         int attributeCount = 0;
         for (int i = 0; i < depth; i++) {
             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
             try (DataOutputStream dos = new DataOutputStream(baos)) {
                 dos.writeShort(1); // attribute_name_index -> "Code"
-                dos.writeInt(12 + inner.length); // attribute_length
-                dos.writeShort(0); // max_stack
-                dos.writeShort(0); // max_locals
-                dos.writeInt(0); // code_length
+                dos.writeInt((compactCodeHeader ? 8 : 12) + inner.length); // 
attribute_length
+                if (compactCodeHeader) {
+                    dos.writeByte(0); // max_stack
+                    dos.writeByte(0); // max_locals
+                    dos.writeShort(0); // code_length
+                } else {
+                    dos.writeShort(0); // max_stack
+                    dos.writeShort(0); // max_locals
+                    dos.writeInt(0); // code_length
+                }
                 dos.writeShort(0); // exception_table_length
                 dos.writeShort(attributeCount); // attributes_count
                 dos.write(inner);
@@ -61,21 +68,23 @@ class AttributeNestingTest {
         return inner;
     }
 
-    @Test
-    void testDeeplyNestedCodeAttributeRejected() throws IOException {
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void testDeeplyNestedCodeAttributeRejected(final boolean 
compactCodeHeader) throws IOException {
         final ConstantPool cp = new ConstantPool(new ConstantUtf8("unused 
index 0"), new ConstantUtf8("Code"));
-        final byte[] bytes = nestedCodeAttribute(1_000);
+        final byte[] bytes = nestedCodeAttribute(1_000, compactCodeHeader);
         try (DataInputStream in = new DataInputStream(new 
ByteArrayInputStream(bytes))) {
-            assertThrows(ClassFormatException.class, () -> 
Attribute.readAttribute((DataInput) in, cp));
+            assertThrows(ClassFormatException.class, () -> 
Attribute.readAttribute(in, cp, compactCodeHeader));
         }
     }
 
-    @Test
-    void testModeratelyNestedCodeAttributeAccepted() throws IOException {
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void testModeratelyNestedCodeAttributeAccepted(final boolean 
compactCodeHeader) throws IOException {
         final ConstantPool cp = new ConstantPool(new ConstantUtf8("unused 
index 0"), new ConstantUtf8("Code"));
-        final byte[] bytes = nestedCodeAttribute(3);
+        final byte[] bytes = nestedCodeAttribute(3, compactCodeHeader);
         try (DataInputStream in = new DataInputStream(new 
ByteArrayInputStream(bytes))) {
-            assertTrue(Attribute.readAttribute((DataInput) in, cp) instanceof 
Code);
+            assertTrue(Attribute.readAttribute(in, cp, compactCodeHeader) 
instanceof Code);
         }
     }
 }
diff --git a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java 
b/src/test/java/org/apache/bcel/classfile/ClassParserTest.java
index f16d0c91..a9f21981 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,11 @@ 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 {
+        final ClassParser parser = new 
ClassParser("src/test/resources/Java1OakExample.class");
+        parser.setCompactCodeHeaderEnabled(true);
+        assertNotNull(parser.parse());
+    }
 }
diff --git a/src/test/java/org/apache/bcel/classfile/CompactCodeHeaderTest.java 
b/src/test/java/org/apache/bcel/classfile/CompactCodeHeaderTest.java
new file mode 100644
index 00000000..6ad5b604
--- /dev/null
+++ b/src/test/java/org/apache/bcel/classfile/CompactCodeHeaderTest.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.bcel.classfile;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.Java1OakExampleCreator;
+import org.apache.bcel.generic.ClassGen;
+import org.apache.bcel.generic.ConstantPoolGen;
+import org.apache.bcel.generic.FieldGen;
+import org.apache.bcel.generic.MethodGen;
+import org.apache.bcel.generic.Type;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnJre;
+import org.junit.jupiter.api.condition.JRE;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests compact four-byte and standard eight-byte Code headers and their 
preservation during bytecode manipulation.
+ * The sizes cover max_stack, max_locals, and code_length only; the rest of 
the Code attribute layout is unchanged.
+ */
+class CompactCodeHeaderTest {
+
+    private static final class TestLoader extends ClassLoader {
+        Class<?> define(final byte[] bytes) {
+            return defineClass(null, bytes, 0, bytes.length);
+        }
+    }
+
+    private static JavaClass compactHeaderClass() throws IOException {
+        return parse(fixture(), true);
+    }
+
+    private static byte[] dump(final Code code) throws IOException {
+        final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        try (DataOutputStream out = new DataOutputStream(bytes)) {
+            code.dump(out);
+        }
+        assertEquals(code.getLength() + 6, bytes.size());
+        return bytes.toByteArray();
+    }
+
+    private static byte[] fixture() throws IOException {
+        return 
Files.readAllBytes(Paths.get("src/test/resources/Java1OakExample.class"));
+    }
+
+    private static Code mainCode(final JavaClass clazz) {
+        return Arrays.stream(clazz.getMethods()).filter(method -> 
"main".equals(method.getName())).findFirst().get().getCode();
+    }
+
+    private static JavaClass parse(final byte[] bytes, final boolean 
compactCodeHeader) throws IOException {
+        final ClassParser parser = new ClassParser(new 
ByteArrayInputStream(bytes), "Java1OakExample.class");
+        assertFalse(parser.isCompactCodeHeaderEnabled());
+        parser.setCompactCodeHeaderEnabled(compactCodeHeader);
+        assertEquals(compactCodeHeader, parser.isCompactCodeHeaderEnabled());
+        return parser.parse();
+    }
+
+    private static Code readCode(final byte[] bytes, final ConstantPool cp, 
final boolean compactCodeHeader) throws IOException {
+        try (DataInputStream in = new DataInputStream(new 
ByteArrayInputStream(bytes))) {
+            final Code code = (Code) Attribute.readAttribute(in, cp, 
compactCodeHeader);
+            assertEquals(0, in.available());
+            return code;
+        }
+    }
+
+    private static JavaClass rebuild(final JavaClass clazz) {
+        final ClassGen cg = new ClassGen(clazz);
+        for (final Method method : cg.getMethods()) {
+            final MethodGen mg = new MethodGen(method, cg.getClassName(), 
cg.getConstantPool());
+            assertTrue(mg.isCompactCodeHeader());
+            cg.replaceMethod(method, mg.getMethod());
+        }
+        return cg.getJavaClass();
+    }
+
+    @Test
+    void testAddedMethodUsesEnclosingCompactCodeHeader() throws IOException {
+        final ClassGen cg = new ClassGen(compactHeaderClass());
+        final Method main = Arrays.stream(cg.getMethods()).filter(method -> 
"main".equals(method.getName())).findFirst().get();
+        final MethodGen mg = new MethodGen(main, cg.getClassName(), 
cg.getConstantPool());
+        mg.setName("anotherMethod");
+        mg.setCompactCodeHeader(false);
+        final Method added = mg.getMethod();
+        cg.addMethod(added);
+        final JavaClass parsed = parse(cg.getJavaClass().getBytes(), true);
+        assertEquals(3, parsed.getMethods().length);
+        for (final Method method : parsed.getMethods()) {
+            assertTrue(method.getCode().isCompactCodeHeader());
+        }
+        assertFalse(added.getCode().isCompactCodeHeader(), "Dump must not 
modify shared method attributes");
+        added.getCode().setMaxLocals(256);
+        assertThrows(ClassFormatException.class, () -> 
cg.getJavaClass().getBytes());
+    }
+
+    @Test
+    void testAttributeLayoutSelection() throws IOException {
+        final Code code = mainCode(compactHeaderClass());
+        assertArrayEquals(dump(code), dump(readCode(dump(code), 
code.getConstantPool(), true)));
+        code.setCompactCodeHeader(false);
+        try (DataInputStream in = new DataInputStream(new 
ByteArrayInputStream(dump(code)))) {
+            final Code parsed = (Code) Attribute.readAttribute(in, 
code.getConstantPool());
+            assertFalse(parsed.isCompactCodeHeader());
+            assertEquals(0, in.available());
+            assertArrayEquals(code.getCode(), parsed.getCode());
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {0, 65535})
+    void testCodeLengthLimits(final int length) throws IOException {
+        final Code code = mainCode(compactHeaderClass());
+        code.setCode(new byte[length]);
+        assertEquals(length, readCode(dump(code), code.getConstantPool(), 
true).getCode().length);
+        code.setCode(new byte[65536]);
+        assertThrows(ClassFormatException.class, () -> dump(code));
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {-1, 256})
+    void testCompactCodeHeaderBounds(final int invalid) throws IOException {
+        final Code code = mainCode(compactHeaderClass());
+        code.setMaxStack(255);
+        code.setMaxLocals(255);
+        final Code parsed = readCode(dump(code), code.getConstantPool(), true);
+        assertEquals(255, parsed.getMaxStack());
+        assertEquals(255, parsed.getMaxLocals());
+        code.setMaxStack(invalid);
+        assertThrows(ClassFormatException.class, () -> dump(code));
+        code.setMaxStack(255);
+        code.setMaxLocals(invalid);
+        assertThrows(ClassFormatException.class, () -> dump(code));
+    }
+
+    @Test
+    void testCompactCodeHeaderValidatesBeforeChangingMode() throws IOException 
{
+        final Code code = mainCode(compactHeaderClass());
+        code.setCompactCodeHeader(false);
+        code.setMaxStack(256);
+        code.setMaxLocals(256);
+        assertEquals(256, readCode(dump(code), code.getConstantPool(), 
false).getMaxStack());
+        final int length = code.getLength();
+        assertThrows(ClassFormatException.class, () -> 
code.setCompactCodeHeader(true));
+        assertFalse(code.isCompactCodeHeader());
+        assertEquals(length, code.getLength());
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {0, 1, 2})
+    void testCompactCodeHeaderVersionsRoundTrip(final int minor) throws 
IOException {
+        final byte[] bytes = fixture();
+        bytes[5] = (byte) minor;
+        final JavaClass clazz = parse(bytes, true);
+        assertEquals(minor, clazz.getMinor());
+        assertArrayEquals(bytes, clazz.getBytes());
+        assertTrue(mainCode(clazz).isCompactCodeHeader());
+    }
+
+    @Test
+    void testCopiesPreserveLayout() throws IOException {
+        final JavaClass original = compactHeaderClass();
+        assertArrayEquals(fixture(), original.copy().getBytes());
+        final Code code = mainCode(original);
+        final Code shallow = new Code(code);
+        final Code deep = (Code) code.copy(code.getConstantPool());
+        assertTrue(shallow.isCompactCodeHeader());
+        assertTrue(deep.isCompactCodeHeader());
+        assertArrayEquals(dump(code), dump(shallow));
+        assertArrayEquals(dump(code), dump(deep));
+        shallow.setCompactCodeHeader(false);
+        assertTrue(code.isCompactCodeHeader());
+    }
+
+    @Test
+    void testFixtureValuesAndReproducibility() throws IOException {
+        assertArrayEquals(Java1OakExampleCreator.create().getBytes(), 
fixture());
+        final JavaClass clazz = compactHeaderClass();
+        assertEquals("Java1OakExample", clazz.getClassName());
+        assertEquals(45, clazz.getMajor());
+        assertEquals(2, clazz.getMinor());
+        assertEquals(2, clazz.getMethods().length);
+        final Code main = mainCode(clazz);
+        assertEquals(2, main.getMaxStack());
+        assertEquals(1, main.getMaxLocals());
+        assertEquals(9, main.getCode().length);
+        assertNotNull(main.getLocalVariableTable());
+        assertEquals(0, main.getExceptionTable().length);
+    }
+
+    @Test
+    void testLengthRecalculation() throws IOException {
+        final Code code = mainCode(compactHeaderClass());
+        code.setAttributes(Attribute.EMPTY_ARRAY);
+        code.setExceptionTable(new CodeException[] {new CodeException(0, 1, 0, 
0)});
+        code.setCode(new byte[] {(byte) Const.RETURN});
+        assertEquals(17, code.getLength());
+        final Code parsed = readCode(dump(code), code.getConstantPool(), true);
+        assertEquals(1, parsed.getExceptionTable().length);
+        assertEquals(1, parsed.getCode().length);
+        code.setCompactCodeHeader(false);
+        assertEquals(21, code.getLength());
+        assertEquals(1, readCode(dump(code), code.getConstantPool(), 
false).getExceptionTable().length);
+        code.setCompactCodeHeader(true);
+        assertEquals(17, code.getLength());
+    }
+
+    @Test
+    void testLocalVariableGrowthFailureDoesNotLeaveAttributes() throws 
IOException {
+        final JavaClass clazz = compactHeaderClass();
+        final Method main = Arrays.stream(clazz.getMethods()).filter(method -> 
"main".equals(method.getName())).findFirst().get();
+        final MethodGen mg = new MethodGen(main, clazz.getClassName(), new 
ConstantPoolGen(clazz.getConstantPool()));
+        mg.addLocalVariable("extra", Type.INT, 255, 
mg.getInstructionList().getStart(), mg.getInstructionList().getEnd());
+        // Rebuilding the local variable table restores the required count of 
256 during getMethod().
+        mg.setMaxLocals(1);
+        assertThrows(ClassFormatException.class, mg::getMethod);
+        assertEquals(0, mg.getCodeAttributes().length, "Failed generation must 
not leave temporary attributes");
+        mg.setCompactCodeHeader(false);
+        final Code code = mg.getMethod().getCode();
+        assertEquals(256, code.getMaxLocals());
+        assertEquals(1, 
Arrays.stream(code.getAttributes()).filter(LocalVariableTable.class::isInstance).count());
+    }
+
+    @Test
+    void testMethodGenPreservesLayout() throws IOException {
+        final JavaClass clazz = compactHeaderClass();
+        final JavaClass rebuilt = parse(rebuild(clazz).getBytes(), true);
+        assertTrue(mainCode(rebuilt).isCompactCodeHeader());
+        assertArrayEquals(mainCode(clazz).getCode(), 
mainCode(rebuilt).getCode());
+        final MethodGen mg = new MethodGen(clazz.getMethods()[0], 
clazz.getClassName(), new ConstantPoolGen(clazz.getConstantPool()));
+        assertTrue(mg.copy(clazz.getClassName(), new 
ConstantPoolGen(clazz.getConstantPool())).getMethod().getCode().isCompactCodeHeader());
+        mg.setMaxStack(256);
+        assertThrows(ClassFormatException.class, mg::getMethod);
+        mg.setCompactCodeHeader(false);
+        assertFalse(mg.getMethod().getCode().isCompactCodeHeader());
+    }
+
+    @Test
+    void testNonCodeAttributesWithCompactCodeHeaders() throws IOException {
+        final ClassGen cg = new ClassGen(compactHeaderClass());
+        final FieldGen fg = new FieldGen(Const.ACC_PUBLIC | Const.ACC_STATIC | 
Const.ACC_FINAL, Type.INT, "VALUE", cg.getConstantPool());
+        fg.setInitValue(42);
+        cg.addField(fg.getField());
+        final JavaClass parsed = parse(cg.getJavaClass().getBytes(), true);
+        assertEquals("42", 
parsed.getFields()[0].getConstantValue().toString());
+        assertEquals("Java1OakExample.java", parsed.getSourceFileName());
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {0, 1, 2, 3})
+    void testStandardCodeHeaderRemainsDefault(final int minor) throws 
IOException {
+        final JavaClass clazz = compactHeaderClass();
+        for (final Method method : clazz.getMethods()) {
+            method.getCode().setCompactCodeHeader(false);
+        }
+        clazz.setMinor(minor);
+        final byte[] bytes = clazz.getBytes();
+        final JavaClass parsed = parse(bytes, false);
+        assertFalse(mainCode(parsed).isCompactCodeHeader());
+        assertEquals(1, mainCode(parsed).getMaxLocals());
+        assertArrayEquals(bytes, parsed.getBytes());
+    }
+
+    @Test
+    void testTruncatedCompactCodeAttribute() throws IOException {
+        final Code code = mainCode(compactHeaderClass());
+        final byte[] bytes = dump(code);
+        assertThrows(EOFException.class, () -> readCode(Arrays.copyOf(bytes, 
bytes.length - 1), code.getConstantPool(), true));
+    }
+
+    @Test
+    @EnabledOnJre(JRE.JAVA_8)
+    void testValidCompactCodeHeaderFixtureAndRegeneratedMethodsLoadInJava8() 
throws Exception {
+        assertNotNull(new 
TestLoader().define(fixture()).getDeclaredMethod("main", String[].class));
+        assertNotNull(new 
TestLoader().define(rebuild(compactHeaderClass()).getBytes()).getDeclaredMethod("main",
 String[].class));
+    }
+
+    @ParameterizedTest
+    @CsvSource({"45, 3", "46, 0"})
+    void testVersionChangesSelectStandardCodeHeaders(final int major, final 
int minor) throws Exception {
+        final JavaClass clazz = compactHeaderClass();
+        clazz.setMajor(major);
+        clazz.setMinor(minor);
+        final byte[] bytes = clazz.getBytes();
+        assertFalse(mainCode(parse(bytes, false)).isCompactCodeHeader());
+        assertFalse(mainCode(parse(bytes, true)).isCompactCodeHeader(), 
"Compact Code header parsing must only apply to versions 45.0 through 45.2");
+        assertTrue(mainCode(clazz).isCompactCodeHeader(), "Dump must not 
mutate the original attributes");
+        assertNotNull(new TestLoader().define(bytes).getDeclaredMethod("main", 
String[].class));
+        final ClassGen cg = new ClassGen(compactHeaderClass());
+        cg.setMajor(major);
+        cg.setMinor(minor);
+        assertFalse(mainCode(parse(cg.getJavaClass().getBytes(), 
false)).isCompactCodeHeader());
+    }
+}
diff --git a/src/test/java/org/apache/bcel/util/BCELifierTest.java 
b/src/test/java/org/apache/bcel/util/BCELifierTest.java
index 40d13258..23ed04d5 100644
--- a/src/test/java/org/apache/bcel/util/BCELifierTest.java
+++ b/src/test/java/org/apache/bcel/util/BCELifierTest.java
@@ -42,6 +42,7 @@ import org.apache.bcel.classfile.ClassParser;
 import org.apache.bcel.classfile.ConstantValue;
 import org.apache.bcel.classfile.Field;
 import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.classfile.Method;
 import org.apache.bcel.classfile.Utility;
 import org.apache.bcel.generic.BinaryOpCreator;
 import org.apache.bcel.generic.ClassGen;
@@ -57,6 +58,7 @@ import org.apache.commons.lang3.SystemUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledForJreRange;
 import org.junit.jupiter.api.condition.JRE;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -308,6 +310,36 @@ class BCELifierTest extends AbstractTest {
         }
     }
 
+    @ParameterizedTest
+    @CsvSource({"true, 45, 2", "false, 45, 2", "true, 45, 3", "true, 46, 0"})
+    void testCompactCodeHeaderRoundTrip(final boolean compactCodeHeader, final 
int major, final int minor, @TempDir final Path directory) throws Exception {
+        final ClassParser parser = new 
ClassParser("src/test/resources/Java1OakExample.class");
+        parser.setCompactCodeHeaderEnabled(true);
+        final JavaClass original = parser.parse();
+        original.setMajor(major);
+        original.setMinor(minor);
+        for (final Method method : original.getMethods()) {
+            method.getCode().setCompactCodeHeader(compactCodeHeader);
+        }
+        final ByteArrayOutputStream source = new ByteArrayOutputStream();
+        new BCELifier(original, source).start();
+        Files.write(directory.resolve("Java1OakExampleCreator.java"), 
source.toByteArray());
+        final File workDir = directory.toFile();
+        exec(workDir, getAppJavaC(), "-source", "8", "-target", "8", "-cp", 
CLASSPATH, "Java1OakExampleCreator.java");
+        exec(workDir, getAppJava(), "-cp", CLASSPATH, 
"Java1OakExampleCreator");
+        final boolean expectedCompactCodeHeader = compactCodeHeader && major 
== 45 && minor < 3;
+        final ClassParser generatedParser = new 
ClassParser(directory.resolve("Java1OakExample.class").toString());
+        generatedParser.setCompactCodeHeaderEnabled(expectedCompactCodeHeader);
+        final JavaClass generated = generatedParser.parse();
+        assertEquals(major, generated.getMajor());
+        assertEquals(minor, generated.getMinor());
+        assertEquals(original.getMethods().length, 
generated.getMethods().length);
+        for (final Method method : generated.getMethods()) {
+            assertEquals(expectedCompactCodeHeader, 
method.getCode().isCompactCodeHeader());
+            assertEquals(1, method.getCode().getMaxLocals());
+        }
+    }
+
     @Test
     void testCreateInvokeEscapesConstantPoolName() throws Exception {
         // A hostile constant pool can hold any UTF-8 as a referenced method 
name.
diff --git a/src/test/resources/Java1OakExample.class 
b/src/test/resources/Java1OakExample.class
new file mode 100644
index 00000000..b179a41b
Binary files /dev/null and b/src/test/resources/Java1OakExample.class differ
diff --git a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java 
b/src/test/resources/Java1OakExample.java
similarity index 68%
copy from src/test/java/org/apache/bcel/classfile/ClassParserTest.java
copy to src/test/resources/Java1OakExample.java
index f16d0c91..3c2114ee 100644
--- a/src/test/java/org/apache/bcel/classfile/ClassParserTest.java
+++ b/src/test/resources/Java1OakExample.java
@@ -17,19 +17,11 @@
  * under the License.
  */
 
-package org.apache.bcel.classfile;
-
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.junit.jupiter.api.Test;
-
 /**
- * Tests {@link ClassParser}.
+ * Source equivalent of the fixture with compact Code headers generated by 
org.apache.bcel.Java1OakExampleCreator.
  */
-class ClassParserTest {
-
-    @Test
-    void testParseInvalidClass() {
-        assertThrows(ClassFormatException.class, () -> new 
ClassParser("src/test/resources/org/apache/commons/bcel/classfile/CodeCtor.class").parse());
+public class Java1OakExample {
+    public static void main(final String[] args) {
+        System.out.println("Hello Oak!");
     }
 }

Reply via email to