| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.javaassertionenabler; | 5 package org.chromium.javaassertionenabler; |
| 6 | 6 |
| 7 import org.objectweb.asm.ClassReader; | 7 import org.objectweb.asm.ClassReader; |
| 8 import org.objectweb.asm.ClassVisitor; | 8 import org.objectweb.asm.ClassVisitor; |
| 9 import org.objectweb.asm.ClassWriter; | 9 import org.objectweb.asm.ClassWriter; |
| 10 import org.objectweb.asm.Label; | |
| 11 import org.objectweb.asm.MethodVisitor; | 10 import org.objectweb.asm.MethodVisitor; |
| 12 import org.objectweb.asm.Opcodes; | 11 import org.objectweb.asm.Opcodes; |
| 13 | 12 |
| 14 import java.io.BufferedInputStream; | 13 import java.io.BufferedInputStream; |
| 15 import java.io.BufferedOutputStream; | 14 import java.io.BufferedOutputStream; |
| 16 import java.io.ByteArrayOutputStream; | 15 import java.io.ByteArrayOutputStream; |
| 17 import java.io.FileInputStream; | 16 import java.io.FileInputStream; |
| 18 import java.io.FileOutputStream; | 17 import java.io.FileOutputStream; |
| 19 import java.io.IOException; | 18 import java.io.IOException; |
| 20 import java.io.InputStream; | 19 import java.io.InputStream; |
| 21 import java.nio.file.Files; | 20 import java.nio.file.Files; |
| 22 import java.nio.file.Path; | 21 import java.nio.file.Path; |
| 23 import java.nio.file.Paths; | 22 import java.nio.file.Paths; |
| 24 import java.nio.file.StandardCopyOption; | 23 import java.nio.file.StandardCopyOption; |
| 25 import java.util.zip.ZipEntry; | 24 import java.util.zip.ZipEntry; |
| 26 import java.util.zip.ZipInputStream; | 25 import java.util.zip.ZipInputStream; |
| 27 import java.util.zip.ZipOutputStream; | 26 import java.util.zip.ZipOutputStream; |
| 28 | 27 |
| 29 /** | 28 /** |
| 30 * An application that replace Java ASSERT statements with a function by modifyi
ng Java bytecode. It | 29 * An application that enables Java ASSERT statements by modifying Java bytecode
. It takes in a JAR |
| 31 * takes in a JAR file, modifies bytecode of classes that use ASSERT, and output
s the bytecode to a | 30 * file, modifies bytecode of classes that use ASSERT, and outputs the bytecode
to a new JAR file. |
| 32 * new JAR file. | |
| 33 * | |
| 34 * We do this in two steps, first step is to enable assert. | |
| 35 * Following bytecode is generated for each class with ASSERT statements: | |
| 36 * 0: ldc #8 // class CLASSNAME | |
| 37 * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z | |
| 38 * 5: ifne 12 | |
| 39 * 8: iconst_1 | |
| 40 * 9: goto 13 | |
| 41 * 12: iconst_0 | |
| 42 * 13: putstatic #2 // Field $assertionsDisabled:Z | |
| 43 * Replaces line #13 to the following: | |
| 44 * 13: pop | |
| 45 * Consequently, $assertionsDisabled is assigned the default value FALSE. | |
| 46 * This is done in the first if statement in overridden visitFieldInsn. We do th
is per per-assert. | |
| 47 * | |
| 48 * Second step is to replace assert statement with a function: | |
| 49 * The followed instructions are generated by a java assert statement: | |
| 50 * getstatic #3 // Field $assertionsDisabled:Z | |
| 51 * ifne 118 // Jump to instruction as if assertion if not enabled | |
| 52 * ... | |
| 53 * ifne 19 | |
| 54 * new #4 // class java/lang/AssertionError | |
| 55 * dup | |
| 56 * ldc #5 // String (don't have this line if no assert message giv
en) | |
| 57 * invokespecial #6 // Method java/lang/AssertionError. | |
| 58 * athrow | |
| 59 * Replace athrow with: | |
| 60 * invokestatic #7 // Method org/chromium/base/JavaExceptionReporter.assert
FailureHandler | |
| 61 * goto 118 | |
| 62 * JavaExceptionReporter.assertFailureHandler is a function that handles the Ass
ertionError, | |
| 63 * 118 is the instruction to execute as if assertion if not enabled. | |
| 64 */ | 31 */ |
| 65 class AssertionEnabler { | 32 class AssertionEnabler { |
| 33 static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; |
| 66 static final String CLASS_FILE_SUFFIX = ".class"; | 34 static final String CLASS_FILE_SUFFIX = ".class"; |
| 35 static final String STATIC_INITIALIZER_NAME = "<clinit>"; |
| 67 static final String TEMPORARY_FILE_SUFFIX = ".temp"; | 36 static final String TEMPORARY_FILE_SUFFIX = ".temp"; |
| 37 |
| 68 static final int BUFFER_SIZE = 16384; | 38 static final int BUFFER_SIZE = 16384; |
| 69 | 39 |
| 70 static class AssertionEnablerVisitor extends ClassVisitor { | 40 static class AssertionEnablerVisitor extends ClassVisitor { |
| 71 AssertionEnablerVisitor(ClassWriter writer) { | 41 AssertionEnablerVisitor(ClassWriter writer) { |
| 72 super(Opcodes.ASM5, writer); | 42 super(Opcodes.ASM5, writer); |
| 73 } | 43 } |
| 74 | 44 |
| 75 @Override | 45 @Override |
| 76 public MethodVisitor visitMethod(final int access, final String name, St
ring desc, | 46 public MethodVisitor visitMethod(final int access, final String name, St
ring desc, |
| 77 String signature, String[] exceptions) { | 47 String signature, String[] exceptions) { |
| 78 return new RewriteAssertMethodVisitorWriter( | 48 // Patch static initializer. |
| 79 Opcodes.ASM5, super.visitMethod(access, name, desc, signatur
e, exceptions)) {}; | 49 if ((access & Opcodes.ACC_STATIC) != 0 && name.equals(STATIC_INITIAL
IZER_NAME)) { |
| 50 return new MethodVisitor(Opcodes.ASM5, |
| 51 super.visitMethod(access, name, desc, signature, excepti
ons)) { |
| 52 // The following bytecode is generated for each class with A
SSERT statements: |
| 53 // 0: ldc #8 // class CLASSNAME |
| 54 // 2: invokevirtual #9 // Method java/lang/Class.desiredAsse
rtionStatus:()Z |
| 55 // 5: ifne 12 |
| 56 // 8: iconst_1 |
| 57 // 9: goto 13 |
| 58 // 12: iconst_0 |
| 59 // 13: putstatic #2 // Field $assertionsDisabled:Z |
| 60 // |
| 61 // This function replaces line #13 to the following: |
| 62 // 13: pop |
| 63 // Consequently, $assertionsDisabled is assigned the default
value FALSE. |
| 64 @Override |
| 65 public void visitFieldInsn(int opcode, String owner, String
name, String desc) { |
| 66 if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION
_DISABLED_NAME)) { |
| 67 mv.visitInsn(Opcodes.POP); |
| 68 } else { |
| 69 super.visitFieldInsn(opcode, owner, name, desc); |
| 70 } |
| 71 } |
| 72 }; |
| 73 } |
| 74 return super.visitMethod(access, name, desc, signature, exceptions); |
| 80 } | 75 } |
| 81 } | 76 } |
| 82 | 77 |
| 83 static class RewriteAssertMethodVisitorWriter extends MethodVisitor { | |
| 84 static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; | |
| 85 static final String INSERT_INSTRUCTION_OWNER = "org/chromium/buildhooks/
BuildHooks"; | |
| 86 static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler"; | |
| 87 static final String INSERT_INSTRUCTION_DESC = "(Ljava/lang/AssertionErro
r;)V"; | |
| 88 static final boolean INSERT_INSTRUCTION_ITF = false; | |
| 89 | |
| 90 boolean mStartLoadingAssert; | |
| 91 Label mGotoLabel; | |
| 92 | |
| 93 public RewriteAssertMethodVisitorWriter(int api, MethodVisitor mv) { | |
| 94 super(api, mv); | |
| 95 } | |
| 96 | |
| 97 @Override | |
| 98 public void visitFieldInsn(int opcode, String owner, String name, String
desc) { | |
| 99 if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NA
ME)) { | |
| 100 super.visitInsn(Opcodes.POP); // enable assert | |
| 101 } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISA
BLED_NAME)) { | |
| 102 mStartLoadingAssert = true; | |
| 103 super.visitFieldInsn(opcode, owner, name, desc); | |
| 104 } else { | |
| 105 super.visitFieldInsn(opcode, owner, name, desc); | |
| 106 } | |
| 107 } | |
| 108 | |
| 109 @Override | |
| 110 public void visitJumpInsn(int opcode, Label label) { | |
| 111 if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == n
ull) { | |
| 112 mGotoLabel = label; | |
| 113 } | |
| 114 super.visitJumpInsn(opcode, label); | |
| 115 } | |
| 116 | |
| 117 @Override | |
| 118 public void visitInsn(int opcode) { | |
| 119 if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) { | |
| 120 super.visitInsn(opcode); | |
| 121 } else { | |
| 122 super.visitMethodInsn(Opcodes.INVOKESTATIC, INSERT_INSTRUCTION_O
WNER, | |
| 123 INSERT_INSTRUCTION_NAME, INSERT_INSTRUCTION_DESC, INSERT
_INSTRUCTION_ITF); | |
| 124 super.visitJumpInsn(Opcodes.GOTO, mGotoLabel); | |
| 125 mStartLoadingAssert = false; | |
| 126 mGotoLabel = null; | |
| 127 } | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 static byte[] readAllBytes(InputStream inputStream) throws IOException { | 78 static byte[] readAllBytes(InputStream inputStream) throws IOException { |
| 132 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | 79 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| 133 int numRead = 0; | 80 int numRead = 0; |
| 134 byte[] data = new byte[BUFFER_SIZE]; | 81 byte[] data = new byte[BUFFER_SIZE]; |
| 135 while ((numRead = inputStream.read(data, 0, data.length)) != -1) { | 82 while ((numRead = inputStream.read(data, 0, data.length)) != -1) { |
| 136 buffer.write(data, 0, numRead); | 83 buffer.write(data, 0, numRead); |
| 137 } | 84 } |
| 85 |
| 138 return buffer.toByteArray(); | 86 return buffer.toByteArray(); |
| 139 } | 87 } |
| 140 | 88 |
| 141 static void enableAssertionInJar(String inputJarPath, String outputJarPath)
{ | 89 static void enableAssertionInJar(String inputJarPath, String outputJarPath)
{ |
| 142 String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; | 90 String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; |
| 143 try (ZipInputStream inputStream = new ZipInputStream( | 91 try (ZipInputStream inputStream = new ZipInputStream( |
| 144 new BufferedInputStream(new FileInputStream(inputJarPath)))
; | 92 new BufferedInputStream(new FileInputStream(inputJarPath))); |
| 145 ZipOutputStream tempStream = new ZipOutputStream( | 93 ZipOutputStream tempStream = new ZipOutputStream( |
| 146 new BufferedOutputStream(new FileOutputStream(tempJarPat
h)))) { | 94 new BufferedOutputStream(new FileOutputStream(tempJarPath)))
) { |
| 147 ZipEntry entry = null; | 95 ZipEntry entry = null; |
| 148 | 96 |
| 149 while ((entry = inputStream.getNextEntry()) != null) { | 97 while ((entry = inputStream.getNextEntry()) != null) { |
| 150 byte[] byteCode = readAllBytes(inputStream); | 98 byte[] byteCode = readAllBytes(inputStream); |
| 151 | 99 |
| 152 if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_
SUFFIX)) { | 100 if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_
SUFFIX)) { |
| 153 tempStream.putNextEntry(entry); | 101 tempStream.putNextEntry(entry); |
| 154 tempStream.write(byteCode); | 102 tempStream.write(byteCode); |
| 155 tempStream.closeEntry(); | 103 tempStream.closeEntry(); |
| 156 continue; | 104 continue; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 175 throw new RuntimeException(ioException); | 123 throw new RuntimeException(ioException); |
| 176 } | 124 } |
| 177 } | 125 } |
| 178 | 126 |
| 179 public static void main(String[] args) { | 127 public static void main(String[] args) { |
| 180 if (args.length != 2) { | 128 if (args.length != 2) { |
| 181 System.out.println("Incorrect number of arguments."); | 129 System.out.println("Incorrect number of arguments."); |
| 182 System.out.println("Example usage: java_assertion_enabler input.jar
output.jar"); | 130 System.out.println("Example usage: java_assertion_enabler input.jar
output.jar"); |
| 183 System.exit(-1); | 131 System.exit(-1); |
| 184 } | 132 } |
| 185 String inputJarPath = args[0]; | 133 enableAssertionInJar(args[0], args[1]); |
| 186 String outputJarPath = args[1]; | |
| 187 enableAssertionInJar(inputJarPath, outputJarPath); | |
| 188 } | 134 } |
| 189 } | 135 } |
| OLD | NEW |