| 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.MethodVisitor; | 10 import org.objectweb.asm.MethodVisitor; |
| 11 import org.objectweb.asm.Opcodes; | 11 import org.objectweb.asm.Opcodes; |
| 12 | 12 |
| 13 import java.io.BufferedInputStream; | 13 import java.io.BufferedInputStream; |
| 14 import java.io.BufferedOutputStream; | 14 import java.io.BufferedOutputStream; |
| 15 import java.io.ByteArrayOutputStream; | 15 import java.io.ByteArrayOutputStream; |
| 16 import java.io.FileInputStream; | 16 import java.io.FileInputStream; |
| 17 import java.io.FileOutputStream; | 17 import java.io.FileOutputStream; |
| 18 import java.io.IOException; | 18 import java.io.IOException; |
| 19 import java.io.InputStream; | 19 import java.io.InputStream; |
| 20 import java.nio.file.Files; | 20 import java.nio.file.Files; |
| 21 import java.nio.file.Path; | 21 import java.nio.file.Path; |
| 22 import java.nio.file.Paths; | 22 import java.nio.file.Paths; |
| 23 import java.nio.file.StandardCopyOption; | 23 import java.nio.file.StandardCopyOption; |
| 24 import java.util.zip.ZipEntry; | 24 import java.util.zip.ZipEntry; |
| 25 import java.util.zip.ZipInputStream; | 25 import java.util.zip.ZipInputStream; |
| 26 import java.util.zip.ZipOutputStream; | 26 import java.util.zip.ZipOutputStream; |
| 27 | 27 |
| 28 /** | 28 /** |
| 29 * An application that enables Java ASSERT statements by modifying Java bytecode
. It takes in a JAR | 29 * 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. | 30 * takes in a JAR file, modifies bytecode of classes that use ASSERT, and output
s the bytecode to a |
| 31 * new JAR file. |
| 32 * First enable assert: |
| 33 * The following bytecode is generated for each class with ASSERT statements: |
| 34 * 0: ldc #8 // class CLASSNAME |
| 35 * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z |
| 36 * 5: ifne 12 |
| 37 * 8: iconst_1 |
| 38 * 9: goto 13 |
| 39 * 12: iconst_0 |
| 40 * 13: putstatic #2 // Field $assertionsDisabled:Z |
| 41 * Replaces line #13 to the following: |
| 42 * 13: pop |
| 43 * Consequently, $assertionsDisabled is assigned the default value FALSE. |
| 44 * |
| 45 * To replace assert statement with a function: |
| 46 * The followed instructions are generated by a java assert statement: |
| 47 * getstatic #3 // Field $assertionsDisabled:Z |
| 48 * ... |
| 49 * ifne 19 |
| 50 * new #4 // class java/lang/AssertionError |
| 51 * dup |
| 52 * ldc #5 // String (don't have this line if no asser
t message given) |
| 53 * invokespecial #6 // Method java/lang/AssertionError. |
| 54 * athrow |
| 55 * We replace athrow with a call to a function that handles the AssertionError. |
| 31 */ | 56 */ |
| 32 class AssertionEnabler { | 57 class AssertionEnabler { |
| 33 static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; | |
| 34 static final String CLASS_FILE_SUFFIX = ".class"; | 58 static final String CLASS_FILE_SUFFIX = ".class"; |
| 35 static final String STATIC_INITIALIZER_NAME = "<clinit>"; | |
| 36 static final String TEMPORARY_FILE_SUFFIX = ".temp"; | 59 static final String TEMPORARY_FILE_SUFFIX = ".temp"; |
| 37 | |
| 38 static final int BUFFER_SIZE = 16384; | 60 static final int BUFFER_SIZE = 16384; |
| 39 | 61 |
| 40 static class AssertionEnablerVisitor extends ClassVisitor { | 62 static class AssertionEnablerVisitor extends ClassVisitor { |
| 41 AssertionEnablerVisitor(ClassWriter writer) { | 63 AssertionEnablerVisitor(ClassWriter writer) { |
| 42 super(Opcodes.ASM5, writer); | 64 super(Opcodes.ASM5, writer); |
| 43 } | 65 } |
| 44 | 66 |
| 45 @Override | 67 @Override |
| 46 public MethodVisitor visitMethod(final int access, final String name, St
ring desc, | 68 public MethodVisitor visitMethod(final int access, final String name, St
ring desc, |
| 47 String signature, String[] exceptions) { | 69 String signature, String[] exceptions) { |
| 48 // Patch static initializer. | 70 return new RewriteAssertMethodVisitorWriter( |
| 49 if ((access & Opcodes.ACC_STATIC) != 0 && name.equals(STATIC_INITIAL
IZER_NAME)) { | 71 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 } | 72 } |
| 76 } | 73 } |
| 77 | 74 |
| 75 static class RewriteAssertMethodVisitorWriter extends MethodVisitor { |
| 76 static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; |
| 77 static final String INSERT_INSTRUCTION_OWNER = "org/chromium/base/JavaEx
ceptionReporter"; |
| 78 static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler"; |
| 79 static final String INSERT_INSTRUCTION_DESC = "(Ljava/lang/AssertionErro
r;)V"; |
| 80 static final boolean INSERT_INSTRUCTION_ITF = false; |
| 81 |
| 82 boolean mStartLoadingAssert; |
| 83 |
| 84 public RewriteAssertMethodVisitorWriter(int api, MethodVisitor mv) { |
| 85 super(api, mv); |
| 86 } |
| 87 |
| 88 @Override |
| 89 public void visitFieldInsn(int opcode, String owner, String name, String
desc) { |
| 90 if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NA
ME)) { |
| 91 super.visitInsn(Opcodes.POP); // enable assert |
| 92 } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISA
BLED_NAME)) { |
| 93 mStartLoadingAssert = true; |
| 94 super.visitFieldInsn(opcode, owner, name, desc); |
| 95 } else { |
| 96 super.visitFieldInsn(opcode, owner, name, desc); |
| 97 } |
| 98 } |
| 99 |
| 100 @Override |
| 101 public void visitInsn(int opcode) { |
| 102 if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) { |
| 103 super.visitInsn(opcode); |
| 104 } else { |
| 105 super.visitMethodInsn(Opcodes.INVOKESTATIC, INSERT_INSTRUCTION_O
WNER, |
| 106 INSERT_INSTRUCTION_NAME, INSERT_INSTRUCTION_DESC, INSERT
_INSTRUCTION_ITF); |
| 107 mStartLoadingAssert = false; |
| 108 } |
| 109 } |
| 110 } |
| 111 |
| 78 static byte[] readAllBytes(InputStream inputStream) throws IOException { | 112 static byte[] readAllBytes(InputStream inputStream) throws IOException { |
| 79 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | 113 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| 80 int numRead = 0; | 114 int numRead = 0; |
| 81 byte[] data = new byte[BUFFER_SIZE]; | 115 byte[] data = new byte[BUFFER_SIZE]; |
| 82 while ((numRead = inputStream.read(data, 0, data.length)) != -1) { | 116 while ((numRead = inputStream.read(data, 0, data.length)) != -1) { |
| 83 buffer.write(data, 0, numRead); | 117 buffer.write(data, 0, numRead); |
| 84 } | 118 } |
| 85 | |
| 86 return buffer.toByteArray(); | 119 return buffer.toByteArray(); |
| 87 } | 120 } |
| 88 | 121 |
| 89 static void enableAssertionInJar(String inputJarPath, String outputJarPath)
{ | 122 static void enableAssertionInJar(String inputJarPath, String outputJarPath)
{ |
| 90 String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; | 123 String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; |
| 91 try (ZipInputStream inputStream = new ZipInputStream( | 124 try (ZipInputStream inputStream = new ZipInputStream( |
| 92 new BufferedInputStream(new FileInputStream(inputJarPath))); | 125 new BufferedInputStream(new FileInputStream(inputJarPath)))
; |
| 93 ZipOutputStream tempStream = new ZipOutputStream( | 126 ZipOutputStream tempStream = new ZipOutputStream( |
| 94 new BufferedOutputStream(new FileOutputStream(tempJarPath)))
) { | 127 new BufferedOutputStream(new FileOutputStream(tempJarPat
h)))) { |
| 95 ZipEntry entry = null; | 128 ZipEntry entry = null; |
| 96 | 129 |
| 97 while ((entry = inputStream.getNextEntry()) != null) { | 130 while ((entry = inputStream.getNextEntry()) != null) { |
| 98 byte[] byteCode = readAllBytes(inputStream); | 131 byte[] byteCode = readAllBytes(inputStream); |
| 99 | 132 |
| 100 if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_
SUFFIX)) { | 133 if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_
SUFFIX)) { |
| 101 tempStream.putNextEntry(entry); | 134 tempStream.putNextEntry(entry); |
| 102 tempStream.write(byteCode); | 135 tempStream.write(byteCode); |
| 103 tempStream.closeEntry(); | 136 tempStream.closeEntry(); |
| 104 continue; | 137 continue; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 126 | 159 |
| 127 public static void main(String[] args) { | 160 public static void main(String[] args) { |
| 128 if (args.length != 2) { | 161 if (args.length != 2) { |
| 129 System.out.println("Incorrect number of arguments."); | 162 System.out.println("Incorrect number of arguments."); |
| 130 System.out.println("Example usage: java_assertion_enabler input.jar
output.jar"); | 163 System.out.println("Example usage: java_assertion_enabler input.jar
output.jar"); |
| 131 System.exit(-1); | 164 System.exit(-1); |
| 132 } | 165 } |
| 133 enableAssertionInJar(args[0], args[1]); | 166 enableAssertionInJar(args[0], args[1]); |
| 134 } | 167 } |
| 135 } | 168 } |
| OLD | NEW |