blob: 50f6e055db1c2dd2df7044c3fbcb03f8a6b63dbc [file] [log] [blame]
// 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);
}
}
}