";
/** A pseudo access flag used to distinguish class access flags. */
private static final int ACCESS_CLASS = 0x40000;
@@ -105,6 +105,12 @@ public class ASMifier extends Printer {
classVersions.put(Opcodes.V12, "V12");
classVersions.put(Opcodes.V13, "V13");
classVersions.put(Opcodes.V14, "V14");
+ classVersions.put(Opcodes.V15, "V15");
+ classVersions.put(Opcodes.V16, "V16");
+ classVersions.put(Opcodes.V17, "V17");
+ classVersions.put(Opcodes.V18, "V18");
+ classVersions.put(Opcodes.V19, "V19");
+ classVersions.put(Opcodes.V20, "V20");
CLASS_VERSIONS = Collections.unmodifiableMap(classVersions);
}
@@ -124,7 +130,7 @@ public class ASMifier extends Printer {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public ASMifier() {
- this(/* latest api = */ Opcodes.ASM7, "classWriter", 0);
+ this(/* latest api = */ Opcodes.ASM9, "classWriter", 0);
if (getClass() != ASMifier.class) {
throw new IllegalStateException();
}
@@ -133,8 +139,8 @@ public ASMifier() {
/**
* Constructs a new {@link ASMifier}.
*
- * @param api the ASM API version implemented by this class. Must be one of {@link Opcodes#ASM4},
- * {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this class. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param visitorVariableName the name of the visitor variable in the produced code.
* @param annotationVisitorId identifier of the annotation visitor variable in the produced code.
*/
@@ -148,7 +154,7 @@ protected ASMifier(
/**
* Prints the ASM source code to generate the given class to the standard output.
*
- * Usage: ASMifier [-debug] <binary class name or class file name>
+ *
Usage: ASMifier [-nodebug] <binary class name or class file name>
*
* @param args the command line arguments.
* @throws IOException if the class cannot be found, or if an IOException occurs.
@@ -160,7 +166,7 @@ public static void main(final String[] args) throws IOException {
/**
* Prints the ASM source code to generate the given class to the given output.
*
- *
Usage: ASMifier [-debug] <binary class name or class file name>
+ *
Usage: ASMifier [-nodebug] <binary class name or class file name>
*
* @param args the command line arguments.
* @param output where to print the result.
@@ -206,12 +212,14 @@ public void visit(
text.add("import nginx.clojure.asm.Label;\n");
text.add("import nginx.clojure.asm.MethodVisitor;\n");
text.add("import nginx.clojure.asm.Opcodes;\n");
+ text.add("import nginx.clojure.asm.RecordComponentVisitor;\n");
text.add("import nginx.clojure.asm.Type;\n");
text.add("import nginx.clojure.asm.TypePath;\n");
text.add("public class " + simpleName + "Dump implements Opcodes {\n\n");
text.add("public static byte[] dump () throws Exception {\n\n");
text.add("ClassWriter classWriter = new ClassWriter(0);\n");
text.add("FieldVisitor fieldVisitor;\n");
+ text.add("RecordComponentVisitor recordComponentVisitor;\n");
text.add("MethodVisitor methodVisitor;\n");
text.add("AnnotationVisitor annotationVisitor0;\n\n");
@@ -260,6 +268,7 @@ public void visitSource(final String file, final String debug) {
@Override
public Printer visitModule(final String name, final int flags, final String version) {
stringBuilder.setLength(0);
+ stringBuilder.append("{\n");
stringBuilder.append("ModuleVisitor moduleVisitor = classWriter.visitModule(");
appendConstant(name);
stringBuilder.append(", ");
@@ -322,10 +331,10 @@ public void visitNestMember(final String nestMember) {
}
@Override
- public void visitPermittedSubtypeExperimental(final String visitPermittedSubtype) {
+ public void visitPermittedSubclass(final String permittedSubclass) {
stringBuilder.setLength(0);
- stringBuilder.append("classWriter.visitPermittedSubtypeExperimental(");
- appendConstant(visitPermittedSubtype);
+ stringBuilder.append("classWriter.visitPermittedSubclass(");
+ appendConstant(permittedSubclass);
stringBuilder.append(END_PARAMETERS);
text.add(stringBuilder.toString());
}
@@ -346,6 +355,25 @@ public void visitInnerClass(
text.add(stringBuilder.toString());
}
+ @Override
+ public ASMifier visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ stringBuilder.setLength(0);
+ stringBuilder.append("{\n");
+ stringBuilder.append("recordComponentVisitor = classWriter.visitRecordComponent(");
+ appendConstant(name);
+ stringBuilder.append(", ");
+ appendConstant(descriptor);
+ stringBuilder.append(", ");
+ appendConstant(signature);
+ stringBuilder.append(");\n");
+ text.add(stringBuilder.toString());
+ ASMifier asmifier = createASMifier("recordComponentVisitor", 0);
+ text.add(asmifier.getText());
+ text.add("}\n");
+ return asmifier;
+ }
+
@Override
public ASMifier visitField(
final int access,
@@ -583,6 +611,31 @@ public void visitAnnotationEnd() {
text.add(stringBuilder.toString());
}
+ // -----------------------------------------------------------------------------------------------
+ // Record components
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public ASMifier visitRecordComponentAnnotation(final String descriptor, final boolean visible) {
+ return visitAnnotation(descriptor, visible);
+ }
+
+ @Override
+ public ASMifier visitRecordComponentTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ }
+
+ @Override
+ public void visitRecordComponentAttribute(final Attribute attribute) {
+ visitAttribute(attribute);
+ }
+
+ @Override
+ public void visitRecordComponentEnd() {
+ visitMemberEnd();
+ }
+
// -----------------------------------------------------------------------------------------------
// Fields
// -----------------------------------------------------------------------------------------------
@@ -605,9 +658,7 @@ public void visitFieldAttribute(final Attribute attribute) {
@Override
public void visitFieldEnd() {
- stringBuilder.setLength(0);
- stringBuilder.append(name).append(VISIT_END);
- text.add(stringBuilder.toString());
+ visitMemberEnd();
}
// -----------------------------------------------------------------------------------------------
@@ -774,14 +825,14 @@ public void visitIntInsn(final int opcode, final int operand) {
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
stringBuilder.setLength(0);
stringBuilder
.append(name)
.append(".visitVarInsn(")
.append(OPCODES[opcode])
.append(", ")
- .append(var)
+ .append(varIndex)
.append(");\n");
text.add(stringBuilder.toString());
}
@@ -887,12 +938,12 @@ public void visitLdcInsn(final Object value) {
}
@Override
- public void visitIincInsn(final int var, final int increment) {
+ public void visitIincInsn(final int varIndex, final int increment) {
stringBuilder.setLength(0);
stringBuilder
.append(name)
.append(".visitIincInsn(")
- .append(var)
+ .append(varIndex)
.append(", ")
.append(increment)
.append(");\n");
@@ -1080,9 +1131,7 @@ public void visitMaxs(final int maxStack, final int maxLocals) {
@Override
public void visitMethodEnd() {
- stringBuilder.setLength(0);
- stringBuilder.append(name).append(VISIT_END);
- text.add(stringBuilder.toString());
+ visitMemberEnd();
}
// -----------------------------------------------------------------------------------------------
@@ -1154,9 +1203,9 @@ public ASMifier visitTypeAnnotation(
.append("{\n")
.append(ANNOTATION_VISITOR0)
.append(name)
- .append(".")
+ .append('.')
.append(method)
- .append("(")
+ .append('(')
.append(typeRef);
if (typePath == null) {
stringBuilder.append(", null, ");
@@ -1192,6 +1241,13 @@ public void visitAttribute(final Attribute attribute) {
text.add(stringBuilder.toString());
}
+ /** Visits the end of a field, record component or method. */
+ private void visitMemberEnd() {
+ stringBuilder.setLength(0);
+ stringBuilder.append(name).append(VISIT_END);
+ text.add(stringBuilder.toString());
+ }
+
// -----------------------------------------------------------------------------------------------
// Utility methods
// -----------------------------------------------------------------------------------------------
@@ -1232,11 +1288,7 @@ private void appendAccessFlags(final int accessFlags) {
if (!isEmpty) {
stringBuilder.append(" | ");
}
- if ((accessFlags & ACCESS_MODULE) == 0) {
- stringBuilder.append("ACC_FINAL");
- } else {
- stringBuilder.append("ACC_TRANSITIVE");
- }
+ stringBuilder.append("ACC_FINAL");
isEmpty = false;
}
if ((accessFlags & Opcodes.ACC_STATIC) != 0) {
@@ -1352,6 +1404,13 @@ private void appendAccessFlags(final int accessFlags) {
stringBuilder.append("ACC_DEPRECATED");
isEmpty = false;
}
+ if ((accessFlags & Opcodes.ACC_RECORD) != 0) {
+ if (!isEmpty) {
+ stringBuilder.append(" | ");
+ }
+ stringBuilder.append("ACC_RECORD");
+ isEmpty = false;
+ }
if ((accessFlags & (Opcodes.ACC_MANDATED | Opcodes.ACC_MODULE)) != 0) {
if (!isEmpty) {
stringBuilder.append(" | ");
@@ -1391,7 +1450,7 @@ protected void appendConstant(final Object value) {
stringBuilder.append(handle.getOwner()).append(COMMA);
stringBuilder.append(handle.getName()).append(COMMA);
stringBuilder.append(handle.getDesc()).append("\", ");
- stringBuilder.append(handle.isInterface()).append(")");
+ stringBuilder.append(handle.isInterface()).append(')');
} else if (value instanceof ConstantDynamic) {
stringBuilder.append("new ConstantDynamic(\"");
ConstantDynamic constantDynamic = (ConstantDynamic) value;
diff --git a/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java b/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java
index cae8dd8a..2931e37f 100644
--- a/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java
@@ -52,7 +52,7 @@ public CheckAnnotationAdapter(final AnnotationVisitor annotationVisitor) {
}
CheckAnnotationAdapter(final AnnotationVisitor annotationVisitor, final boolean useNamedValues) {
- super(/* latest api = */ Opcodes.ASM7, annotationVisitor);
+ super(/* latest api = */ Opcodes.ASM9, annotationVisitor);
this.useNamedValue = useNamedValues;
}
diff --git a/src/java/nginx/clojure/asm/util/CheckClassAdapter.java b/src/java/nginx/clojure/asm/util/CheckClassAdapter.java
index 1cef2124..21de6a34 100644
--- a/src/java/nginx/clojure/asm/util/CheckClassAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckClassAdapter.java
@@ -40,11 +40,13 @@
import nginx.clojure.asm.Attribute;
import nginx.clojure.asm.ClassReader;
import nginx.clojure.asm.ClassVisitor;
+import nginx.clojure.asm.ClassWriter;
import nginx.clojure.asm.FieldVisitor;
import nginx.clojure.asm.Label;
import nginx.clojure.asm.MethodVisitor;
import nginx.clojure.asm.ModuleVisitor;
import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.RecordComponentVisitor;
import nginx.clojure.asm.Type;
import nginx.clojure.asm.TypePath;
import nginx.clojure.asm.TypeReference;
@@ -161,7 +163,7 @@ public class CheckClassAdapter extends ClassVisitor {
* @param classVisitor the class visitor to which this adapter must delegate calls.
*/
public CheckClassAdapter(final ClassVisitor classVisitor) {
- this(classVisitor, true);
+ this(classVisitor, /* checkDataFlow = */ true);
}
/**
@@ -169,12 +171,11 @@ public CheckClassAdapter(final ClassVisitor classVisitor) {
* Instead, they must use the {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version.
*
* @param classVisitor the class visitor to which this adapter must delegate calls.
- * @param checkDataFlow whether to perform basic data flow checks. This option requires valid
- * maxLocals and maxStack values.
+ * @param checkDataFlow whether to perform basic data flow checks.
* @throws IllegalStateException If a subclass calls this constructor.
*/
public CheckClassAdapter(final ClassVisitor classVisitor, final boolean checkDataFlow) {
- this(/* latest api = */ Opcodes.ASM7, classVisitor, checkDataFlow);
+ this(/* latest api = */ Opcodes.ASM9, classVisitor, checkDataFlow);
if (getClass() != CheckClassAdapter.class) {
throw new IllegalStateException();
}
@@ -183,12 +184,11 @@ public CheckClassAdapter(final ClassVisitor classVisitor, final boolean checkDat
/**
* Constructs a new {@link CheckClassAdapter}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param classVisitor the class visitor to which this adapter must delegate calls.
* @param checkDataFlow {@literal true} to perform basic data flow checks, or {@literal false} to
- * not perform any data flow check (see {@link CheckMethodAdapter}). This option requires
- * valid maxLocals and maxStack values.
+ * not perform any data flow check (see {@link CheckMethodAdapter}).
*/
protected CheckClassAdapter(
final int api, final ClassVisitor classVisitor, final boolean checkDataFlow) {
@@ -225,6 +225,7 @@ public void visit(
| Opcodes.ACC_ANNOTATION
| Opcodes.ACC_ENUM
| Opcodes.ACC_DEPRECATED
+ | Opcodes.ACC_RECORD
| Opcodes.ACC_MODULE);
if (name == null) {
throw new IllegalArgumentException("Illegal class name (null)");
@@ -321,10 +322,10 @@ public void visitNestMember(final String nestMember) {
}
@Override
- public void visitPermittedSubtypeExperimental(final String permittedSubtype) {
+ public void visitPermittedSubclass(final String permittedSubclass) {
checkState();
- CheckMethodAdapter.checkInternalName(version, permittedSubtype, "permittedSubtype");
- super.visitPermittedSubtypeExperimental(permittedSubtype);
+ CheckMethodAdapter.checkInternalName(version, permittedSubclass, "permittedSubclass");
+ super.visitPermittedSubclass(permittedSubclass);
}
@Override
@@ -375,6 +376,19 @@ public void visitInnerClass(
super.visitInnerClass(name, outerName, innerName, access);
}
+ @Override
+ public RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ checkState();
+ CheckMethodAdapter.checkUnqualifiedName(version, name, "record component name");
+ CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid = */ false);
+ if (signature != null) {
+ checkFieldSignature(signature);
+ }
+ return new CheckRecordComponentAdapter(
+ api, super.visitRecordComponent(name, descriptor, signature));
+ }
+
@Override
public FieldVisitor visitField(
final int access,
@@ -394,6 +408,7 @@ public FieldVisitor visitField(
| Opcodes.ACC_TRANSIENT
| Opcodes.ACC_SYNTHETIC
| Opcodes.ACC_ENUM
+ | Opcodes.ACC_MANDATED
| Opcodes.ACC_DEPRECATED);
CheckMethodAdapter.checkUnqualifiedName(version, name, "field name");
CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid = */ false);
@@ -414,7 +429,8 @@ public MethodVisitor visitMethod(
final String signature,
final String[] exceptions) {
checkState();
- checkAccess(
+ checkMethodAccess(
+ version,
access,
Opcodes.ACC_PUBLIC
| Opcodes.ACC_PRIVATE
@@ -428,6 +444,7 @@ public MethodVisitor visitMethod(
| Opcodes.ACC_ABSTRACT
| Opcodes.ACC_STRICT
| Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_MANDATED
| Opcodes.ACC_DEPRECATED);
if (!"".equals(name) && !"".equals(name)) {
CheckMethodAdapter.checkMethodIdentifier(version, name, "method name");
@@ -443,21 +460,18 @@ public MethodVisitor visitMethod(
}
}
CheckMethodAdapter checkMethodAdapter;
+ MethodVisitor methodVisitor =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
if (checkDataFlow) {
+ if (cv instanceof ClassWriter) {
+ methodVisitor =
+ new CheckMethodAdapter.MethodWriterWrapper(
+ api, version, (ClassWriter) cv, methodVisitor);
+ }
checkMethodAdapter =
- new CheckMethodAdapter(
- api,
- access,
- name,
- descriptor,
- super.visitMethod(access, name, descriptor, signature, exceptions),
- labelInsnIndices);
+ new CheckMethodAdapter(api, access, name, descriptor, methodVisitor, labelInsnIndices);
} else {
- checkMethodAdapter =
- new CheckMethodAdapter(
- api,
- super.visitMethod(access, name, descriptor, signature, exceptions),
- labelInsnIndices);
+ checkMethodAdapter = new CheckMethodAdapter(api, methodVisitor, labelInsnIndices);
}
checkMethodAdapter.version = version;
return checkMethodAdapter;
@@ -538,6 +552,23 @@ static void checkAccess(final int access, final int possibleAccess) {
}
}
+ /**
+ * Checks that the given access flags do not contain invalid flags for a method. This method also
+ * checks that mutually incompatible flags are not set simultaneously.
+ *
+ * @param version the class version.
+ * @param access the method access flags to be checked.
+ * @param possibleAccess the valid access flags.
+ */
+ private static void checkMethodAccess(
+ final int version, final int access, final int possibleAccess) {
+ checkAccess(access, possibleAccess);
+ if ((version & 0xFFFF) < Opcodes.V17
+ && Integer.bitCount(access & (Opcodes.ACC_STRICT | Opcodes.ACC_ABSTRACT)) > 1) {
+ throw new IllegalArgumentException("strictfp and abstract are mutually exclusive: " + access);
+ }
+ }
+
/**
* Checks that the given name is a fully qualified name, using dots.
*
@@ -926,9 +957,9 @@ static void checkTypeRef(final int typeRef) {
mask = 0xFF0000FF;
break;
default:
- throw new AssertionError();
+ break;
}
- if ((typeRef & ~mask) != 0) {
+ if (mask == 0 || (typeRef & ~mask) != 0) {
throw new IllegalArgumentException(
"Invalid type reference 0x" + Integer.toHexString(typeRef));
}
@@ -979,9 +1010,10 @@ static void main(final String[] args, final PrintWriter logger) throws IOExcepti
ClassReader classReader;
if (args[0].endsWith(".class")) {
- InputStream inputStream =
- new FileInputStream(args[0]); // NOPMD(AvoidFileStream): can't fix for 1.5 compatibility
- classReader = new ClassReader(inputStream);
+ // Can't fix PMD warning for 1.5 compatibility.
+ try (InputStream inputStream = new FileInputStream(args[0])) { // NOPMD(AvoidFileStream)
+ classReader = new ClassReader(inputStream);
+ }
} else {
classReader = new ClassReader(args[0]);
}
@@ -1017,7 +1049,7 @@ public static void verify(
final PrintWriter printWriter) {
ClassNode classNode = new ClassNode();
classReader.accept(
- new CheckClassAdapter(Opcodes.ASM8_EXPERIMENTAL, classNode, false) {},
+ new CheckClassAdapter(/*latest*/ Opcodes.ASM10_EXPERIMENTAL, classNode, false) {},
ClassReader.SKIP_DEBUG);
Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
@@ -1096,7 +1128,11 @@ private static String getUnqualifiedName(final String name) {
if (name.charAt(endIndex - 1) == ';') {
endIndex--;
}
- return name.substring(lastSlashIndex + 1, endIndex);
+ int lastBracketIndex = name.lastIndexOf('[');
+ if (lastBracketIndex == -1) {
+ return name.substring(lastSlashIndex + 1, endIndex);
+ }
+ return name.substring(0, lastBracketIndex + 1) + name.substring(lastSlashIndex + 1, endIndex);
}
}
}
diff --git a/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java b/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java
index 2922a807..d92ce3d2 100644
--- a/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java
@@ -52,7 +52,7 @@ public class CheckFieldAdapter extends FieldVisitor {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public CheckFieldAdapter(final FieldVisitor fieldVisitor) {
- this(/* latest api = */ Opcodes.ASM7, fieldVisitor);
+ this(/* latest api = */ Opcodes.ASM9, fieldVisitor);
if (getClass() != CheckFieldAdapter.class) {
throw new IllegalStateException();
}
@@ -61,8 +61,8 @@ public CheckFieldAdapter(final FieldVisitor fieldVisitor) {
/**
* Constructs a new {@link CheckFieldAdapter}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param fieldVisitor the field visitor to which this adapter must delegate calls.
*/
protected CheckFieldAdapter(final int api, final FieldVisitor fieldVisitor) {
diff --git a/src/java/nginx/clojure/asm/util/CheckFrameAnalyzer.java b/src/java/nginx/clojure/asm/util/CheckFrameAnalyzer.java
new file mode 100644
index 00000000..fa11f25c
--- /dev/null
+++ b/src/java/nginx/clojure/asm/util/CheckFrameAnalyzer.java
@@ -0,0 +1,478 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm.util;
+
+import java.util.Collections;
+import java.util.List;
+
+import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.Type;
+import nginx.clojure.asm.tree.AbstractInsnNode;
+import nginx.clojure.asm.tree.FrameNode;
+import nginx.clojure.asm.tree.InsnList;
+import nginx.clojure.asm.tree.InsnNode;
+import nginx.clojure.asm.tree.JumpInsnNode;
+import nginx.clojure.asm.tree.LabelNode;
+import nginx.clojure.asm.tree.LookupSwitchInsnNode;
+import nginx.clojure.asm.tree.MethodNode;
+import nginx.clojure.asm.tree.TableSwitchInsnNode;
+import nginx.clojure.asm.tree.TryCatchBlockNode;
+import nginx.clojure.asm.tree.TypeInsnNode;
+import nginx.clojure.asm.tree.analysis.Analyzer;
+import nginx.clojure.asm.tree.analysis.AnalyzerException;
+import nginx.clojure.asm.tree.analysis.Frame;
+import nginx.clojure.asm.tree.analysis.Interpreter;
+import nginx.clojure.asm.tree.analysis.Value;
+
+/**
+ * An {@link Analyzer} subclass which checks that methods provide stack map frames where expected
+ * (i.e. at jump target and after instructions without immediate successor), and that these stack
+ * map frames are valid (for the provided interpreter; they may still be invalid for the JVM, if the
+ * {@link Interpreter} uses a simplified type system compared to the JVM verifier). This is done in
+ * two steps:
+ *
+ *
+ * - First, the stack map frames in {@link FrameNode}s are expanded, and stored at their
+ * respective instruction offsets. The expansion process uncompresses the APPEND, CHOP and
+ * SAME frames to FULL frames. It also converts the stack map frame verification types to
+ * {@link Value}s, via the provided {@link Interpreter}. The expansion is done in {@link
+ * #expandFrames}, by looking at each {@link FrameNode} in sequence (compressed frames are
+ * defined relatively to the previous {@link FrameNode}, or the implicit first frame). The
+ * actual decompression is done in {@link #expandFrame}, and the type conversion in {@link
+ * #newFrameValue}.
+ *
- Next, the method instructions are checked in sequence. Starting from the implicit initial
+ * frame, the execution of each instruction i is simulated on the current stack map
+ * frame, with the {@link Frame#execute} method. This gives a new stack map frame f,
+ * representing the stack map frame state after the execution of i. Then:
+ *
+ * - If there is a next instruction and if the control flow cannot continue to it (e.g. if
+ * i is a RETURN or an ATHROW, for instance): an existing stack map frame
+ * f0 (coming from the first step) is expected after i.
+ *
- If there is a next instruction and if the control flow can continue to it (e.g. if
+ * i is a ALOAD, for instance): either there an existing stack map frame
+ * f0 (coming from the first step) after i, or there is none. In the
+ * first case f and f0 must be compatible: the types in
+ * f must be sub types of the corresponding types in the existing frame
+ * f0 (otherwise an exception is thrown). In the second case, f0 is
+ * simply set to the value of f.
+ *
- If the control flow can continue to some instruction j (e.g. if i
+ * is an IF_EQ, for instance): an existing stack map frame f0 (coming from the
+ * first step) is expected at j, which must be compatible with f (as
+ * defined previously).
+ *
+ * The sequential loop over the instructions is done in {@link #init}, which is called from
+ * the {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the
+ * next instruction are handled in {@link #endControlFlow}. Cases where the control flow can
+ * continue to the next instruction, or jump to another instruction, are handled in {@link
+ * #checkFrame}. This method checks that an existing stack map frame is present when required,
+ * and checks the stack map frames compatibility with {@link #checkMerge}.
+ *
+ *
+ * @author Eric Bruneton
+ * @param type of the {@link Value} used for the analysis.
+ */
+class CheckFrameAnalyzer extends Analyzer {
+
+ /** The interpreter to use to symbolically interpret the bytecode instructions. */
+ private final Interpreter interpreter;
+
+ /** The instructions of the currently analyzed method. */
+ private InsnList insnList;
+
+ /**
+ * The number of locals in the last stack map frame processed by {@link expandFrame}. Long and
+ * double values are represented with two elements.
+ */
+ private int currentLocals;
+
+ CheckFrameAnalyzer(final Interpreter interpreter) {
+ super(interpreter);
+ this.interpreter = interpreter;
+ }
+
+ @Override
+ protected void init(final String owner, final MethodNode method) throws AnalyzerException {
+ insnList = method.instructions;
+ currentLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2;
+
+ Frame[] frames = getFrames();
+ Frame currentFrame = frames[0];
+ expandFrames(owner, method, currentFrame);
+ for (int insnIndex = 0; insnIndex < insnList.size(); ++insnIndex) {
+ Frame oldFrame = frames[insnIndex];
+
+ // Simulate the execution of this instruction.
+ AbstractInsnNode insnNode = null;
+ try {
+ insnNode = method.instructions.get(insnIndex);
+ int insnOpcode = insnNode.getOpcode();
+ int insnType = insnNode.getType();
+
+ if (insnType == AbstractInsnNode.LABEL
+ || insnType == AbstractInsnNode.LINE
+ || insnType == AbstractInsnNode.FRAME) {
+ checkFrame(insnIndex + 1, oldFrame, /* requireFrame = */ false);
+ } else {
+ currentFrame.init(oldFrame).execute(insnNode, interpreter);
+
+ if (insnNode instanceof JumpInsnNode) {
+ if (insnOpcode == JSR) {
+ throw new AnalyzerException(insnNode, "JSR instructions are unsupported");
+ }
+ JumpInsnNode jumpInsn = (JumpInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(jumpInsn.label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ if (insnOpcode == GOTO) {
+ endControlFlow(insnIndex);
+ } else {
+ checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
+ }
+ } else if (insnNode instanceof LookupSwitchInsnNode) {
+ LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) {
+ LabelNode label = lookupSwitchInsn.labels.get(i);
+ targetInsnIndex = insnList.indexOf(label);
+ currentFrame.initJumpTarget(insnOpcode, label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ endControlFlow(insnIndex);
+ } else if (insnNode instanceof TableSwitchInsnNode) {
+ TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt);
+ currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ newControlFlowEdge(insnIndex, targetInsnIndex);
+ for (int i = 0; i < tableSwitchInsn.labels.size(); ++i) {
+ LabelNode label = tableSwitchInsn.labels.get(i);
+ currentFrame.initJumpTarget(insnOpcode, label);
+ targetInsnIndex = insnList.indexOf(label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ endControlFlow(insnIndex);
+ } else if (insnOpcode == RET) {
+ throw new AnalyzerException(insnNode, "RET instructions are unsupported");
+ } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
+ checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
+ } else {
+ endControlFlow(insnIndex);
+ }
+ }
+
+ List insnHandlers = getHandlers(insnIndex);
+ if (insnHandlers != null) {
+ for (TryCatchBlockNode tryCatchBlock : insnHandlers) {
+ Type catchType;
+ if (tryCatchBlock.type == null) {
+ catchType = Type.getObjectType("java/lang/Throwable");
+ } else {
+ catchType = Type.getObjectType(tryCatchBlock.type);
+ }
+ Frame handler = newFrame(oldFrame);
+ handler.clearStack();
+ handler.push(interpreter.newExceptionValue(tryCatchBlock, handler, catchType));
+ checkFrame(insnList.indexOf(tryCatchBlock.handler), handler, /* requireFrame = */ true);
+ }
+ }
+
+ if (!hasNextJvmInsnOrFrame(insnIndex)) {
+ break;
+ }
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(
+ e.node, "Error at instruction " + insnIndex + ": " + e.getMessage(), e);
+ } catch (RuntimeException e) {
+ // DontCheck(IllegalCatch): can't be fixed, for backward compatibility.
+ throw new AnalyzerException(
+ insnNode, "Error at instruction " + insnIndex + ": " + e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Expands the {@link FrameNode} "instructions" of the given method into {@link Frame} objects and
+ * stores them at the corresponding indices of the {@link #frames} array. The expanded frames are
+ * also associated with the label and line number nodes immediately preceding each frame node.
+ *
+ * @param owner the internal name of the class to which 'method' belongs.
+ * @param method the method whose frames must be expanded.
+ * @param initialFrame the implicit initial frame of 'method'.
+ * @throws AnalyzerException if the stack map frames of 'method', i.e. its FrameNode
+ * "instructions", are invalid.
+ */
+ private void expandFrames(
+ final String owner, final MethodNode method, final Frame initialFrame)
+ throws AnalyzerException {
+ int lastJvmOrFrameInsnIndex = -1;
+ Frame currentFrame = initialFrame;
+ int currentInsnIndex = 0;
+ for (AbstractInsnNode insnNode : method.instructions) {
+ if (insnNode instanceof FrameNode) {
+ try {
+ currentFrame = expandFrame(owner, currentFrame, (FrameNode) insnNode);
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(
+ e.node, "Error at instruction " + currentInsnIndex + ": " + e.getMessage(), e);
+ }
+ for (int index = lastJvmOrFrameInsnIndex + 1; index <= currentInsnIndex; ++index) {
+ getFrames()[index] = currentFrame;
+ }
+ }
+ if (isJvmInsnNode(insnNode) || insnNode instanceof FrameNode) {
+ lastJvmOrFrameInsnIndex = currentInsnIndex;
+ }
+ currentInsnIndex += 1;
+ }
+ }
+
+ /**
+ * Returns the expanded representation of the given {@link FrameNode}.
+ *
+ * @param owner the internal name of the class to which 'frameNode' belongs.
+ * @param previousFrame the frame before 'frameNode', in expanded form.
+ * @param frameNode a possibly compressed stack map frame.
+ * @return the expanded version of 'frameNode'.
+ * @throws AnalyzerException if 'frameNode' is invalid.
+ */
+ private Frame expandFrame(
+ final String owner, final Frame previousFrame, final FrameNode frameNode)
+ throws AnalyzerException {
+ Frame frame = newFrame(previousFrame);
+ List