1// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4package com.android.tools.r8.utils;
5
6import static junit.framework.TestCase.assertFalse;
7import static junit.framework.TestCase.assertTrue;
8
9import com.android.tools.r8.CompilationException;
10import com.android.tools.r8.R8Command;
11import com.android.tools.r8.ToolHelper;
12import com.android.tools.r8.graph.DexEncodedMethod;
13import com.android.tools.r8.shaking.ProguardRuleParserException;
14import com.android.tools.r8.utils.DexInspector.ClassSubject;
15import com.android.tools.r8.utils.DexInspector.MethodSubject;
16import java.io.IOException;
17import java.nio.file.Files;
18import java.nio.file.Path;
19import java.nio.file.Paths;
20import java.util.Arrays;
21import java.util.Collection;
22import java.util.Collections;
23import java.util.concurrent.ExecutionException;
24import org.junit.Before;
25import org.junit.Rule;
26import org.junit.Test;
27import org.junit.rules.ExpectedException;
28import org.junit.rules.TemporaryFolder;
29import org.junit.runner.RunWith;
30import org.junit.runners.Parameterized;
31import org.junit.runners.Parameterized.Parameters;
32
33@RunWith(Parameterized.class)
34public class R8InliningTest {
35
36  private static final String JAR_EXTENSION = ".jar";
37  private static final String DEFAULT_DEX_FILENAME = "classes.dex";
38  private static final String DEFAULT_MAP_FILENAME = "proguard.map";
39
40  @Parameters(name = "{0}")
41  public static Collection<Object[]> data() {
42    return Arrays.asList(new Object[][]{{"Inlining"}});
43  }
44
45  @Rule
46  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
47  private final String name;
48  private final String keepRulesFile;
49
50  public R8InliningTest(String name) {
51    this.name = name.toLowerCase();
52    this.keepRulesFile = ToolHelper.EXAMPLES_DIR + this.name + "/keep-rules.txt";
53  }
54
55  private Path getInputFile() {
56    return Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, name + JAR_EXTENSION);
57  }
58
59  private Path getOriginalDexFile() {
60    return Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, name, DEFAULT_DEX_FILENAME);
61  }
62
63  private Path getGeneratedDexFile() throws IOException {
64    return Paths.get(temp.getRoot().getCanonicalPath(), DEFAULT_DEX_FILENAME);
65  }
66
67  private String getGeneratedProguardMap() throws IOException {
68    Path mapFile = Paths.get(temp.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME);
69    if (Files.exists(mapFile)) {
70      return mapFile.toAbsolutePath().toString();
71    }
72    return null;
73  }
74
75  @Rule
76  public ExpectedException thrown = ExpectedException.none();
77
78  @Before
79  public void generateR8Version()
80      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
81    Path out = temp.getRoot().toPath();
82    R8Command command =
83        R8Command.builder()
84            .addProgramFiles(getInputFile())
85            .setOutputPath(out)
86            .addProguardConfigurationFiles(Paths.get(keepRulesFile))
87            .build();
88    ToolHelper.runR8(command);
89    ToolHelper.runArtNoVerificationErrors(out + "/classes.dex", "inlining.Inlining");
90  }
91
92  private void checkAbsent(ClassSubject clazz, String name) {
93    assertTrue(clazz.isPresent());
94    MethodSubject method = clazz.method("boolean", name, Collections.emptyList());
95    assertFalse(method.isPresent());
96  }
97
98  private void dump(DexEncodedMethod method) {
99    System.out.println(method);
100    System.out.println(method.codeToString());
101  }
102
103  private void dump(Path path, String title) throws Throwable {
104    System.out.println(title + ":");
105    DexInspector inspector = new DexInspector(path.toAbsolutePath());
106    inspector.clazz("inlining.Inlining").forAllMethods(m -> dump(m.getMethod()));
107    System.out.println(title + " size: " + Files.size(path));
108  }
109
110  @Test
111  public void checkNoInvokes() throws Throwable {
112    DexInspector inspector = new DexInspector(getGeneratedDexFile().toAbsolutePath(),
113        getGeneratedProguardMap());
114    ClassSubject clazz = inspector.clazz("inlining.Inlining");
115    // Simple constant inlining.
116    checkAbsent(clazz, "longExpression");
117    checkAbsent(clazz, "intExpression");
118    checkAbsent(clazz, "doubleExpression");
119    checkAbsent(clazz, "floatExpression");
120    // Simple return argument inlining.
121    checkAbsent(clazz, "longArgumentExpression");
122    checkAbsent(clazz, "intArgumentExpression");
123    checkAbsent(clazz, "doubleArgumentExpression");
124    checkAbsent(clazz, "floatArgumentExpression");
125  }
126
127  @Test
128  public void processedFileIsSmaller() throws Throwable {
129    long original = Files.size(getOriginalDexFile());
130    long generated = Files.size(getGeneratedDexFile());
131    final boolean ALWAYS_DUMP = false;  // Used for debugging.
132    if (ALWAYS_DUMP || generated > original) {
133      dump(getOriginalDexFile(), "Original");
134      dump(getGeneratedDexFile(), "Generated");
135    }
136    assertTrue("Inlining failed to reduce size", original > generated);
137  }
138}
139