|  | // Copyright (c) 2017, 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 static org.hamcrest.CoreMatchers.hasItems; | 
|  | import static org.hamcrest.CoreMatchers.is; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.smali.SmaliBuilder; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.google.common.collect.Lists; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.io.IOException; | 
|  | import java.lang.annotation.Retention; | 
|  | import java.lang.annotation.RetentionPolicy; | 
|  | import java.nio.file.Path; | 
|  | import java.util.List; | 
|  | import java.util.Objects; | 
|  | import java.util.Set; | 
|  | import java.util.concurrent.ExecutionException; | 
|  | import org.junit.Rule; | 
|  | import org.junit.Test; | 
|  | import org.junit.rules.TemporaryFolder; | 
|  |  | 
|  | /** | 
|  | * Tests for resource shrinker analyzer. This is checking that dex files are processed correctly. | 
|  | */ | 
|  | public class ResourceShrinkerTest extends TestBase { | 
|  |  | 
|  | @Rule | 
|  | public TemporaryFolder tmp = new TemporaryFolder(); | 
|  |  | 
|  | private static class TrackAll implements ResourceShrinker.ReferenceChecker { | 
|  | Set<Integer> integers = Sets.newHashSet(); | 
|  | Set<String> strings = Sets.newHashSet(); | 
|  | List<List<String>> fields = Lists.newArrayList(); | 
|  | List<List<String>> methods = Lists.newArrayList(); | 
|  |  | 
|  | @Override | 
|  | public boolean shouldProcess(String internalName) { | 
|  | return !internalName.equals(ResourceClassToSkip.class.getName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void referencedInt(int value) { | 
|  | integers.add(value); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void referencedString(String value) { | 
|  | strings.add(value); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void referencedStaticField(String internalName, String fieldName) { | 
|  | fields.add(Lists.newArrayList(internalName, fieldName)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void referencedMethod(String internalName, String methodName, String methodDescriptor) { | 
|  | if (Objects.equals(internalName, "java/lang/Object") | 
|  | && Objects.equals(methodName, "<init>")) { | 
|  | return; | 
|  | } | 
|  | methods.add(Lists.newArrayList(internalName, methodName, methodDescriptor)); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class EmptyClass { | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testEmptyClass() throws CompilationFailedException, IOException, ExecutionException { | 
|  | TrackAll analysis = runAnalysis(EmptyClass.class); | 
|  |  | 
|  | assertThat(analysis.integers, is(Sets.newHashSet())); | 
|  | assertThat(analysis.strings, is(Sets.newHashSet())); | 
|  | assertThat(analysis.fields, is(Lists.newArrayList())); | 
|  | assertThat(analysis.methods, is(Lists.newArrayList())); | 
|  | } | 
|  |  | 
|  | private static class ConstInCode { | 
|  | public void foo() { | 
|  | int i = 10; | 
|  | System.out.print(i); | 
|  | System.out.print(11); | 
|  | String s = "my_layout"; | 
|  | System.out.print("another_layout"); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testConstsAndFieldAndMethods() | 
|  | throws CompilationFailedException, IOException, ExecutionException { | 
|  | TrackAll analysis = runAnalysis(ConstInCode.class); | 
|  |  | 
|  | assertThat(analysis.integers, is(Sets.newHashSet(10, 11))); | 
|  | assertThat(analysis.strings, is(Sets.newHashSet("my_layout", "another_layout"))); | 
|  |  | 
|  | assertEquals(3, analysis.fields.size()); | 
|  | assertThat(analysis.fields.get(0), is(Lists.newArrayList("java/lang/System", "out"))); | 
|  | assertThat(analysis.fields.get(1), is(Lists.newArrayList("java/lang/System", "out"))); | 
|  | assertThat(analysis.fields.get(2), is(Lists.newArrayList("java/lang/System", "out"))); | 
|  |  | 
|  | assertEquals(3, analysis.methods.size()); | 
|  | assertThat(analysis.methods.get(0), | 
|  | is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V"))); | 
|  | assertThat(analysis.methods.get(1), | 
|  | is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V"))); | 
|  | assertThat(analysis.methods.get(2), | 
|  | is(Lists.newArrayList("java/io/PrintStream", "print", "(Ljava/lang/String;)V"))); | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("unused") | 
|  | private static class StaticFields { | 
|  | static final String sStringValue = "staticValue"; | 
|  | static final int sIntValue = 10; | 
|  | static final int[] sIntArrayValue = {11, 12, 13}; | 
|  | static final String[] sStringArrayValue = {"a", "b", "c"}; | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testStaticValues() | 
|  | throws CompilationFailedException, IOException, ExecutionException { | 
|  | TrackAll analysis = runAnalysis(StaticFields.class); | 
|  |  | 
|  | assertThat(analysis.integers, hasItems(10, 11, 12, 13)); | 
|  | assertThat(analysis.strings, hasItems("staticValue", "a", "b", "c")); | 
|  | assertThat(analysis.fields, is(Lists.newArrayList())); | 
|  | assertThat(analysis.methods, is(Lists.newArrayList())); | 
|  | } | 
|  |  | 
|  | @Retention(RetentionPolicy.RUNTIME) | 
|  | private @interface IntAnnotation { | 
|  | int value() default 10; | 
|  | } | 
|  |  | 
|  | @Retention(RetentionPolicy.RUNTIME) | 
|  | private @interface OuterAnnotation { | 
|  | IntAnnotation inner() default @IntAnnotation(11); | 
|  | } | 
|  |  | 
|  | @IntAnnotation(42) | 
|  | private static class Annotated { | 
|  | @OuterAnnotation | 
|  | Object defaultAnnotated = new Object(); | 
|  | @IntAnnotation(12) | 
|  | Object withValueAnnotated = new Object(); | 
|  | @IntAnnotation(13) | 
|  | static Object staticValueAnnotated = new Object(); | 
|  |  | 
|  | @IntAnnotation(14) | 
|  | public void annotatedPublic() { | 
|  | } | 
|  |  | 
|  | @IntAnnotation(15) | 
|  | public void annotatedPrivate() { | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testAnnotations() throws CompilationFailedException, IOException, ExecutionException { | 
|  | TrackAll analysis = runAnalysis(IntAnnotation.class, OuterAnnotation.class, Annotated.class); | 
|  |  | 
|  | assertThat(analysis.integers, hasItems(10, 11, 12, 13, 14, 15, 42)); | 
|  | assertThat(analysis.strings, is(Sets.newHashSet())); | 
|  | assertThat(analysis.fields, is(Lists.newArrayList())); | 
|  | assertThat(analysis.methods, is(Lists.newArrayList())); | 
|  | } | 
|  |  | 
|  | private static class ResourceClassToSkip { | 
|  | int[] i = {100, 101, 102}; | 
|  | } | 
|  |  | 
|  | private static class ToProcess { | 
|  | int[] i = {10, 11, 12}; | 
|  | String[] s = {"10", "11", "12"}; | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testWithSkippingSome() | 
|  | throws ExecutionException, CompilationFailedException, IOException { | 
|  | TrackAll analysis = runAnalysis(ResourceClassToSkip.class, ToProcess.class); | 
|  |  | 
|  | assertThat(analysis.integers, hasItems(10, 11, 12)); | 
|  | assertThat(analysis.strings, is(Sets.newHashSet("10", "11", "12"))); | 
|  | assertThat(analysis.fields, is(Lists.newArrayList())); | 
|  | assertThat(analysis.methods, is(Lists.newArrayList())); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testPayloadBeforeFillArrayData() throws Exception { | 
|  | SmaliBuilder builder = new SmaliBuilder("Test"); | 
|  | builder.addMainMethod( | 
|  | 2, | 
|  | "goto :start", | 
|  | "", | 
|  | ":array_data", | 
|  | ".array-data 4", | 
|  | "  4 5 6", | 
|  | ".end array-data", | 
|  | "", | 
|  | ":start", | 
|  | "const/4 v1, 3", | 
|  | "new-array v0, v1, [I", | 
|  | "fill-array-data v0, :array_data", | 
|  | "return-object v0" | 
|  | ); | 
|  | AndroidApp app = | 
|  | AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(); | 
|  | TrackAll analysis = runOnApp(app); | 
|  |  | 
|  | assertThat(analysis.integers, hasItems(4, 5, 6)); | 
|  | assertThat(analysis.strings, is(Sets.newHashSet())); | 
|  | assertThat(analysis.fields, is(Lists.newArrayList())); | 
|  | assertThat(analysis.methods, is(Lists.newArrayList())); | 
|  | } | 
|  |  | 
|  | private TrackAll runAnalysis(Class<?>... classes) | 
|  | throws IOException, ExecutionException, CompilationFailedException { | 
|  | AndroidApp app = readClasses(classes); | 
|  | return runOnApp(app); | 
|  | } | 
|  |  | 
|  | private TrackAll runOnApp(AndroidApp app) | 
|  | throws IOException, ExecutionException, CompilationFailedException { | 
|  | AndroidApp outputApp = compileWithD8(app); | 
|  | Path outputDex = tmp.newFolder().toPath().resolve("classes.dex"); | 
|  | outputApp.writeToDirectory(outputDex.getParent(), OutputMode.DexIndexed); | 
|  |  | 
|  | ProgramResourceProvider provider = | 
|  | () -> Lists.newArrayList(ProgramResource.fromFile(ProgramResource.Kind.DEX, outputDex)); | 
|  | ResourceShrinker.Command command = | 
|  | new ResourceShrinker.Builder().addProgramResourceProvider(provider).build(); | 
|  |  | 
|  | TrackAll analysis = new TrackAll(); | 
|  | ResourceShrinker.run(command, analysis); | 
|  | return analysis; | 
|  | } | 
|  | } |