| // Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8; |
| |
| import com.android.tools.r8.utils.IntBox; |
| import com.android.tools.r8.utils.Pair; |
| import com.android.tools.r8.utils.ZipUtils; |
| import java.io.BufferedOutputStream; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.OpenOption; |
| import java.nio.file.Path; |
| import java.nio.file.StandardOpenOption; |
| import java.util.function.Supplier; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| |
| public class SoftVerificationErrorJarGenerator { |
| |
| public enum ApiCallerName { |
| CONSTRUCT_UNKNOWN("constructUnknownObject"), |
| CALL_UNKNOWN("callUnknownMethod"), |
| CONSTRUCT_AND_CALL_UNKNOWN("constructUnknownObjectAndCallUnknownMethod"); |
| |
| private String apiCallerMethodName; |
| |
| ApiCallerName(String apiCallerMethodName) { |
| this.apiCallerMethodName = apiCallerMethodName; |
| } |
| |
| public String getApiCallerMethodName() { |
| return apiCallerMethodName; |
| } |
| } |
| |
| public static String NEW_API_CLASS_NAME = "android/app/NotificationChannel"; |
| public static String NEW_API_CLASS_METHOD_NAME = "setDescription"; |
| public static String EXISTING_API_METHOD_NAME = "createNotificationChannel"; |
| |
| public static void createJar( |
| Path archive, |
| int numberOfClasses, |
| boolean isOutlined, |
| ApiCallerName callerName, |
| String newApiClassName, |
| String newApiClassMethodName, |
| String existingApiNewMethodName) |
| throws IOException { |
| OpenOption[] options = |
| new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING}; |
| try (ZipOutputStream out = |
| new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(archive, options)))) { |
| IntBox intBox = new IntBox(0); |
| Pair<byte[], String> callerPair = |
| isOutlined |
| ? Dumps.dumpApiCallerInlined( |
| -1, |
| callerName.getApiCallerMethodName(), |
| newApiClassName, |
| newApiClassMethodName, |
| existingApiNewMethodName) |
| : null; |
| if (callerPair != null) { |
| // The outlined code will call the apiCallerName on the callerPair code. |
| ZipUtils.writeToZipStream( |
| out, callerPair.getSecond() + ".class", callerPair.getFirst(), ZipEntry.STORED); |
| } |
| byte[] mainBytes = |
| Dumps.dumpMain( |
| () -> { |
| if (intBox.get() > numberOfClasses) { |
| return null; |
| } |
| Pair<byte[], String> classData = |
| isOutlined |
| ? Dumps.dumpApiCallerOutlined( |
| intBox.getAndIncrement(), |
| callerPair.getSecond(), |
| callerName.getApiCallerMethodName()) |
| : Dumps.dumpApiCallerInlined( |
| intBox.getAndIncrement(), |
| callerName.getApiCallerMethodName(), |
| newApiClassName, |
| newApiClassMethodName, |
| existingApiNewMethodName); |
| try { |
| ZipUtils.writeToZipStream( |
| out, classData.getSecond() + ".class", classData.getFirst(), ZipEntry.STORED); |
| } catch (IOException exception) { |
| throw new RuntimeException(exception); |
| } |
| return classData.getSecond(); |
| }); |
| ZipUtils.writeToZipStream( |
| out, "com/example/softverificationsample/TestRunner.class", mainBytes, ZipEntry.STORED); |
| } |
| } |
| |
| public static class Dumps implements Opcodes { |
| |
| public static byte[] dumpMain(Supplier<String> targetSupplier) { |
| |
| ClassWriter classWriter = new ClassWriter(0); |
| MethodVisitor methodVisitor; |
| |
| classWriter.visit( |
| V1_8, |
| ACC_PUBLIC | ACC_SUPER, |
| "com/example/softverificationsample/TestRunner", |
| null, |
| "java/lang/Object", |
| null); |
| |
| { |
| methodVisitor = |
| classWriter.visitMethod( |
| ACC_PUBLIC | ACC_STATIC, "run", "(Landroid/content/Context;)V", null, null); |
| methodVisitor.visitCode(); |
| String target = targetSupplier.get(); |
| while (target != null) { |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitMethodInsn( |
| INVOKESTATIC, target, "callApi", "(Landroid/content/Context;)V", false); |
| target = targetSupplier.get(); |
| } |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(1, 1); |
| methodVisitor.visitEnd(); |
| } |
| classWriter.visitEnd(); |
| |
| return classWriter.toByteArray(); |
| } |
| |
| public static Pair<byte[], String> dumpApiCallerOutlined( |
| int index, String apiCallerName, String apiMethodCaller) { |
| |
| ClassWriter classWriter = new ClassWriter(0); |
| MethodVisitor methodVisitor; |
| |
| String binaryName = |
| "com/example/softverificationsample/ApiCallerOutlined" + (index > -1 ? index : ""); |
| |
| classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, binaryName, null, "java/lang/Object", null); |
| |
| classWriter.visitInnerClass( |
| "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC); |
| |
| { |
| methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(1, 1); |
| methodVisitor.visitEnd(); |
| } |
| { |
| methodVisitor = |
| classWriter.visitMethod( |
| ACC_PUBLIC | ACC_STATIC, "callApi", "(Landroid/content/Context;)V", null, null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", "I"); |
| methodVisitor.visitIntInsn(BIPUSH, 26); |
| Label label0 = new Label(); |
| methodVisitor.visitJumpInsn(IF_ICMPLT, label0); |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitMethodInsn( |
| INVOKESTATIC, apiCallerName, apiMethodCaller, "(Landroid/content/Context;)V", false); |
| methodVisitor.visitLabel(label0); |
| methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(2, 1); |
| methodVisitor.visitEnd(); |
| } |
| classWriter.visitEnd(); |
| |
| return Pair.create(classWriter.toByteArray(), binaryName); |
| } |
| |
| public static Pair<byte[], String> dumpApiCallerInlined( |
| int index, |
| String apiMethodCaller, |
| String newApiClassName, |
| String newApiClassMethodName, |
| String existingApiNewMethodName) { |
| |
| ClassWriter classWriter = new ClassWriter(0); |
| MethodVisitor methodVisitor; |
| |
| String binaryName = |
| "com/example/softverificationsample/ApiCallerInlined" + (index > -1 ? index : ""); |
| |
| classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, binaryName, null, "java/lang/Object", null); |
| |
| classWriter.visitInnerClass( |
| "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC); |
| |
| { |
| methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(1, 1); |
| methodVisitor.visitEnd(); |
| } |
| { |
| methodVisitor = |
| classWriter.visitMethod( |
| ACC_PUBLIC | ACC_STATIC, "callApi", "(Landroid/content/Context;)V", null, null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", "I"); |
| methodVisitor.visitIntInsn(BIPUSH, 26); |
| Label label0 = new Label(); |
| methodVisitor.visitJumpInsn(IF_ICMPLT, label0); |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitMethodInsn( |
| INVOKESTATIC, binaryName, apiMethodCaller, "(Landroid/content/Context;)V", false); |
| methodVisitor.visitLabel(label0); |
| methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(2, 1); |
| methodVisitor.visitEnd(); |
| } |
| { |
| methodVisitor = |
| classWriter.visitMethod( |
| ACC_PUBLIC | ACC_STATIC, |
| "constructUnknownObject", |
| "(Landroid/content/Context;)V", |
| null, |
| null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitTypeInsn(NEW, newApiClassName); |
| methodVisitor.visitInsn(DUP); |
| methodVisitor.visitLdcInsn("CHANNEL_ID"); |
| methodVisitor.visitLdcInsn("FOO"); |
| methodVisitor.visitFieldInsn( |
| GETSTATIC, "android/app/NotificationManager", "IMPORTANCE_DEFAULT", "I"); |
| methodVisitor.visitMethodInsn( |
| INVOKESPECIAL, |
| newApiClassName, |
| "<init>", |
| "(Ljava/lang/String;Ljava/lang/String;I)V", |
| false); |
| methodVisitor.visitVarInsn(ASTORE, 1); |
| methodVisitor.visitVarInsn(ALOAD, 1); |
| methodVisitor.visitLdcInsn("This is a test channel"); |
| methodVisitor.visitMethodInsn( |
| INVOKEVIRTUAL, newApiClassName, newApiClassMethodName, "(Ljava/lang/String;)V", false); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(5, 2); |
| methodVisitor.visitEnd(); |
| } |
| { |
| methodVisitor = |
| classWriter.visitMethod( |
| ACC_PUBLIC | ACC_STATIC, |
| "callUnknownMethod", |
| "(Landroid/content/Context;)V", |
| null, |
| null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitLdcInsn(Type.getType("Landroid/app/NotificationManager;")); |
| methodVisitor.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "android/content/Context", |
| "getSystemService", |
| "(Ljava/lang/Class;)Ljava/lang/Object;", |
| false); |
| methodVisitor.visitTypeInsn(CHECKCAST, "android/app/NotificationManager"); |
| methodVisitor.visitVarInsn(ASTORE, 1); |
| methodVisitor.visitVarInsn(ALOAD, 1); |
| methodVisitor.visitInsn(ACONST_NULL); |
| methodVisitor.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "android/app/NotificationManager", |
| existingApiNewMethodName, |
| "(L" + newApiClassName + ";)V", |
| false); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(2, 2); |
| methodVisitor.visitEnd(); |
| } |
| { |
| methodVisitor = |
| classWriter.visitMethod( |
| ACC_PUBLIC | ACC_STATIC, |
| "constructUnknownObjectAndCallUnknownMethod", |
| "(Landroid/content/Context;)V", |
| null, |
| null); |
| methodVisitor.visitCode(); |
| methodVisitor.visitTypeInsn(NEW, newApiClassName); |
| methodVisitor.visitInsn(DUP); |
| methodVisitor.visitLdcInsn("CHANNEL_ID"); |
| methodVisitor.visitLdcInsn("FOO"); |
| methodVisitor.visitFieldInsn( |
| GETSTATIC, "android/app/NotificationManager", "IMPORTANCE_DEFAULT", "I"); |
| methodVisitor.visitMethodInsn( |
| INVOKESPECIAL, |
| newApiClassName, |
| "<init>", |
| "(Ljava/lang/String;Ljava/lang/String;I)V", |
| false); |
| methodVisitor.visitVarInsn(ASTORE, 1); |
| methodVisitor.visitVarInsn(ALOAD, 1); |
| methodVisitor.visitLdcInsn("This is a test channel"); |
| methodVisitor.visitMethodInsn( |
| INVOKEVIRTUAL, newApiClassName, newApiClassMethodName, "(Ljava/lang/String;)V", false); |
| methodVisitor.visitVarInsn(ALOAD, 0); |
| methodVisitor.visitLdcInsn(Type.getType("Landroid/app/NotificationManager;")); |
| methodVisitor.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "android/content/Context", |
| "getSystemService", |
| "(Ljava/lang/Class;)Ljava/lang/Object;", |
| false); |
| methodVisitor.visitTypeInsn(CHECKCAST, "android/app/NotificationManager"); |
| methodVisitor.visitVarInsn(ASTORE, 2); |
| methodVisitor.visitVarInsn(ALOAD, 2); |
| methodVisitor.visitVarInsn(ALOAD, 1); |
| methodVisitor.visitMethodInsn( |
| INVOKEVIRTUAL, |
| "android/app/NotificationManager", |
| existingApiNewMethodName, |
| "(L" + newApiClassName + ";)V", |
| false); |
| methodVisitor.visitInsn(RETURN); |
| methodVisitor.visitMaxs(5, 3); |
| methodVisitor.visitEnd(); |
| } |
| classWriter.visitEnd(); |
| |
| return Pair.create(classWriter.toByteArray(), binaryName); |
| } |
| } |
| } |