| // Copyright (c) 2016, 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.smali; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.R8; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.dex.ApplicationReader; |
| import com.android.tools.r8.graph.AppInfo; |
| import com.android.tools.r8.graph.AppInfoWithSubtyping; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.ValueNumberGenerator; |
| import com.android.tools.r8.ir.conversion.IRConverter; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.shaking.ProguardRuleParserException; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.DexInspector; |
| import com.android.tools.r8.utils.DexInspector.ClassSubject; |
| import com.android.tools.r8.utils.DexInspector.MethodSubject; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.OutputMode; |
| import com.android.tools.r8.utils.Smali; |
| import com.android.tools.r8.utils.Timing; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executors; |
| import org.antlr.runtime.RecognitionException; |
| import org.junit.Rule; |
| import org.junit.rules.TemporaryFolder; |
| |
| public class SmaliTestBase { |
| |
| public static final String DEFAULT_CLASS_NAME = "Test"; |
| public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME; |
| public static final String DEFAULT_METHOD_NAME = "method"; |
| |
| @Rule |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); |
| |
| public static class MethodSignature { |
| |
| public final String clazz; |
| public final String name; |
| public final String returnType; |
| public final List<String> parameterTypes; |
| |
| public MethodSignature(String clazz, String name, String returnType, |
| List<String> parameterTypes) { |
| this.clazz = clazz; |
| this.name = name; |
| this.returnType = returnType; |
| this.parameterTypes = parameterTypes; |
| } |
| |
| public static MethodSignature staticInitializer(String clazz) { |
| return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of()); |
| } |
| } |
| |
| public static class SmaliBuilder { |
| |
| abstract class Builder { |
| String name; |
| String superName; |
| List<String> implementedInterfaces; |
| List<String> source = new ArrayList<>(); |
| |
| Builder(String name, String superName, List<String> implementedInterfaces) { |
| this.name = name; |
| this.superName = superName; |
| this.implementedInterfaces = implementedInterfaces; |
| } |
| |
| protected void appendSuper(StringBuilder builder) { |
| builder.append(".super "); |
| builder.append(DescriptorUtils.javaTypeToDescriptor(superName)); |
| builder.append("\n"); |
| } |
| |
| protected void appendImplementedInterfaces(StringBuilder builder) { |
| for (String implementedInterface : implementedInterfaces) { |
| builder.append(".implements "); |
| builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface)); |
| builder.append("\n"); |
| } |
| } |
| |
| protected void writeSource(StringBuilder builder) { |
| for (String sourceLine : source) { |
| builder.append(sourceLine); |
| builder.append("\n"); |
| } |
| } |
| } |
| |
| public class ClassBuilder extends Builder { |
| |
| ClassBuilder(String name, String superName, List<String> implementedInterfaces) { |
| super(name, superName, implementedInterfaces); |
| } |
| |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(".class public "); |
| builder.append(DescriptorUtils.javaTypeToDescriptor(name)); |
| builder.append("\n"); |
| appendSuper(builder); |
| appendImplementedInterfaces(builder); |
| builder.append("\n"); |
| writeSource(builder); |
| return builder.toString(); |
| } |
| } |
| |
| public class InterfaceBuilder extends Builder { |
| |
| InterfaceBuilder(String name, String superName) { |
| super(name, superName, ImmutableList.of()); |
| } |
| |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(".class public interface abstract "); |
| builder.append(DescriptorUtils.javaTypeToDescriptor(name)); |
| builder.append("\n"); |
| appendSuper(builder); |
| appendImplementedInterfaces(builder); |
| builder.append("\n"); |
| writeSource(builder); |
| return builder.toString(); |
| } |
| } |
| |
| private String currentClassName; |
| private final Map<String, Builder> classes = new HashMap<>(); |
| |
| public SmaliBuilder() { |
| // No default class. |
| } |
| |
| public SmaliBuilder(String name) { |
| addClass(name); |
| } |
| |
| public SmaliBuilder(String name, String superName) { |
| addClass(name, superName); |
| } |
| |
| private List<String> getSource(String clazz) { |
| return classes.get(clazz).source; |
| } |
| |
| public String getCurrentClassName() { |
| return currentClassName; |
| } |
| |
| public String getCurrentClassDescriptor() { |
| return DescriptorUtils.javaTypeToDescriptor(currentClassName); |
| } |
| |
| public void addClass(String name) { |
| addClass(name, "java.lang.Object"); |
| } |
| |
| public void addClass(String name, String superName) { |
| addClass(name, superName, ImmutableList.of()); |
| } |
| |
| public void addClass(String name, String superName, List<String> implementedInterfaces) { |
| assert !classes.containsKey(name); |
| currentClassName = name; |
| classes.put(name, new ClassBuilder(name, superName, implementedInterfaces)); |
| } |
| |
| public void addInterface(String name) { |
| addInterface(name, "java.lang.Object"); |
| } |
| |
| public void addInterface(String name, String superName) { |
| assert !classes.containsKey(name); |
| currentClassName = name; |
| classes.put(name, new InterfaceBuilder(name, superName)); |
| } |
| |
| public void addDefaultConstructor() { |
| String superDescriptor = |
| DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName); |
| addMethodRaw( |
| " .method public constructor <init>()V", |
| " .locals 0", |
| " invoke-direct {p0}, " + superDescriptor + "-><init>()V", |
| " return-void", |
| " .end method" |
| ); |
| } |
| |
| public void addStaticField(String name, String type, String defaultValue) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(".field static "); |
| builder.append(name); |
| builder.append(":"); |
| builder.append(type); |
| if (defaultValue != null) { |
| builder.append(" = "); |
| if (type.equals("Ljava/lang/String;")) { |
| builder.append('"'); |
| builder.append(defaultValue); |
| builder.append('"'); |
| } else { |
| builder.append(defaultValue); |
| } |
| } |
| getSource(currentClassName).add(builder.toString()); |
| } |
| |
| public void addStaticField(String name, String type) { |
| addStaticField(name, type, null); |
| } |
| |
| public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters, |
| int locals, String... instructions) { |
| StringBuilder builder = new StringBuilder(); |
| for (String instruction : instructions) { |
| builder.append(instruction); |
| builder.append("\n"); |
| } |
| return addStaticMethod(returnType, name, parameters, locals, builder.toString()); |
| } |
| |
| public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters, |
| int locals, String code) { |
| return addStaticMethod("", returnType, name, parameters, locals, code); |
| } |
| |
| public MethodSignature addStaticInitializer(int locals, String... instructions) { |
| StringBuilder builder = new StringBuilder(); |
| for (String instruction : instructions) { |
| builder.append(instruction); |
| builder.append("\n"); |
| } |
| return addStaticInitializer(locals, builder.toString()); |
| } |
| |
| public MethodSignature addStaticInitializer(int locals, String code) { |
| return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code); |
| } |
| |
| private MethodSignature addStaticMethod(String flags, String returnType, String name, |
| List<String> parameters, int locals, String code) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(".method public static "); |
| if (flags != null && flags.length() > 0) { |
| builder.append(flags); |
| builder.append(" "); |
| } |
| builder.append(name); |
| builder.append("("); |
| for (String parameter : parameters) { |
| builder.append(DescriptorUtils.javaTypeToDescriptor(parameter)); |
| } |
| builder.append(")"); |
| builder.append(DescriptorUtils.javaTypeToDescriptor(returnType)); |
| builder.append("\n"); |
| builder.append(".locals "); |
| builder.append(locals); |
| builder.append("\n\n"); |
| builder.append(code); |
| builder.append(".end method"); |
| getSource(currentClassName).add(builder.toString()); |
| return new MethodSignature(currentClassName, name, returnType, parameters); |
| } |
| |
| public MethodSignature addAbstractMethod( |
| String returnType, String name, List<String> parameters) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(".method public abstract "); |
| builder.append(name); |
| builder.append("("); |
| for (String parameter : parameters) { |
| builder.append(DescriptorUtils.javaTypeToDescriptor(parameter)); |
| } |
| builder.append(")"); |
| builder.append(DescriptorUtils.javaTypeToDescriptor(returnType)); |
| builder.append("\n"); |
| builder.append(".end method"); |
| getSource(currentClassName).add(builder.toString()); |
| return new MethodSignature(currentClassName, name, returnType, parameters); |
| } |
| |
| public MethodSignature addMainMethod(int locals, String... instructions) { |
| return addStaticMethod( |
| "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions); |
| } |
| |
| public void addMethodRaw(String... source) { |
| StringBuilder builder = new StringBuilder(); |
| for (String line : source) { |
| builder.append(line); |
| builder.append("\n"); |
| } |
| getSource(currentClassName).add(builder.toString()); |
| } |
| |
| public List<String> build() { |
| List<String> result = new ArrayList<>(classes.size()); |
| for (String clazz : classes.keySet()) { |
| Builder classBuilder = classes.get(clazz); |
| result.add(classBuilder.toString()); |
| } |
| return result; |
| } |
| |
| public byte[] compile() throws IOException, RecognitionException { |
| return Smali.compile(build()); |
| } |
| |
| @Override |
| public String toString() { |
| return String.join("\n\n", build()); |
| } |
| } |
| |
| public class TestApplication { |
| |
| public final DexApplication application; |
| public final DexEncodedMethod method; |
| public final IRCode code; |
| public final List<IRCode> additionalCode; |
| public final ValueNumberGenerator valueNumberGenerator; |
| public final InternalOptions options; |
| |
| public TestApplication( |
| DexApplication application, |
| DexEncodedMethod method, |
| IRCode code, |
| ValueNumberGenerator valueNumberGenerator, |
| InternalOptions options) { |
| this(application, method, code, null, valueNumberGenerator, options); |
| } |
| |
| public TestApplication( |
| DexApplication application, |
| DexEncodedMethod method, |
| IRCode code, |
| List<IRCode> additionalCode, |
| ValueNumberGenerator valueNumberGenerator, |
| InternalOptions options) { |
| this.application = application; |
| this.method = method; |
| this.code = code; |
| this.additionalCode = additionalCode; |
| this.valueNumberGenerator = valueNumberGenerator; |
| this.options = options; |
| } |
| |
| public int countArgumentInstructions() { |
| int count = 0; |
| ListIterator<Instruction> iterator = code.blocks.get(0).listIterator(); |
| while (iterator.next().isArgument()) { |
| count++; |
| } |
| return count; |
| } |
| |
| public InstructionListIterator listIteratorAt(BasicBlock block, int index) { |
| InstructionListIterator iterator = block.listIterator(); |
| for (int i = 0; i < index; i++) { |
| iterator.next(); |
| } |
| return iterator; |
| } |
| |
| public String run() { |
| AppInfo appInfo = new AppInfo(application); |
| IRConverter converter = new IRConverter(application, appInfo, options); |
| converter.replaceCodeForTesting(method, code); |
| return runArt(application, options); |
| } |
| } |
| |
| protected DexApplication buildApplication(SmaliBuilder builder) { |
| return buildApplication(builder, new InternalOptions()); |
| } |
| |
| protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) { |
| try { |
| return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options); |
| } catch (IOException | RecognitionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected DexApplication buildApplicationWithAndroidJar( |
| SmaliBuilder builder, InternalOptions options) { |
| try { |
| AndroidApp input = AndroidApp.builder() |
| .addDexProgramData(builder.compile()) |
| .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar())) |
| .build(); |
| return buildApplication(input, options); |
| } catch (IOException | RecognitionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected DexApplication buildApplication(AndroidApp input, InternalOptions options) { |
| try { |
| options.itemFactory.resetSortedIndices(); |
| return new ApplicationReader(input, options, new Timing("SmaliTest")).read(); |
| } catch (IOException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected DexApplication processApplication(DexApplication application, InternalOptions options) { |
| try { |
| return ToolHelper.optimizeWithR8(application, new AppInfoWithSubtyping(application), options); |
| } catch (IOException | ProguardRuleParserException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected DexClass getClass(DexApplication application, String className) { |
| DexInspector inspector = new DexInspector(application); |
| ClassSubject clazz = inspector.clazz(className); |
| assertTrue(clazz.isPresent()); |
| return clazz.getDexClass(); |
| } |
| |
| protected DexClass getClass(DexApplication application, MethodSignature signature) { |
| return getClass(application, signature.clazz); |
| } |
| |
| protected DexEncodedMethod getMethod(DexApplication application, String className, |
| String returnType, String methodName, List<String> parameters) { |
| DexInspector inspector = new DexInspector(application); |
| ClassSubject clazz = inspector.clazz(className); |
| assertTrue(clazz.isPresent()); |
| MethodSubject method = clazz.method(returnType, methodName, parameters); |
| assertTrue(method.isPresent()); |
| return method.getMethod(); |
| } |
| |
| protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) { |
| return getMethod(application, |
| signature.clazz, signature.returnType, signature.name, signature.parameterTypes); |
| } |
| |
| /** |
| * Create an application with one method, and processed that application using R8. |
| * |
| * Returns the processed method for inspection. |
| * |
| * @param returnType the return type for the method |
| * @param parameters the parameter types for the method |
| * @param locals number of locals needed for the application |
| * @param instructions instructions for the method |
| * @return the processed method for inspection |
| */ |
| public DexApplication singleMethodApplication(String returnType, List<String> parameters, |
| int locals, String... instructions) { |
| // Build a one class method. |
| SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); |
| builder.addStaticMethod(returnType, DEFAULT_METHOD_NAME, parameters, locals, instructions); |
| |
| // Read the one class method as an application. |
| DexApplication application = buildApplication(builder); |
| assertEquals(1, Iterables.size(application.classes())); |
| return application; |
| } |
| |
| /** |
| * Create an application with one method, and processed that application using R8. |
| * |
| * Returns the processed method for inspection. |
| * |
| * @param returnType the return type for the method |
| * @param parameters the parameter types for the method |
| * @param locals number of locals needed for the application |
| * @param instructions instructions for the method |
| * @return the processed method for inspection |
| */ |
| public DexEncodedMethod oneMethodApplication(String returnType, List<String> parameters, |
| int locals, String... instructions) { |
| InternalOptions options = new InternalOptions(); |
| |
| // Build a one class application. |
| DexApplication application = singleMethodApplication( |
| returnType, parameters, locals, instructions); |
| |
| // Process the application with R8. |
| DexApplication processdApplication = processApplication(application, options); |
| assertEquals(1, Iterables.size(processdApplication.classes())); |
| |
| // Return the processed method for inspection. |
| return getMethod( |
| processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters); |
| } |
| |
| public String runArt(DexApplication application, InternalOptions options) { |
| return runArt(application, options, DEFAULT_MAIN_CLASS_NAME); |
| } |
| |
| public String runArt(DexApplication application, InternalOptions options, String mainClass) { |
| try { |
| AndroidApp app = writeDex(application, options); |
| Path out = temp.getRoot().toPath().resolve("run-art-input.zip"); |
| // TODO(sgjesse): Pass in a unique temp directory for each run. |
| app.writeToZip(out, OutputMode.Indexed); |
| return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public void runDex2Oat(DexApplication application, InternalOptions options) { |
| try { |
| AndroidApp app = writeDex(application, options); |
| Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip"); |
| Path oatFile = temp.getRoot().toPath().resolve("oat-file"); |
| app.writeToZip(dexOut, OutputMode.Indexed); |
| ToolHelper.runDex2Oat(dexOut, oatFile); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public AndroidApp writeDex(DexApplication application, InternalOptions options) { |
| AppInfo appInfo = new AppInfo(application); |
| try { |
| return R8.writeApplication( |
| Executors.newSingleThreadExecutor(), |
| application, |
| appInfo, |
| null, |
| NamingLens.getIdentityLens(), |
| null, |
| null, |
| options); |
| } catch (ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| } |