| 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; |
| 10 import org.objectweb.asm.MethodVisitor; | 11 import org.objectweb.asm.MethodVisitor; |
| 11 import org.objectweb.asm.Opcodes; | 12 import org.objectweb.asm.Opcodes; |
| 12 | 13 |
| 13 import java.io.BufferedInputStream; | 14 import java.io.BufferedInputStream; |
| 14 import java.io.BufferedOutputStream; | 15 import java.io.BufferedOutputStream; |
| 15 import java.io.ByteArrayOutputStream; | 16 import java.io.ByteArrayOutputStream; |
| 16 import java.io.FileInputStream; | 17 import java.io.FileInputStream; |
| 17 import java.io.FileOutputStream; | 18 import java.io.FileOutputStream; |
| 18 import java.io.IOException; | 19 import java.io.IOException; |
| 19 import java.io.InputStream; | 20 import java.io.InputStream; |
| 20 import java.nio.file.Files; | 21 import java.nio.file.Files; |
| 21 import java.nio.file.Path; | 22 import java.nio.file.Path; |
| 22 import java.nio.file.Paths; | 23 import java.nio.file.Paths; |
| 23 import java.nio.file.StandardCopyOption; | 24 import java.nio.file.StandardCopyOption; |
| 24 import java.util.zip.ZipEntry; | 25 import java.util.zip.ZipEntry; |
| 25 import java.util.zip.ZipInputStream; | 26 import java.util.zip.ZipInputStream; |
| 26 import java.util.zip.ZipOutputStream; | 27 import java.util.zip.ZipOutputStream; |
| 27 | 28 |
| 28 /** | 29 /** |
| 29 * An application that enables Java ASSERT statements by modifying Java bytecode
. It takes in a JAR | 30 * An application that replace Java ASSERT statements with a function by modifyi
ng Java bytecode. It |
| 30 * file, modifies bytecode of classes that use ASSERT, and outputs the bytecode
to a new JAR file. | 31 * takes in a JAR file, modifies bytecode of classes that use ASSERT, and output
s the bytecode to a |
| 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. |
| 31 */ | 64 */ |
| 32 class AssertionEnabler { | 65 class AssertionEnabler { |
| 33 static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; | |
| 34 static final String CLASS_FILE_SUFFIX = ".class"; | 66 static final String CLASS_FILE_SUFFIX = ".class"; |
| 35 static final String STATIC_INITIALIZER_NAME = "<clinit>"; | |
| 36 static final String TEMPORARY_FILE_SUFFIX = ".temp"; | 67 static final String TEMPORARY_FILE_SUFFIX = ".temp"; |
| 37 | |
| 38 static final int BUFFER_SIZE = 16384; | 68 static final int BUFFER_SIZE = 16384; |
| 39 | 69 |
| 40 static class AssertionEnablerVisitor extends ClassVisitor { | 70 static class AssertionEnablerVisitor extends ClassVisitor { |
| 41 AssertionEnablerVisitor(ClassWriter writer) { | 71 AssertionEnablerVisitor(ClassWriter writer) { |
| 42 super(Opcodes.ASM5, writer); | 72 super(Opcodes.ASM5, writer); |
| 43 } | 73 } |
| 44 | 74 |
| 45 @Override | 75 @Override |
| 46 public MethodVisitor visitMethod(final int access, final String name, St
ring desc, | 76 public MethodVisitor visitMethod(final int access, final String name, St
ring desc, |
| 47 String signature, String[] exceptions) { | 77 String signature, String[] exceptions) { |
| 48 // Patch static initializer. | 78 return new RewriteAssertMethodVisitorWriter( |
| 49 if ((access & Opcodes.ACC_STATIC) != 0 && name.equals(STATIC_INITIAL
IZER_NAME)) { | 79 Opcodes.ASM5, super.visitMethod(access, name, desc, signatur
e, exceptions)) {}; |
| 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); | |
| 75 } | 80 } |
| 76 } | 81 } |
| 77 | 82 |
| 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 |
| 78 static byte[] readAllBytes(InputStream inputStream) throws IOException { | 131 static byte[] readAllBytes(InputStream inputStream) throws IOException { |
| 79 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | 132 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| 80 int numRead = 0; | 133 int numRead = 0; |
| 81 byte[] data = new byte[BUFFER_SIZE]; | 134 byte[] data = new byte[BUFFER_SIZE]; |
| 82 while ((numRead = inputStream.read(data, 0, data.length)) != -1) { | 135 while ((numRead = inputStream.read(data, 0, data.length)) != -1) { |
| 83 buffer.write(data, 0, numRead); | 136 buffer.write(data, 0, numRead); |
| 84 } | 137 } |
| 85 | |
| 86 return buffer.toByteArray(); | 138 return buffer.toByteArray(); |
| 87 } | 139 } |
| 88 | 140 |
| 89 static void enableAssertionInJar(String inputJarPath, String outputJarPath)
{ | 141 static void enableAssertionInJar(String inputJarPath, String outputJarPath)
{ |
| 90 String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; | 142 String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; |
| 91 try (ZipInputStream inputStream = new ZipInputStream( | 143 try (ZipInputStream inputStream = new ZipInputStream( |
| 92 new BufferedInputStream(new FileInputStream(inputJarPath))); | 144 new BufferedInputStream(new FileInputStream(inputJarPath)))
; |
| 93 ZipOutputStream tempStream = new ZipOutputStream( | 145 ZipOutputStream tempStream = new ZipOutputStream( |
| 94 new BufferedOutputStream(new FileOutputStream(tempJarPath)))
) { | 146 new BufferedOutputStream(new FileOutputStream(tempJarPat
h)))) { |
| 95 ZipEntry entry = null; | 147 ZipEntry entry = null; |
| 96 | 148 |
| 97 while ((entry = inputStream.getNextEntry()) != null) { | 149 while ((entry = inputStream.getNextEntry()) != null) { |
| 98 byte[] byteCode = readAllBytes(inputStream); | 150 byte[] byteCode = readAllBytes(inputStream); |
| 99 | 151 |
| 100 if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_
SUFFIX)) { | 152 if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_
SUFFIX)) { |
| 101 tempStream.putNextEntry(entry); | 153 tempStream.putNextEntry(entry); |
| 102 tempStream.write(byteCode); | 154 tempStream.write(byteCode); |
| 103 tempStream.closeEntry(); | 155 tempStream.closeEntry(); |
| 104 continue; | 156 continue; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 123 throw new RuntimeException(ioException); | 175 throw new RuntimeException(ioException); |
| 124 } | 176 } |
| 125 } | 177 } |
| 126 | 178 |
| 127 public static void main(String[] args) { | 179 public static void main(String[] args) { |
| 128 if (args.length != 2) { | 180 if (args.length != 2) { |
| 129 System.out.println("Incorrect number of arguments."); | 181 System.out.println("Incorrect number of arguments."); |
| 130 System.out.println("Example usage: java_assertion_enabler input.jar
output.jar"); | 182 System.out.println("Example usage: java_assertion_enabler input.jar
output.jar"); |
| 131 System.exit(-1); | 183 System.exit(-1); |
| 132 } | 184 } |
| 133 enableAssertionInJar(args[0], args[1]); | 185 String inputJarPath = args[0]; |
| 186 String outputJarPath = args[1]; |
| 187 enableAssertionInJar(inputJarPath, outputJarPath); |
| 134 } | 188 } |
| 135 } | 189 } |
| OLD | NEW |