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. 4 package com.android.tools.r8.jsr45; 5 6 import com.android.tools.r8.CompilationException; 7 import com.android.tools.r8.D8; 8 import com.android.tools.r8.D8Command; 9 import com.android.tools.r8.R8; 10 import com.android.tools.r8.R8Command; 11 import com.android.tools.r8.ToolHelper; 12 import com.android.tools.r8.dex.Constants; 13 import com.android.tools.r8.graph.DexAnnotationElement; 14 import com.android.tools.r8.graph.DexValue.DexValueString; 15 import com.android.tools.r8.shaking.ProguardRuleParserException; 16 import com.android.tools.r8.utils.AndroidApp; 17 import com.android.tools.r8.utils.DexInspector; 18 import com.android.tools.r8.utils.DexInspector.AnnotationSubject; 19 import com.android.tools.r8.utils.DexInspector.ClassSubject; 20 import com.google.common.io.Closer; 21 import java.io.FileInputStream; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.nio.file.Files; 25 import java.nio.file.Path; 26 import java.nio.file.Paths; 27 import java.util.concurrent.ExecutionException; 28 import org.junit.Assert; 29 import org.junit.Rule; 30 import org.junit.Test; 31 import org.junit.rules.TemporaryFolder; 32 import org.objectweb.asm.ClassReader; 33 import org.objectweb.asm.ClassVisitor; 34 import org.objectweb.asm.Opcodes; 35 36 public class JSR45Tests { 37 38 private static final String DEFAULT_MAP_FILENAME = "proguard.map"; 39 private static final Path INPUT_PATH = 40 Paths.get("src/test/java/com/android/tools/r8/jsr45/HelloKt.class"); 41 private static final Path DONT_SHRINK_DONT_OBFUSCATE_CONFIG = 42 Paths.get("src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt"); 43 private static final Path DONT_SHRINK_CONFIG = 44 Paths.get("src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt"); 45 private static final Path SHRINK_KEEP_CONFIG = 46 Paths.get("src/test/java/com/android/tools/r8/jsr45/keep-rules-3.txt"); 47 private static final Path SHRINK_NO_KEEP_CONFIG = 48 Paths.get("src/test/java/com/android/tools/r8/jsr45/keep-rules-4.txt"); 49 50 @Rule 51 public TemporaryFolder tmpOutputDir = ToolHelper.getTemporaryFolderForTest(); 52 compileWithD8(Path intputPath, Path outputPath)53 void compileWithD8(Path intputPath, Path outputPath) throws IOException, CompilationException { 54 D8.run( 55 D8Command.builder() 56 .setMinApiLevel(Constants.ANDROID_O_API) 57 .addProgramFiles(intputPath) 58 .setOutputPath(outputPath) 59 .build()); 60 } 61 compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath)62 void compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath) 63 throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { 64 AndroidApp androidApp = 65 R8.run( 66 R8Command.builder() 67 .addProgramFiles(inputPath) 68 .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar())) 69 .setOutputPath(outputPath) 70 .addProguardConfigurationFiles(keepRulesPath) 71 .build()); 72 if (androidApp.hasProguardMap()) { 73 try (Closer closer = Closer.create()) { 74 androidApp.writeProguardMap(closer, new FileOutputStream( 75 Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME).toFile())); 76 } 77 } 78 } 79 80 static class ReadSourceDebugExtensionAttribute extends ClassVisitor { 81 ReadSourceDebugExtensionAttribute(int api, ClassVisitor cv)82 private ReadSourceDebugExtensionAttribute(int api, ClassVisitor cv) { 83 super(api, cv); 84 } 85 86 private String debugSourceExtension = null; 87 88 @Override visitSource(String source, String debug)89 public void visitSource(String source, String debug) { 90 debugSourceExtension = debug; 91 super.visitSource(source, debug); 92 } 93 } 94 95 @Test testSourceDebugExtensionWithD8()96 public void testSourceDebugExtensionWithD8() 97 throws IOException, CompilationException, ExecutionException { 98 Path outputPath = tmpOutputDir.newFolder().toPath(); 99 100 compileWithD8(INPUT_PATH, outputPath); 101 102 checkAnnotationContent(INPUT_PATH, outputPath); 103 } 104 105 /** 106 * Check that when dontshrink and dontobfuscate is used the annotation is transmitted. 107 */ 108 @Test testSourceDebugExtensionWithShriking1()109 public void testSourceDebugExtensionWithShriking1() 110 throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { 111 Path outputPath = tmpOutputDir.newFolder().toPath(); 112 compileWithR8(INPUT_PATH, outputPath, DONT_SHRINK_DONT_OBFUSCATE_CONFIG); 113 checkAnnotationContent(INPUT_PATH, outputPath); 114 } 115 116 /** 117 * Check that when dontshrink is used the annotation is not removed due to obfuscation. 118 */ 119 @Test testSourceDebugExtensionWithShrinking2()120 public void testSourceDebugExtensionWithShrinking2() 121 throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { 122 Path outputPath = tmpOutputDir.newFolder().toPath(); 123 compileWithR8(INPUT_PATH, outputPath, DONT_SHRINK_CONFIG); 124 checkAnnotationContent(INPUT_PATH, outputPath); 125 } 126 127 /** 128 * Check that the annotation is transmitted when shrinking is enabled with a keepattribute option. 129 */ 130 @Test testSourceDebugExtensionWithShrinking3()131 public void testSourceDebugExtensionWithShrinking3() 132 throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { 133 Path outputPath = tmpOutputDir.newFolder().toPath(); 134 135 compileWithR8(INPUT_PATH, outputPath, SHRINK_KEEP_CONFIG); 136 137 checkAnnotationContent(INPUT_PATH, outputPath); 138 } 139 140 /** 141 * Check that the annotation is removed when shriking is enabled and that there is not 142 * keepattributes option. 143 */ 144 @Test testSourceDebugExtensionWithShrinking4()145 public void testSourceDebugExtensionWithShrinking4() 146 throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { 147 Path outputPath = tmpOutputDir.newFolder().toPath(); 148 149 compileWithR8(INPUT_PATH, outputPath, SHRINK_NO_KEEP_CONFIG); 150 151 DexInspector dexInspector = 152 new DexInspector(outputPath.resolve("classes.dex"), getGeneratedProguardMap()); 153 ClassSubject classSubject = dexInspector.clazz("HelloKt"); 154 AnnotationSubject annotationSubject = 155 classSubject.annotation("dalvik.annotation.SourceDebugExtension"); 156 Assert.assertFalse(annotationSubject.isPresent()); 157 } 158 checkAnnotationContent(Path inputPath, Path outputPath)159 private void checkAnnotationContent(Path inputPath, Path outputPath) 160 throws IOException, ExecutionException { 161 ClassReader classReader = new ClassReader(new FileInputStream(inputPath.toFile())); 162 ReadSourceDebugExtensionAttribute sourceDebugExtensionReader = 163 new ReadSourceDebugExtensionAttribute(Opcodes.ASM5, null); 164 classReader.accept(sourceDebugExtensionReader, 0); 165 166 DexInspector dexInspector = 167 new DexInspector(outputPath.resolve("classes.dex"), getGeneratedProguardMap()); 168 ClassSubject classSubject = dexInspector.clazz("HelloKt"); 169 170 AnnotationSubject annotationSubject = 171 classSubject.annotation("dalvik.annotation.SourceDebugExtension"); 172 Assert.assertTrue(annotationSubject.isPresent()); 173 DexAnnotationElement[] annotationElement = annotationSubject.getAnnotation().elements; 174 Assert.assertNotNull(annotationElement); 175 Assert.assertTrue(annotationElement.length == 1); 176 Assert.assertEquals("value", annotationElement[0].name.toString()); 177 Assert.assertTrue(annotationElement[0].value instanceof DexValueString); 178 Assert.assertEquals( 179 sourceDebugExtensionReader.debugSourceExtension, 180 ((DexValueString) annotationElement[0].value).value.toSourceString()); 181 } 182 getGeneratedProguardMap()183 private String getGeneratedProguardMap() throws IOException { 184 Path mapFile = Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME); 185 if (Files.exists(mapFile)) { 186 return mapFile.toAbsolutePath().toString(); 187 } 188 return null; 189 } 190 } 191