| // 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); | 
 |     } | 
 |   } | 
 | } |