| // 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; | 
 |   } | 
 | } |