This is an automated email from the ASF dual-hosted git repository. ggregory 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 d0996540 [Bcelifier] stackmap support to pass JDK verifier (#177) d0996540 is described below commit d0996540e2574d6c8c537ee6f493d5f35e25b206 Author: nbauma109 <nbauma...@users.noreply.github.com> AuthorDate: Tue Apr 25 19:43:06 2023 +0200 [Bcelifier] stackmap support to pass JDK verifier (#177) * Bcelifier stackmap support verifier * Bcelifier stackmap support * BCELifier generates the stack map table so that the generated class passes the Java verifier checks. * added some test inputs StackMapExample and StackMapExample2 compiled in JDK8 * Updated BCELifierTestCase.java: added testStackMap * StackMapType.EMPTY_ARRAY in package visibility and updated BinaryOpCreator.java accordingly Co-authored-by: nbauma109 <nbauma...@github.com> * Re-integrate lost fix on possible null pointer * Fixing visibility of EMPTY_ARRAY * Comment for StackMapExample2 * Javadoc fix * Add StackMapTest * Fix Javadoc since tag * Fix Javadoc since tag * Fix Javadoc since tag * Fix Javadoc since tag * Fix Javadoc since tag * Don't break BC * Whitespace * Javadoc --------- Co-authored-by: nbauma109 <nbauma...@github.com> Co-authored-by: Gary David Gregory (Code signing key) <ggreg...@apache.org> Co-authored-by: Gary Gregory <garydgreg...@users.noreply.github.com> --- src/main/java/org/apache/bcel/classfile/Code.java | 14 ++++ .../apache/bcel/classfile/DescendingVisitor.java | 14 ++++ .../org/apache/bcel/classfile/EmptyVisitor.java | 9 +++ .../org/apache/bcel/classfile/StackMapType.java | 27 ++++++- .../java/org/apache/bcel/classfile/Visitor.java | 9 +++ src/main/java/org/apache/bcel/util/BCELifier.java | 86 +++++++++++++++++++++ .../org/apache/bcel/CounterVisitorTestCase.java | 5 ++ .../org/apache/bcel/generic/BinaryOpCreator.java | 12 +-- .../org/apache/bcel/util/BCELifierTestCase.java | 10 ++- .../org/apache/bcel/visitors/CountingVisitor.java | 11 +++ src/test/resources/StackMapExample.class | Bin 0 -> 790 bytes src/test/resources/StackMapExample.java | 18 +++++ src/test/resources/StackMapExample2.class | Bin 0 -> 673 bytes src/test/resources/StackMapExample2.java | 12 +++ 14 files changed, 218 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/bcel/classfile/Code.java b/src/main/java/org/apache/bcel/classfile/Code.java index 5e12d00d..4d2dea39 100644 --- a/src/main/java/org/apache/bcel/classfile/Code.java +++ b/src/main/java/org/apache/bcel/classfile/Code.java @@ -247,6 +247,20 @@ public final class Code extends Attribute { return null; } + /** + * Finds the attribute of {@link StackMap} instance. + * @return StackMap of Code, if it has one, else null. + * @since 6.8.0 + */ + public StackMap getStackMap() { + for (final Attribute attribute : attributes) { + if (attribute instanceof StackMap) { + return (StackMap) attribute; + } + } + return null; + } + /** * @return LocalVariableTable of Code, if it has one */ diff --git a/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java b/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java index 5197f2ef..c893a394 100644 --- a/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java +++ b/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java @@ -526,6 +526,20 @@ public class DescendingVisitor implements Visitor { @Override public void visitStackMapEntry(final StackMapEntry var) { + stack.push(var); + var.accept(visitor); + accept(var.getTypesOfLocals()); + accept(var.getTypesOfStackItems()); + stack.pop(); + } + + /** + * Visits a {@link StackMapType} object. + * @param var object to visit + * @since 6.8.0 + */ + @Override + public void visitStackMapType(final StackMapType var) { stack.push(var); var.accept(visitor); stack.pop(); diff --git a/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java b/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java index b817ad9f..5eaaa968 100644 --- a/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java +++ b/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java @@ -310,6 +310,15 @@ public class EmptyVisitor implements Visitor { public void visitStackMapEntry(final StackMapEntry obj) { } + /** + * Visits a {@link StackMapType} object. + * @param obj object to visit + * @since 6.8.0 + */ + @Override + public void visitStackMapType(final StackMapType obj) { + } + @Override public void visitSynthetic(final Synthetic obj) { } diff --git a/src/main/java/org/apache/bcel/classfile/StackMapType.java b/src/main/java/org/apache/bcel/classfile/StackMapType.java index e43fd386..3f3fc50c 100644 --- a/src/main/java/org/apache/bcel/classfile/StackMapType.java +++ b/src/main/java/org/apache/bcel/classfile/StackMapType.java @@ -29,9 +29,9 @@ import org.apache.bcel.Const; * @see StackMap * @see Const */ -public final class StackMapType implements Cloneable { +public final class StackMapType implements Node, Cloneable { - public static final StackMapType[] EMPTY_ARRAY = {}; // must be public because BCELifier code generator writes calls to it + public static final StackMapType[] EMPTY_ARRAY = {}; // BCELifier code generator writes calls to constructor translating null to EMPTY_ARRAY private byte type; private int index = -1; // Index to CONSTANT_Class or offset @@ -61,6 +61,18 @@ public final class StackMapType implements Cloneable { this.constantPool = constantPool; } + /** + * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. + * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. + * + * @param v Visitor object + * @since 6.8.0 + */ + @Override + public void accept(final Visitor v) { + v.visitStackMapType(this); + } + private byte checkType(final byte type) { if (type < Const.ITEM_Bogus || type > Const.ITEM_NewObject) { throw new ClassFormatException("Illegal type for StackMapType: " + type); @@ -124,7 +136,7 @@ public final class StackMapType implements Cloneable { if (index < 0) { return ", class=<unknown>"; } - return ", class=" + constantPool.constantToString(index, Const.CONSTANT_Class); + return ", class=" + getClassName(); } if (type == Const.ITEM_NewObject) { return ", offset=" + index; @@ -132,6 +144,15 @@ public final class StackMapType implements Cloneable { return ""; } + /** + * Gets the class name of this StackMapType from the constant pool at index position. + * @return the fully qualified name of the class for this StackMapType. + * @since 6.8.0 + */ + public String getClassName() { + return constantPool.constantToString(index, Const.CONSTANT_Class); + } + /** * @param constantPool Constant pool to be used for this object. */ diff --git a/src/main/java/org/apache/bcel/classfile/Visitor.java b/src/main/java/org/apache/bcel/classfile/Visitor.java index b4670705..9a3388cf 100644 --- a/src/main/java/org/apache/bcel/classfile/Visitor.java +++ b/src/main/java/org/apache/bcel/classfile/Visitor.java @@ -225,6 +225,15 @@ public interface Visitor { void visitStackMapEntry(StackMapEntry obj); + /** + * Visits a {@link StackMapType} object. + * @param obj object to visit + * @since 6.8.0 + */ + default void visitStackMapType(StackMapType obj) { + // empty + } + void visitSynthetic(Synthetic obj); void visitUnknown(Unknown obj); diff --git a/src/main/java/org/apache/bcel/util/BCELifier.java b/src/main/java/org/apache/bcel/util/BCELifier.java index f719146e..bdf8d57e 100644 --- a/src/main/java/org/apache/bcel/util/BCELifier.java +++ b/src/main/java/org/apache/bcel/util/BCELifier.java @@ -26,16 +26,21 @@ import java.util.Locale; import org.apache.bcel.Const; import org.apache.bcel.Repository; import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.ConstantValue; import org.apache.bcel.classfile.ExceptionTable; import org.apache.bcel.classfile.Field; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; +import org.apache.bcel.classfile.StackMap; +import org.apache.bcel.classfile.StackMapEntry; +import org.apache.bcel.classfile.StackMapType; import org.apache.bcel.classfile.Utility; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.Type; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; /** @@ -306,6 +311,13 @@ public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor { printWriter.println("\");"); } } + final Code code = method.getCode(); + if (code != null) { + final StackMap stackMap = code.getStackMap(); + if (stackMap != null) { + stackMap.accept(this); + } + } printWriter.println(); final BCELFactory factory = new BCELFactory(mg, printWriter); factory.start(); @@ -314,4 +326,78 @@ public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor { printWriter.println(" _cg.addMethod(method.getMethod());"); printWriter.println(" il.dispose();"); } + + @Override + public void visitStackMap(final StackMap stackMap) { + super.visitStackMap(stackMap); + printWriter.print(" method.addCodeAttribute("); + printWriter.print("new StackMap(_cp.addUtf8(\""); + printWriter.print(stackMap.getName()); + printWriter.print("\"), "); + printWriter.print(stackMap.getLength()); + printWriter.print(", "); + printWriter.print("new StackMapEntry[] {"); + final StackMapEntry[] table = stackMap.getStackMap(); + for (int i = 0; i < table.length; i++) { + table[i].accept(this); + if (i < table.length - 1) { + printWriter.print(", "); + } else { + printWriter.print(" }"); + } + } + printWriter.print(", _cp.getConstantPool())"); + printWriter.println(");"); + } + + @Override + public void visitStackMapEntry(final StackMapEntry stackMapEntry) { + super.visitStackMapEntry(stackMapEntry); + printWriter.print("new StackMapEntry("); + printWriter.print(stackMapEntry.getFrameType()); + printWriter.print(", "); + printWriter.print(stackMapEntry.getByteCodeOffset()); + printWriter.print(", "); + visitStackMapTypeArray(stackMapEntry.getTypesOfLocals()); + printWriter.print(", "); + visitStackMapTypeArray(stackMapEntry.getTypesOfStackItems()); + printWriter.print(", _cp.getConstantPool())"); + } + + /** + * Visits a {@link StackMapType} object. + * @param stackMapType object to visit + * @since 6.7.1 + */ + @Override + public void visitStackMapType(final StackMapType stackMapType) { + super.visitStackMapType(stackMapType); + printWriter.print("new StackMapType((byte)"); + printWriter.print(stackMapType.getType()); + printWriter.print(", "); + if (stackMapType.hasIndex()) { + printWriter.print("_cp.addClass(\""); + printWriter.print(stackMapType.getClassName()); + printWriter.print("\")"); + } else { + printWriter.print("-1"); + } + printWriter.print(", _cp.getConstantPool())"); + } + + private void visitStackMapTypeArray(final StackMapType[] types) { + if (ArrayUtils.isEmpty(types)) { + printWriter.print("null"); // null translates to StackMapType.EMPTY_ARRAY + } else { + printWriter.print("new StackMapType[] {"); + for (int i = 0; i < types.length; i++) { + types[i].accept(this); + if (i < types.length -1) { + printWriter.print(", "); + } else { + printWriter.print(" }"); + } + } + } + } } diff --git a/src/test/java/org/apache/bcel/CounterVisitorTestCase.java b/src/test/java/org/apache/bcel/CounterVisitorTestCase.java index 0cdc69f1..d1fcfa5e 100644 --- a/src/test/java/org/apache/bcel/CounterVisitorTestCase.java +++ b/src/test/java/org/apache/bcel/CounterVisitorTestCase.java @@ -203,6 +203,11 @@ public class CounterVisitorTestCase extends AbstractCounterVisitorTestCase { assertEquals(0, getVisitor().stackMapEntryCount, "stackMapEntryCount"); } + @Test + public void testStackMapTypeCount() { + assertEquals(0, getVisitor().stackMapTypeCount, "stackMapTypeCount"); + } + @Test public void testSyntheticCount() { assertEquals(0, getVisitor().syntheticCount, "syntheticCount"); diff --git a/src/test/java/org/apache/bcel/generic/BinaryOpCreator.java b/src/test/java/org/apache/bcel/generic/BinaryOpCreator.java index 3d372b8f..6450445b 100644 --- a/src/test/java/org/apache/bcel/generic/BinaryOpCreator.java +++ b/src/test/java/org/apache/bcel/generic/BinaryOpCreator.java @@ -115,11 +115,13 @@ public class BinaryOpCreator { "calculate", ORG_APACHE_BCEL_GENERIC_BINARY_OP, il, cp); method.addException("java.lang.Exception"); final StackMapType[] typesOfLocals = { new StackMapType((byte) 7, cp.addClass("java.lang.String"), cp.getConstantPool()) }; - final StackMapEntry[] table = { new StackMapEntry(252, 70, typesOfLocals, StackMapType.EMPTY_ARRAY, cp.getConstantPool()), - new StackMapEntry(251, 65, StackMapType.EMPTY_ARRAY, StackMapType.EMPTY_ARRAY, cp.getConstantPool()), - new StackMapEntry(251, 65, StackMapType.EMPTY_ARRAY, StackMapType.EMPTY_ARRAY, cp.getConstantPool()), - new StackMapEntry(251, 65, StackMapType.EMPTY_ARRAY, StackMapType.EMPTY_ARRAY, cp.getConstantPool()) }; - method.addCodeAttribute(new StackMap(cp.addUtf8("StackMapTable"), 17, table, cp.getConstantPool())); + final StackMapEntry[] table = { new StackMapEntry(252, 70, typesOfLocals, null, cp.getConstantPool()), + new StackMapEntry(251, 65, null, null, cp.getConstantPool()), + new StackMapEntry(251, 65, null, null, cp.getConstantPool()), + new StackMapEntry(251, 65, null, null, cp.getConstantPool()) }; + final StackMap stackMap = new StackMap(cp.addUtf8("StackMapTable"), 17, table, cp.getConstantPool()); + stackMap.setStackMap(table); + method.addCodeAttribute(stackMap); il.append(InstructionFactory.createLoad(Type.OBJECT, 1)); il.append(new PUSH(cp, 0)); diff --git a/src/test/java/org/apache/bcel/util/BCELifierTestCase.java b/src/test/java/org/apache/bcel/util/BCELifierTestCase.java index 24d2eb2e..a856c2cd 100644 --- a/src/test/java/org/apache/bcel/util/BCELifierTestCase.java +++ b/src/test/java/org/apache/bcel/util/BCELifierTestCase.java @@ -42,7 +42,7 @@ import org.junit.jupiter.params.provider.ValueSource; public class BCELifierTestCase extends AbstractTestCase { private static final String EOL = System.lineSeparator(); - public static final String CLASSPATH = System.getProperty("java.class.path") + File.pathSeparator + "."; + public static final String CLASSPATH = "." + File.pathSeparator + System.getProperty("java.class.path"); // Canonicalise the javap output so it compares better private String canonHashRef(String input) { @@ -216,4 +216,12 @@ public class BCELifierTestCase extends AbstractTestCase { final BCELifier bcelifier = new BCELifier(javaClass, os); bcelifier.start(); } + + @ParameterizedTest + @ValueSource(strings = { "StackMapExample", "StackMapExample2" }) + public void testStackMap(final String className) throws Exception { + testJavapCompare(className); + final File workDir = new File("target"); + assertEquals("Hello World" + EOL, exec(workDir, "java", "-cp", CLASSPATH, className, "Hello")); + } } diff --git a/src/test/java/org/apache/bcel/visitors/CountingVisitor.java b/src/test/java/org/apache/bcel/visitors/CountingVisitor.java index 8a350385..3522df26 100644 --- a/src/test/java/org/apache/bcel/visitors/CountingVisitor.java +++ b/src/test/java/org/apache/bcel/visitors/CountingVisitor.java @@ -72,6 +72,7 @@ import org.apache.bcel.classfile.Signature; import org.apache.bcel.classfile.SourceFile; import org.apache.bcel.classfile.StackMap; import org.apache.bcel.classfile.StackMapEntry; +import org.apache.bcel.classfile.StackMapType; import org.apache.bcel.classfile.Synthetic; import org.apache.bcel.classfile.Unknown; import org.apache.bcel.classfile.Visitor; @@ -84,6 +85,8 @@ public class CountingVisitor implements Visitor { public int stackMapEntryCount; + public int stackMapTypeCount; + public int stackMapCount; public int sourceFileCount; @@ -497,6 +500,14 @@ public class CountingVisitor implements Visitor { stackMapEntryCount++; } + /** + * @since 6.8.0 + */ + @Override + public void visitStackMapType(final StackMapType obj) { + stackMapTypeCount++; + } + @Override public void visitSynthetic(final Synthetic obj) { syntheticCount++; diff --git a/src/test/resources/StackMapExample.class b/src/test/resources/StackMapExample.class new file mode 100644 index 00000000..62066fd6 Binary files /dev/null and b/src/test/resources/StackMapExample.class differ diff --git a/src/test/resources/StackMapExample.java b/src/test/resources/StackMapExample.java new file mode 100644 index 00000000..f4fc3168 --- /dev/null +++ b/src/test/resources/StackMapExample.java @@ -0,0 +1,18 @@ +/* + * Example to check if BCELifier generates the stack map table + * so that the generated class passes the Java verifier checks. + */ +public abstract class StackMapExample { + + protected abstract void someAbstractMethod(); + + public static void main(final String[] args) { + switch (args[0]) { + case "Hello": + System.out.println("Hello World"); + break; + default: + break; + } + } +} diff --git a/src/test/resources/StackMapExample2.class b/src/test/resources/StackMapExample2.class new file mode 100644 index 00000000..c449a193 Binary files /dev/null and b/src/test/resources/StackMapExample2.class differ diff --git a/src/test/resources/StackMapExample2.java b/src/test/resources/StackMapExample2.java new file mode 100644 index 00000000..441f678e --- /dev/null +++ b/src/test/resources/StackMapExample2.java @@ -0,0 +1,12 @@ +/* + * Another version of StackMapExample using 2 types of locals String and int + * instead of just String. + */ +public class StackMapExample2 { + + public static void main(String[] args) { + if (args.length == 1 && "Hello".equals(args[0])) { + System.out.println("Hello World"); + } + } +}