| // 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.CompilationFailedException; |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.OutputMode; |
| import com.android.tools.r8.ProgramResource; |
| import com.android.tools.r8.R8Command; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.dex.ApplicationReader; |
| 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.graph.ProgramMethod; |
| import com.android.tools.r8.origin.EmbeddedOrigin; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.shaking.ProguardConfiguration; |
| import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Timing; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| import java.util.function.Consumer; |
| import org.antlr.runtime.RecognitionException; |
| |
| public class SmaliTestBase extends TestBase { |
| |
| 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"; |
| |
| protected AndroidApp buildApplication(SmaliBuilder builder) { |
| try { |
| return AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(); |
| } catch (IOException | RecognitionException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected AndroidApp buildApplicationWithAndroidJar(SmaliBuilder builder) { |
| try { |
| return AndroidApp.builder() |
| .addDexProgramData(builder.compile(), EmbeddedOrigin.INSTANCE) |
| .addLibraryFiles(ToolHelper.getDefaultAndroidJar()) |
| .build(); |
| } catch (IOException | RecognitionException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected DexApplication buildApplication(AndroidApp input, InternalOptions options) { |
| try { |
| return new ApplicationReader(input, options, Timing.empty()).read(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected AndroidApp processApplication(AndroidApp application) |
| throws CompilationFailedException { |
| return processApplication(application, null); |
| } |
| |
| protected AndroidApp processApplication( |
| AndroidApp application, Consumer<InternalOptions> optionsConsumer) |
| throws CompilationFailedException { |
| return ToolHelper.runR8(application, optionsConsumer); |
| } |
| |
| protected Path runR8(SmaliBuilder builder, List<String> proguardConfigurations) { |
| return runR8(builder, proguardConfigurations, pg -> {}, o -> {}); |
| } |
| |
| protected Path runR8( |
| SmaliBuilder builder, |
| List<String> proguardConfigurations, |
| Consumer<ProguardConfiguration.Builder> pgConsumer, |
| Consumer<InternalOptions> optionsConsumer) { |
| try { |
| Path dexOutputDir = temp.newFolder().toPath(); |
| R8Command.Builder command = |
| ToolHelper.addProguardConfigurationConsumer(R8Command.builder(), pgConsumer) |
| .setOutput(dexOutputDir, OutputMode.DexIndexed) |
| .setMode(CompilationMode.DEBUG) |
| .addLibraryFiles(ToolHelper.getDefaultAndroidJar()) |
| .addProguardConfiguration(proguardConfigurations, Origin.unknown()); |
| ToolHelper.getAppBuilder(command) |
| .addDexProgramData(builder.compile(), EmbeddedOrigin.INSTANCE); |
| ToolHelper.runR8WithFullResult(command.build(), optionsConsumer); |
| return dexOutputDir.resolve("classes.dex"); |
| } catch (IOException |
| | RecognitionException |
| | ExecutionException |
| | CompilationFailedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected DexClass getClass(DexApplication application, String className) { |
| CodeInspector inspector = new CodeInspector(application); |
| ClassSubject clazz = inspector.clazz(className); |
| assertTrue(clazz.isPresent()); |
| return clazz.getDexProgramClass(); |
| } |
| |
| protected DexClass getClass(DexApplication application, MethodSignature signature) { |
| return getClass(application, signature.clazz); |
| } |
| |
| protected DexClass getClass(Path appPath, String className) { |
| try { |
| CodeInspector inspector = new CodeInspector(appPath); |
| ClassSubject clazz = inspector.clazz(className); |
| assertTrue(clazz.isPresent()); |
| return clazz.getDexProgramClass(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected MethodSubject getMethodSubject(Path appPath, MethodSignature signature) { |
| try { |
| CodeInspector inspector = new CodeInspector(appPath); |
| return getMethodSubject(inspector, signature); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected MethodSubject getMethodSubject(CodeInspector inspector, MethodSignature signature) { |
| return getMethodSubject( |
| inspector, signature.clazz, signature.returnType, signature.name, signature.parameterTypes); |
| } |
| |
| protected MethodSubject getMethodSubject(AndroidApp application, MethodSignature signature) { |
| return getMethodSubject( |
| application, |
| signature.clazz, |
| signature.returnType, |
| signature.name, |
| signature.parameterTypes); |
| } |
| |
| protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) { |
| return getMethodSubject(appPath, signature).getMethod(); |
| } |
| |
| protected DexEncodedMethod getMethod(CodeInspector inspector, MethodSignature signature) { |
| return getMethodSubject(inspector, signature).getMethod(); |
| } |
| |
| protected DexEncodedMethod getMethod(AndroidApp application, MethodSignature signature) { |
| return getMethodSubject(application, signature).getMethod(); |
| } |
| |
| protected ProgramMethod getProgramMethod(AndroidApp application, MethodSignature signature) { |
| return getMethodSubject(application, signature).getProgramMethod(); |
| } |
| |
| /** |
| * 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 AndroidApp 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. |
| return buildApplication(builder); |
| } |
| |
| private int getNumberOfClassesForResources(Iterable<ProgramResource> resources) { |
| int count = 0; |
| for (ProgramResource resource : resources) { |
| Collection<String> descriptors = resource.getClassDescriptors(); |
| if (descriptors == null) { |
| throw new IllegalStateException("Cannot count classes in application without descriptors."); |
| } |
| count += descriptors.size(); |
| } |
| return count; |
| } |
| |
| protected int getNumberOfProgramClasses(AndroidApp application) { |
| try { |
| return getNumberOfClassesForResources(application.getClassProgramResourcesForTesting()) |
| + getNumberOfClassesForResources(application.getDexProgramResourcesForTesting()); |
| } catch (IOException e) { |
| return -1; |
| } |
| } |
| |
| /** |
| * 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) { |
| // Build a one class application. |
| AndroidApp application = singleMethodApplication( |
| returnType, parameters, locals, instructions); |
| |
| // Process the application with R8. |
| AndroidApp processdApplication; |
| try { |
| processdApplication = processApplication(application); |
| } catch (CompilationFailedException e) { |
| throw new RuntimeException(e); |
| } |
| assertEquals(1, getNumberOfProgramClasses(processdApplication)); |
| |
| // Return the processed method for inspection. |
| return getMethodSubject( |
| processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters) |
| .getMethod(); |
| } |
| |
| public String runArt(AndroidApp application) { |
| return runArt(application, DEFAULT_MAIN_CLASS_NAME); |
| } |
| |
| public String runArt(AndroidApp application, String mainClass) { |
| try { |
| Path out = temp.getRoot().toPath().resolve("run-art-input.zip"); |
| // TODO(sgjesse): Pass in a unique temp directory for each run. |
| application.writeToZip(out, OutputMode.DexIndexed); |
| return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public static String runArt(Path path, String mainClass) { |
| try { |
| return ToolHelper.runArtNoVerificationErrors(path.toString(), mainClass); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public void runDex2Oat(AndroidApp application) { |
| try { |
| Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip"); |
| Path oatFile = temp.getRoot().toPath().resolve("oat-file"); |
| application.writeToZip(dexOut, OutputMode.DexIndexed); |
| ToolHelper.runDex2Oat(dexOut, oatFile); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |