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 <[email protected]>
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 <[email protected]>
* 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 <[email protected]>
Co-authored-by: Gary David Gregory (Code signing key) <[email protected]>
Co-authored-by: Gary Gregory <[email protected]>
---
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");
+ }
+ }
+}