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 |