Index: build/android/java_assertion_enabler/java/org/chromium/javaassertionenabler/AssertionEnabler.java |
diff --git a/build/android/java_assertion_enabler/java/org/chromium/javaassertionenabler/AssertionEnabler.java b/build/android/java_assertion_enabler/java/org/chromium/javaassertionenabler/AssertionEnabler.java |
index beb26facda44e40dc31ec9ced6e18415803a29c9..86419ce1b98086a6097f17f444ad9c7d296751b0 100644 |
--- a/build/android/java_assertion_enabler/java/org/chromium/javaassertionenabler/AssertionEnabler.java |
+++ b/build/android/java_assertion_enabler/java/org/chromium/javaassertionenabler/AssertionEnabler.java |
@@ -7,6 +7,7 @@ package org.chromium.javaassertionenabler; |
import org.objectweb.asm.ClassReader; |
import org.objectweb.asm.ClassVisitor; |
import org.objectweb.asm.ClassWriter; |
+import org.objectweb.asm.Label; |
import org.objectweb.asm.MethodVisitor; |
import org.objectweb.asm.Opcodes; |
@@ -26,15 +27,44 @@ import java.util.zip.ZipInputStream; |
import java.util.zip.ZipOutputStream; |
/** |
- * An application that enables Java ASSERT statements by modifying Java bytecode. It takes in a JAR |
- * file, modifies bytecode of classes that use ASSERT, and outputs the bytecode to a new JAR file. |
+ * An application that replace Java ASSERT statements with a function by modifying Java bytecode. It |
+ * takes in a JAR file, modifies bytecode of classes that use ASSERT, and outputs the bytecode to a |
+ * new JAR file. |
+ * |
+ * We do this in two steps, first step is to enable assert. |
+ * Following bytecode is generated for each class with ASSERT statements: |
+ * 0: ldc #8 // class CLASSNAME |
+ * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z |
+ * 5: ifne 12 |
+ * 8: iconst_1 |
+ * 9: goto 13 |
+ * 12: iconst_0 |
+ * 13: putstatic #2 // Field $assertionsDisabled:Z |
+ * Replaces line #13 to the following: |
+ * 13: pop |
+ * Consequently, $assertionsDisabled is assigned the default value FALSE. |
+ * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert. |
+ * |
+ * Second step is to replace assert statement with a function: |
+ * The followed instructions are generated by a java assert statement: |
+ * getstatic #3 // Field $assertionsDisabled:Z |
+ * ifne 118 // Jump to instruction as if assertion if not enabled |
+ * ... |
+ * ifne 19 |
+ * new #4 // class java/lang/AssertionError |
+ * dup |
+ * ldc #5 // String (don't have this line if no assert message given) |
+ * invokespecial #6 // Method java/lang/AssertionError. |
+ * athrow |
+ * Replace athrow with: |
+ * invokestatic #7 // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler |
+ * goto 118 |
+ * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError, |
+ * 118 is the instruction to execute as if assertion if not enabled. |
*/ |
class AssertionEnabler { |
- static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; |
static final String CLASS_FILE_SUFFIX = ".class"; |
- static final String STATIC_INITIALIZER_NAME = "<clinit>"; |
static final String TEMPORARY_FILE_SUFFIX = ".temp"; |
- |
static final int BUFFER_SIZE = 16384; |
static class AssertionEnablerVisitor extends ClassVisitor { |
@@ -45,33 +75,56 @@ class AssertionEnabler { |
@Override |
public MethodVisitor visitMethod(final int access, final String name, String desc, |
String signature, String[] exceptions) { |
- // Patch static initializer. |
- if ((access & Opcodes.ACC_STATIC) != 0 && name.equals(STATIC_INITIALIZER_NAME)) { |
- return new MethodVisitor(Opcodes.ASM5, |
- super.visitMethod(access, name, desc, signature, exceptions)) { |
- // The following bytecode is generated for each class with ASSERT statements: |
- // 0: ldc #8 // class CLASSNAME |
- // 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z |
- // 5: ifne 12 |
- // 8: iconst_1 |
- // 9: goto 13 |
- // 12: iconst_0 |
- // 13: putstatic #2 // Field $assertionsDisabled:Z |
- // |
- // This function replaces line #13 to the following: |
- // 13: pop |
- // Consequently, $assertionsDisabled is assigned the default value FALSE. |
- @Override |
- public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
- if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) { |
- mv.visitInsn(Opcodes.POP); |
- } else { |
- super.visitFieldInsn(opcode, owner, name, desc); |
- } |
- } |
- }; |
+ return new RewriteAssertMethodVisitorWriter( |
+ Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {}; |
+ } |
+ } |
+ |
+ static class RewriteAssertMethodVisitorWriter extends MethodVisitor { |
+ static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; |
+ static final String INSERT_INSTRUCTION_OWNER = "org/chromium/buildhooks/BuildHooks"; |
+ static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler"; |
+ static final String INSERT_INSTRUCTION_DESC = "(Ljava/lang/AssertionError;)V"; |
+ static final boolean INSERT_INSTRUCTION_ITF = false; |
+ |
+ boolean mStartLoadingAssert; |
+ Label mGotoLabel; |
+ |
+ public RewriteAssertMethodVisitorWriter(int api, MethodVisitor mv) { |
+ super(api, mv); |
+ } |
+ |
+ @Override |
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
+ if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) { |
+ super.visitInsn(Opcodes.POP); // enable assert |
+ } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) { |
+ mStartLoadingAssert = true; |
+ super.visitFieldInsn(opcode, owner, name, desc); |
+ } else { |
+ super.visitFieldInsn(opcode, owner, name, desc); |
+ } |
+ } |
+ |
+ @Override |
+ public void visitJumpInsn(int opcode, Label label) { |
+ if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) { |
+ mGotoLabel = label; |
+ } |
+ super.visitJumpInsn(opcode, label); |
+ } |
+ |
+ @Override |
+ public void visitInsn(int opcode) { |
+ if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) { |
+ super.visitInsn(opcode); |
+ } else { |
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, INSERT_INSTRUCTION_OWNER, |
+ INSERT_INSTRUCTION_NAME, INSERT_INSTRUCTION_DESC, INSERT_INSTRUCTION_ITF); |
+ super.visitJumpInsn(Opcodes.GOTO, mGotoLabel); |
+ mStartLoadingAssert = false; |
+ mGotoLabel = null; |
} |
- return super.visitMethod(access, name, desc, signature, exceptions); |
} |
} |
@@ -82,16 +135,15 @@ class AssertionEnabler { |
while ((numRead = inputStream.read(data, 0, data.length)) != -1) { |
buffer.write(data, 0, numRead); |
} |
- |
return buffer.toByteArray(); |
} |
static void enableAssertionInJar(String inputJarPath, String outputJarPath) { |
String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; |
try (ZipInputStream inputStream = new ZipInputStream( |
- new BufferedInputStream(new FileInputStream(inputJarPath))); |
- ZipOutputStream tempStream = new ZipOutputStream( |
- new BufferedOutputStream(new FileOutputStream(tempJarPath)))) { |
+ new BufferedInputStream(new FileInputStream(inputJarPath))); |
+ ZipOutputStream tempStream = new ZipOutputStream( |
+ new BufferedOutputStream(new FileOutputStream(tempJarPath)))) { |
ZipEntry entry = null; |
while ((entry = inputStream.getNextEntry()) != null) { |
@@ -130,6 +182,8 @@ class AssertionEnabler { |
System.out.println("Example usage: java_assertion_enabler input.jar output.jar"); |
System.exit(-1); |
} |
- enableAssertionInJar(args[0], args[1]); |
+ String inputJarPath = args[0]; |
+ String outputJarPath = args[1]; |
+ enableAssertionInJar(inputJarPath, outputJarPath); |
} |
} |