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/base/JavaEx
ceptionReporter"; | |
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 |