Merge "Extend bootstrap benchmark with memory and size metrics."
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE index 12674e3..bc69c40 100644 --- a/LIBRARY-LICENSE +++ b/LIBRARY-LICENSE
@@ -48,3 +48,21 @@ license: ASM license licenseUrl: http://asm.ow2.org/license.html url: http://asm.ow2.org/index.html +- artifact: org.jetbrains.kotlin:kotlin-stdlib:+ + name: org.jetbrains.kotlin:kotlin-stdlib + copyrightHolder: JetBrains s.r.o. + license: The Apache License, Version 2.0 + licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt + url: https://kotlinlang.org/ +- artifact: org.jetbrains.kotlinx:kotlinx-metadata-jvm:+ + name: org.jetbrains.kotlinx:kotlinx-metadata-jvm + copyrightHolder: JetBrains s.r.o. + license: The Apache License, Version 2.0 + licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt + url: https://kotlinlang.org/ +- artifact: org.jetbrains:annotations:+ + name: IntelliJ IDEA Annotations + copyrightHolder: JetBrains s.r.o. + license: The Apache Software License, Version 2.0 + licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt + url: http://www.jetbrains.org
diff --git a/build.gradle b/build.gradle index 92d7471..7f41caa 100644 --- a/build.gradle +++ b/build.gradle
@@ -37,6 +37,7 @@ gsonVersion = '2.7' junitVersion = '4.12' kotlinVersion = '1.2.30' + kotlinExtMetadataJVMVersion = '0.0.2' protobufVersion = '3.0.0' smaliVersion = '2.2b4' } @@ -74,6 +75,7 @@ repositories { maven { url 'https://maven.google.com' } + maven { url 'https://kotlin.bintray.com/kotlinx' } mavenCentral() } @@ -223,6 +225,7 @@ exclude group: 'org.codehaus.mojo' }) compile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion + compile "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion" compile group: 'org.ow2.asm', name: 'asm', version: asmVersion compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java index af83158..a6389da 100644 --- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java +++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -12,7 +12,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.utils.DescriptorUtils; -import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.ZipUtils; import com.google.common.io.ByteStreams; import java.io.Closeable; import java.io.IOException; @@ -21,7 +21,6 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; @@ -73,7 +72,7 @@ while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); - if (FileUtils.isClassFile(Paths.get(name)) && include.test(name)) { + if (ZipUtils.isClassFile(name) && include.test(name)) { descriptors.add(DescriptorUtils.guessTypeDescriptor(name)); } }
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java index 425b3c3..1d6e678 100644 --- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java +++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -3,22 +3,18 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; -import static com.android.tools.r8.utils.FileUtils.isClassFile; -import static com.android.tools.r8.utils.FileUtils.isDexFile; - import com.android.tools.r8.ProgramResource.Kind; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.origin.ArchiveEntryOrigin; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.utils.DescriptorUtils; -import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.ZipUtils; import com.google.common.io.ByteStreams; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -39,16 +35,15 @@ } public static boolean includeClassFileEntries(String entry) { - return isClassFile(Paths.get(entry)); + return ZipUtils.isClassFile(entry); } public static boolean includeDexEntries(String entry) { - return isDexFile(Paths.get(entry)); + return ZipUtils.isDexFile(entry); } public static boolean includeClassFileOrDexEntries(String entry) { - Path path = Paths.get(entry); - return isClassFile(path) || isDexFile(path); + return ZipUtils.isClassFile(entry) || ZipUtils.isDexFile(entry); } private final Origin origin; @@ -97,14 +92,13 @@ ZipEntry entry = entries.nextElement(); try (InputStream stream = zipFile.getInputStream(entry)) { String name = entry.getName(); - Path path = Paths.get(name); - Origin entryOrigin = new ArchiveEntryOrigin(entry.getName(), origin); + Origin entryOrigin = new ArchiveEntryOrigin(name, origin); if (include.test(name)) { - if (FileUtils.isDexFile(path)) { + if (ZipUtils.isDexFile(name)) { dexResources.add( ProgramResource.fromBytes( entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null)); - } else if (isClassFile(path)) { + } else if (ZipUtils.isClassFile(name)) { String descriptor = DescriptorUtils.guessTypeDescriptor(name); classResources.add( ProgramResource.fromBytes(
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java index b775fd3..f896eee 100644 --- a/src/main/java/com/android/tools/r8/Version.java +++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@ // This field is accessed from release scripts using simple pattern matching. // Therefore, changing this field could break our release scripts. - public static final String LABEL = "1.3.0-dev"; + public static final String LABEL = "1.3.1-dev"; private Version() { }
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java index 1336f95..807509a 100644 --- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java +++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -22,7 +22,6 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.utils.DescriptorUtils; -import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableMap; @@ -61,7 +60,7 @@ archive.toString(), (entry, stream) -> { String name = entry.getName(); - if (FileUtils.isClassFile(Paths.get(name))) { + if (ZipUtils.isClassFile(name)) { String descriptor = DescriptorUtils.guessTypeDescriptor(name); builder.put(descriptor, ByteStreams.toByteArray(stream)); }
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java index 871f6d8..7d2907a 100644 --- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java +++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -3,13 +3,6 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.compatdx; -import static com.android.tools.r8.utils.FileUtils.isApkFile; -import static com.android.tools.r8.utils.FileUtils.isArchive; -import static com.android.tools.r8.utils.FileUtils.isClassFile; -import static com.android.tools.r8.utils.FileUtils.isDexFile; -import static com.android.tools.r8.utils.FileUtils.isJarFile; -import static com.android.tools.r8.utils.FileUtils.isZipFile; - import com.android.tools.r8.CompatDxHelper; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.CompilationMode; @@ -28,6 +21,7 @@ import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import java.io.File; @@ -475,7 +469,7 @@ } if (singleDexFile) { return new SingleDexFileConsumer( - isDexFile(output) + FileUtils.isDexFile(output) ? new NamedDexFileConsumer(output) : createDexConsumer(output, inputs, keepClasses)); } @@ -486,13 +480,13 @@ Path output, List<Path> inputs, boolean keepClasses) throws DxUsageMessage { if (keepClasses) { - if (!isArchive(output)) { + if (!FileUtils.isArchive(output)) { throw new DxCompatOptions.DxUsageMessage( "Output must be an archive when --keep-classes is set."); } return new DexKeepClassesConsumer(output, inputs); } - return isArchive(output) + return FileUtils.isArchive(output) ? new DexIndexedConsumer.ArchiveConsumer(output) : new DexIndexedConsumer.DirectoryConsumer(output); } @@ -572,12 +566,12 @@ private void writeZipWithClasses(DiagnosticsHandler handler) throws IOException { // For each input archive file, add all class files within. for (Path input : inputs) { - if (isArchive(input)) { + if (FileUtils.isArchive(input)) { try (ZipFile zipFile = new ZipFile(input.toFile(), StandardCharsets.UTF_8)) { final Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); - if (isClassFile(Paths.get(entry.getName()))) { + if (ZipUtils.isClassFile(entry.getName())) { try (InputStream entryStream = zipFile.getInputStream(entry)) { outputBuilder.addFile( entry.getName(), ByteStreams.toByteArray(entryStream), handler); @@ -604,11 +598,11 @@ return; } Path path = file.toPath(); - if (isZipFile(path) || isJarFile(path) || isClassFile(path)) { + if (FileUtils.isZipFile(path) || FileUtils.isJarFile(path) || FileUtils.isClassFile(path)) { files.add(path); return; } - if (isApkFile(path)) { + if (FileUtils.isApkFile(path)) { throw new Unimplemented("apk files not yet supported"); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java new file mode 100644 index 0000000..7f31098 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -0,0 +1,63 @@ +// Copyright (c) 2018, 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.graph; + +public class DefaultUseRegistry extends UseRegistry { + + @Override + public boolean registerInvokeVirtual(DexMethod method) { + return true; + } + + @Override + public boolean registerInvokeDirect(DexMethod method) { + return true; + } + + @Override + public boolean registerInvokeStatic(DexMethod method) { + return true; + } + + @Override + public boolean registerInvokeInterface(DexMethod method) { + return true; + } + + @Override + public boolean registerInvokeSuper(DexMethod method) { + return true; + } + + @Override + public boolean registerInstanceFieldWrite(DexField field) { + return true; + } + + @Override + public boolean registerInstanceFieldRead(DexField field) { + return true; + } + + @Override + public boolean registerNewInstance(DexType type) { + return true; + } + + @Override + public boolean registerStaticFieldRead(DexField field) { + return true; + } + + @Override + public boolean registerStaticFieldWrite(DexField field) { + return true; + } + + @Override + public boolean registerTypeReference(DexType type) { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 23139c5..2c188da 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.ThrowingConsumer; import com.google.common.base.MoreObjects; +import com.google.common.collect.Iterators; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; @@ -96,6 +97,16 @@ } } + public Iterable<DexEncodedField> fields() { + return () -> + Iterators.concat(Iterators.forArray(instanceFields), Iterators.forArray(staticFields)); + } + + public Iterable<DexEncodedMethod> methods() { + return () -> + Iterators.concat(Iterators.forArray(directMethods), Iterators.forArray(virtualMethods)); + } + @Override void collectMixedSectionItems(MixedSectionCollection mixedItems) { throw new Unreachable();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java index d2c7701..a9df6a3 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -195,7 +195,8 @@ transformBridgeMethod(); } computeNeedsRegister(); - insertArgumentMoves(); + constrainArgumentIntervals(); + insertRangeInvokeMoves(); ImmutableList<BasicBlock> blocks = computeLivenessInformation(); // First attempt to allocate register allowing argument reuse. This will fail if spilling // is required or if we end up using more than 16 registers. @@ -2598,11 +2599,14 @@ } } - private void insertArgumentMoves() { + private void constrainArgumentIntervals() { // Record the constraint that incoming arguments are in consecutive registers. List<Value> arguments = code.collectArguments(); createArgumentLiveIntervals(arguments); linkArgumentValuesAndIntervals(arguments); + } + + private void insertRangeInvokeMoves() { for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(); while (it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java index cf2b2bc..0af41b1 100644 --- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java +++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -5,19 +5,11 @@ package com.android.tools.r8.kotlin; import com.android.tools.r8.DiagnosticsHandler; -import com.android.tools.r8.graph.DexAnnotation; -import com.android.tools.r8.graph.DexAnnotationElement; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.DexValue; -import com.android.tools.r8.graph.DexValue.DexValueArray; -import com.android.tools.r8.graph.DexValue.DexValueInt; -import com.android.tools.r8.graph.DexValue.DexValueString; -import com.android.tools.r8.kotlin.KotlinSyntheticClass.Flavour; -import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.Sets; import java.util.Set; @@ -58,10 +50,13 @@ public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE"); + public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;"); public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;"); - public final DexMethod lambdaInitializerMethod = factory.createMethod(lambdaType, - factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName); + public final DexMethod lambdaInitializerMethod = factory.createMethod( + lambdaType, + factory.createProto(factory.voidType, factory.intType), + factory.constructorMethodName); public boolean isFunctionInterface(DexType type) { return functions.contains(type); @@ -70,9 +65,14 @@ public final class Metadata { public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;"); - public final DexString elementNameK = factory.createString("k"); - public final DexString elementNameD1 = factory.createString("d1"); - public final DexString elementNameD2 = factory.createString("d2"); + public final DexString kind = factory.createString("k"); + public final DexString metadataVersion = factory.createString("mv"); + public final DexString bytecodeVersion = factory.createString("bv"); + public final DexString data1 = factory.createString("d1"); + public final DexString data2 = factory.createString("d2"); + public final DexString extraString = factory.createString("xs"); + public final DexString packageName = factory.createString("pn"); + public final DexString extraInt = factory.createString("xi"); } // kotlin.jvm.internal.Intrinsics class @@ -86,98 +86,6 @@ // Calculates kotlin info for a class. public KotlinInfo getKotlinInfo(DexClass clazz, DiagnosticsHandler reporter) { - if (clazz.annotations.isEmpty()) { - return null; - } - DexAnnotation meta = clazz.annotations.getFirstMatching(metadata.kotlinMetadataType); - if (meta != null) { - try { - return createKotlinInfo(clazz, meta); - } catch (MetadataError e) { - reporter.warning( - new StringDiagnostic("Class " + clazz.type.toSourceString() + - " has malformed kotlin.Metadata: " + e.getMessage())); - } - } - return null; - } - - private KotlinInfo createKotlinInfo(DexClass clazz, DexAnnotation meta) { - DexAnnotationElement kindElement = getAnnotationElement(meta, metadata.elementNameK); - if (kindElement == null) { - throw new MetadataError("element 'k' is missing"); - } - - DexValue value = kindElement.value; - if (!(value instanceof DexValueInt)) { - throw new MetadataError("invalid 'k' value: " + value.toSourceString()); - } - - DexValueInt intValue = (DexValueInt) value; - switch (intValue.value) { - case 1: - return new KotlinClass(); - case 2: - return new KotlinFile(); - case 3: - return createSyntheticClass(clazz, meta); - case 4: - return new KotlinClassFacade(); - case 5: - return new KotlinClassPart(); - default: - throw new MetadataError("unsupported 'k' value: " + value.toSourceString()); - } - } - - private KotlinSyntheticClass createSyntheticClass(DexClass clazz, DexAnnotation meta) { - if (isKotlinStyleLambda(clazz)) { - return new KotlinSyntheticClass(Flavour.KotlinStyleLambda); - } - if (isJavaStyleLambda(clazz, meta)) { - return new KotlinSyntheticClass(Flavour.JavaStyleLambda); - } - return new KotlinSyntheticClass(Flavour.Unclassified); - } - - private boolean isKotlinStyleLambda(DexClass clazz) { - // TODO: replace with direct hints from kotlin metadata when available. - return clazz.superType == this.functional.lambdaType; - } - - private boolean isJavaStyleLambda(DexClass clazz, DexAnnotation meta) { - assert !isKotlinStyleLambda(clazz); - return clazz.superType == this.factory.objectType && - clazz.interfaces.size() == 1 && - isAnnotationElementNotEmpty(meta, metadata.elementNameD1); - } - - private DexAnnotationElement getAnnotationElement(DexAnnotation annotation, DexString name) { - for (DexAnnotationElement element : annotation.annotation.elements) { - if (element.name == name) { - return element; - } - } - return null; - } - - private boolean isAnnotationElementNotEmpty(DexAnnotation annotation, DexString name) { - for (DexAnnotationElement element : annotation.annotation.elements) { - if (element.name == name && element.value instanceof DexValueArray) { - DexValue[] values = ((DexValueArray) element.value).getValues(); - if (values.length == 1 && values[0] instanceof DexValueString) { - return true; - } - // Must be broken metadata. - assert false; - } - } - return false; - } - - private static class MetadataError extends RuntimeException { - MetadataError(String cause) { - super(cause); - } + return KotlinClassMetadataReader.getKotlinInfo(this, clazz, reporter); } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java index fc81994..74184a5 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,7 +4,31 @@ package com.android.tools.r8.kotlin; -public class KotlinClass extends KotlinInfo { +import kotlinx.metadata.KmClassVisitor; +import kotlinx.metadata.jvm.KotlinClassMetadata; + +public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> { + + static KotlinClass fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) { + assert kotlinClassMetadata instanceof KotlinClassMetadata.Class; + KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) kotlinClassMetadata; + return new KotlinClass(kClass); + } + + private KotlinClass(KotlinClassMetadata.Class metadata) { + super(metadata); + } + + @Override + void validateMetadata(KotlinClassMetadata.Class metadata) { + ClassMetadataVisitor visitor = new ClassMetadataVisitor(); + // To avoid lazy parsing/verifying metadata. + metadata.accept(visitor); + } + + private static class ClassMetadataVisitor extends KmClassVisitor { + } + @Override public Kind getKind() { return Kind.Class; @@ -20,6 +44,4 @@ return this; } - KotlinClass() { - } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java index e829a27..62db3d1 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,7 +4,26 @@ package com.android.tools.r8.kotlin; -public final class KotlinClassFacade extends KotlinInfo { +import kotlinx.metadata.jvm.KotlinClassMetadata; + +public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> { + + static KotlinClassFacade fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) { + assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade; + KotlinClassMetadata.MultiFileClassFacade multiFileClassFacade = + (KotlinClassMetadata.MultiFileClassFacade) kotlinClassMetadata; + return new KotlinClassFacade(multiFileClassFacade); + } + + private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata) { + super(metadata); + } + + @Override + void validateMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) { + // No worries about lazy parsing/verifying, since no API to explore metadata details. + } + @Override public Kind getKind() { return Kind.Facade; @@ -20,7 +39,4 @@ return this; } - KotlinClassFacade() { - super(); - } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java new file mode 100644 index 0000000..32aebff --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -0,0 +1,133 @@ +// Copyright (c) 2018, 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.kotlin; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.graph.DexAnnotation; +import com.android.tools.r8.graph.DexAnnotationElement; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexValue; +import com.android.tools.r8.graph.DexValue.DexValueArray; +import com.android.tools.r8.graph.DexValue.DexValueString; +import com.android.tools.r8.utils.StringDiagnostic; +import java.util.IdentityHashMap; +import java.util.Map; +import kotlinx.metadata.InconsistentKotlinMetadataException; +import kotlinx.metadata.jvm.KotlinClassHeader; +import kotlinx.metadata.jvm.KotlinClassMetadata; + +final class KotlinClassMetadataReader { + + static KotlinInfo getKotlinInfo( + Kotlin kotlin, + DexClass clazz, + DiagnosticsHandler reporter) { + if (clazz.annotations.isEmpty()) { + return null; + } + DexAnnotation meta = clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType); + if (meta != null) { + try { + return createKotlinInfo(kotlin, clazz, meta); + } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) { + reporter.warning( + new StringDiagnostic("Class " + clazz.type.toSourceString() + + " has malformed kotlin.Metadata: " + e.getMessage())); + } catch (Throwable e) { + reporter.warning( + new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString() + + "'s kotlin.Metadata: " + e.getMessage())); + } + } + return null; + } + + private static KotlinInfo createKotlinInfo( + Kotlin kotlin, + DexClass clazz, + DexAnnotation meta) { + Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>(); + for (DexAnnotationElement element : meta.annotation.elements) { + elementMap.put(element.name, element); + } + + DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind); + if (kind == null) { + throw new MetadataError("element 'k' is missing."); + } + Integer k = (Integer) kind.value.getBoxedValue(); + DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion); + int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv"); + DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion); + int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv"); + DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1); + String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1"); + DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2); + String[] d2 = data2 == null ? null : getUnboxedStringArray(data2.value, "d2"); + DexAnnotationElement extraString = elementMap.get(kotlin.metadata.extraString); + String xs = extraString == null ? null : getUnboxedString(extraString.value, "xs"); + DexAnnotationElement packageName = elementMap.get(kotlin.metadata.packageName); + String pn = packageName == null ? null : getUnboxedString(packageName.value, "pn"); + DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt); + Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue(); + + KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi); + KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header); + + if (kMetadata instanceof KotlinClassMetadata.Class) { + return KotlinClass.fromKotlinClassMetadata(kMetadata); + } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) { + return KotlinFile.fromKotlinClassMetadata(kMetadata); + } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) { + return KotlinClassFacade.fromKotlinClassMetadata(kMetadata); + } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) { + return KotlinClassPart.fromKotlinClassMetdata(kMetadata); + } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) { + return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz); + } else { + throw new MetadataError("unsupported 'k' value: " + k); + } + } + + private static int[] getUnboxedIntArray(DexValue v, String elementName) { + if (!(v instanceof DexValueArray)) { + throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString()); + } + DexValueArray intArrayValue = (DexValueArray) v; + DexValue[] values = intArrayValue.getValues(); + int[] result = new int [values.length]; + for (int i = 0; i < values.length; i++) { + result[i] = (Integer) values[i].getBoxedValue(); + } + return result; + } + + private static String[] getUnboxedStringArray(DexValue v, String elementName) { + if (!(v instanceof DexValueArray)) { + throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString()); + } + DexValueArray stringArrayValue = (DexValueArray) v; + DexValue[] values = stringArrayValue.getValues(); + String[] result = new String [values.length]; + for (int i = 0; i < values.length; i++) { + result[i] = getUnboxedString(values[i], elementName + "[" + i + "]"); + } + return result; + } + + private static String getUnboxedString(DexValue v, String elementName) { + if (!(v instanceof DexValueString)) { + throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString()); + } + return ((DexValueString) v).getValue().toString(); + } + + private static class MetadataError extends RuntimeException { + MetadataError(String cause) { + super(cause); + } + } + +}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java index d6da817..da66e6c 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,7 +4,31 @@ package com.android.tools.r8.kotlin; -public final class KotlinClassPart extends KotlinInfo { +import kotlinx.metadata.KmPackageVisitor; +import kotlinx.metadata.jvm.KotlinClassMetadata; + +public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> { + + static KotlinClassPart fromKotlinClassMetdata(KotlinClassMetadata kotlinClassMetadata) { + assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart; + KotlinClassMetadata.MultiFileClassPart multiFileClassPart = + (KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata; + return new KotlinClassPart(multiFileClassPart); + } + + private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata) { + super(metadata); + } + + @Override + void validateMetadata(KotlinClassMetadata.MultiFileClassPart metadata) { + // To avoid lazy parsing/verifying metadata. + metadata.accept(new MultiFileClassPartMetadataVisitor()); + } + + private static class MultiFileClassPartMetadataVisitor extends KmPackageVisitor { + } + @Override public Kind getKind() { return Kind.Part; @@ -20,6 +44,4 @@ return this; } - KotlinClassPart() { - } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java index 38a77ad..bcb70ed 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,7 +4,31 @@ package com.android.tools.r8.kotlin; -public final class KotlinFile extends KotlinInfo { +import kotlinx.metadata.KmPackageVisitor; +import kotlinx.metadata.jvm.KotlinClassMetadata; + +public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> { + + static KotlinFile fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) { + assert kotlinClassMetadata instanceof KotlinClassMetadata.FileFacade; + KotlinClassMetadata.FileFacade fileFacade = + (KotlinClassMetadata.FileFacade) kotlinClassMetadata; + return new KotlinFile(fileFacade); + } + + private KotlinFile(KotlinClassMetadata.FileFacade metadata) { + super(metadata); + } + + @Override + void validateMetadata(KotlinClassMetadata.FileFacade metadata) { + // To avoid lazy parsing/verifying metadata. + metadata.accept(new FileFacadeMetadataVisitor()); + } + + private static class FileFacadeMetadataVisitor extends KmPackageVisitor { + } + @Override public Kind getKind() { return Kind.File; @@ -20,6 +44,4 @@ return this; } - KotlinFile() { - } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java index 4043bc6..702e0eb 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,11 +4,23 @@ package com.android.tools.r8.kotlin; +import kotlinx.metadata.jvm.KotlinClassMetadata; + // Provides access to kotlin information. -public abstract class KotlinInfo { +public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> { + MetadataKind metadata; + KotlinInfo() { } + KotlinInfo(MetadataKind metadata) { + validateMetadata(metadata); + this.metadata = metadata; + } + + // Subtypes will define how to validate the given metadata. + abstract void validateMetadata(MetadataKind metadata); + public enum Kind { Class, File, Synthetic, Part, Facade }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java index 68721bb..4660800 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -4,7 +4,11 @@ package com.android.tools.r8.kotlin; -public final class KotlinSyntheticClass extends KotlinInfo { +import com.android.tools.r8.graph.DexClass; +import kotlinx.metadata.KmLambdaVisitor; +import kotlinx.metadata.jvm.KotlinClassMetadata; + +public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> { public enum Flavour { KotlinStyleLambda, JavaStyleLambda, @@ -13,12 +17,40 @@ private final Flavour flavour; - KotlinSyntheticClass(Flavour flavour) { + static KotlinSyntheticClass fromKotlinClassMetadata( + KotlinClassMetadata kotlinClassMetadata, Kotlin kotlin, DexClass clazz) { + assert kotlinClassMetadata instanceof KotlinClassMetadata.SyntheticClass; + KotlinClassMetadata.SyntheticClass syntheticClass = + (KotlinClassMetadata.SyntheticClass) kotlinClassMetadata; + if (isKotlinStyleLambda(syntheticClass, kotlin, clazz)) { + return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass); + } else if (isJavaStyleLambda(syntheticClass, kotlin, clazz)) { + return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass); + } else { + return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass); + } + } + + private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) { this.flavour = flavour; + validateMetadata(metadata); + this.metadata = metadata; + } + + @Override + void validateMetadata(KotlinClassMetadata.SyntheticClass metadata) { + if (metadata.isLambda()) { + SyntheticClassMetadataVisitor visitor = new SyntheticClassMetadataVisitor(); + // To avoid lazy parsing/verifying metadata. + metadata.accept(visitor); + } + } + + private static class SyntheticClassMetadataVisitor extends KmLambdaVisitor { } public boolean isLambda() { - return flavour == Flavour.KotlinStyleLambda || flavour == Flavour.JavaStyleLambda; + return isKotlinStyleLambda() || isJavaStyleLambda(); } public boolean isKotlinStyleLambda() { @@ -43,4 +75,31 @@ public KotlinSyntheticClass asSyntheticClass() { return this; } + + /** + * Returns {@code true} if the given {@link DexClass} is a Kotlin-style lambda: + * a class that + * 1) is recognized as lambda in its Kotlin metadata; + * 2) directly extends kotlin.jvm.internal.Lambda + */ + private static boolean isKotlinStyleLambda( + KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) { + return metadata.isLambda() + && clazz.superType == kotlin.functional.lambdaType; + } + + /** + * Returns {@code true} if the given {@link DexClass} is a Java-style lambda: + * a class that + * 1) is recognized as lambda in its Kotlin metadata; + * 2) doesn't extend any other class; + * 3) directly implements only one Java SAM. + */ + private static boolean isJavaStyleLambda( + KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) { + return metadata.isLambda() + && clazz.superType == kotlin.factory.objectType + && clazz.interfaces.size() == 1; + } + }
diff --git a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java index 1eb11e9..c67af00 100644 --- a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java +++ b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
@@ -46,14 +46,14 @@ return path; } - public boolean matchesFile(Path file) { + public boolean matchesFile(String name) { if (isUnfiltered()) { return true; } boolean isNegated = false; for (String pattern : pattern) { isNegated = pattern.charAt(0) == '!'; - boolean matches = matchAgainstFileName(file.toString(), 0, pattern, isNegated ? 1 : 0); + boolean matches = matchAgainstFileName(name, 0, pattern, isNegated ? 1 : 0); if (matches) { return !isNegated; } @@ -63,7 +63,7 @@ } private boolean containsFileSeparator(String string) { - return string.indexOf(File.separatorChar) != -1; + return string.indexOf('/') != -1; } private boolean matchAgainstFileName(String fileName, int namePos, String pattern, @@ -95,7 +95,7 @@ } } else { for (int i = namePos; i < fileName.length(); i++) { - if (!includeFileSeparators && fileName.charAt(i) == File.separatorChar) { + if (!includeFileSeparators && fileName.charAt(i) == '/') { return false; } if (matchAgainstFileName(fileName, i, pattern, patternPos + 1)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index d54c95c..4b57209 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.shaking; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.graph.DefaultUseRegistry; import com.android.tools.r8.graph.DexAnnotationSet; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; @@ -22,6 +23,7 @@ import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.PresortedComparable; +import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode; import com.android.tools.r8.ir.synthetic.SynthesizedCode; @@ -67,7 +69,7 @@ private final DexApplication application; private final AppInfoWithLiveness appInfo; private final GraphLense graphLense; - private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>(); + private final Map<DexType, DexType> mergedClasses = new HashMap<>(); private final Timing timing; private Collection<DexMethod> invokes; @@ -84,19 +86,109 @@ // Returns a set of types that must not be merged into other types. private Set<DexType> getPinnedTypes(Iterable<DexProgramClass> classes) { - // TODO(christofferqa): Compute types from [classes] that must be pinned in order for vertical - // class merging to work. - return new HashSet<>(); + Set<DexType> pinnedTypes = new HashSet<>(); + for (DexProgramClass clazz : classes) { + for (DexEncodedMethod method : clazz.methods()) { + // TODO(christofferqa): Remove the invariant that the graph lense should not modify any + // methods from the sets alwaysInline and noSideEffects (see use of assertNotModifiedBy- + // Lense). + if (appInfo.alwaysInline.contains(method) || appInfo.noSideEffects.containsKey(method)) { + DexClass other = appInfo.definitionFor(method.method.proto.returnType); + if (other != null && other.isProgramClass()) { + // If we were to merge [other] into its sub class, then we would implicitly change the + // signature of this method, and therefore break the invariant. + pinnedTypes.add(other.type); + } + for (DexType parameterType : method.method.proto.parameters.values) { + other = appInfo.definitionFor(parameterType); + if (other != null && other.isProgramClass()) { + // If we were to merge [other] into its sub class, then we would implicitly change the + // signature of this method, and therefore break the invariant. + pinnedTypes.add(other.type); + } + } + } + + // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the + // following example. (Alternatively, it would be possible to merge A and B and rewrite the + // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This + // situation should generally not occur in practice, though.) + // + // class A {} + // class B extends A { + // public void m() {} + // } + // class C extends A { + // public void m() { + // invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B + // } + // } + if (!method.isStaticMethod()) { + method.registerCodeReferences( + new DefaultUseRegistry() { + @Override + public boolean registerInvokeSuper(DexMethod target) { + DexClass targetClass = appInfo.definitionFor(target.getHolder()); + if (targetClass != null + && targetClass.isProgramClass() + && targetClass.lookupVirtualMethod(target) == null) { + pinnedTypes.add(target.getHolder()); + } + return true; + } + }); + } + } + } + return pinnedTypes; } private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) { - // We can merge program classes if they are not instantiated, have a single subtype - // and we do not have to keep them. - return !clazz.isLibraryClass() - && !appInfo.instantiatedTypes.contains(clazz.type) - && !appInfo.isPinned(clazz.type) - && !pinnedTypes.contains(clazz.type) - && clazz.type.getSingleSubtype() != null; + if (appInfo.instantiatedTypes.contains(clazz.type) + || appInfo.isPinned(clazz.type) + || pinnedTypes.contains(clazz.type)) { + return false; + } + DexType singleSubtype = clazz.type.getSingleSubtype(); + if (singleSubtype == null) { + // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into + // its subclass if [clazz] is not live. This should only be done, though, if it does not + // lead to members being duplicated. + return false; + } + DexClass targetClass = appInfo.definitionFor(singleSubtype); + if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) { + return false; + } + for (DexEncodedField field : clazz.fields()) { + if (appInfo.isPinned(field.field)) { + return false; + } + } + for (DexEncodedMethod method : clazz.methods()) { + if (appInfo.isPinned(method.method)) { + return false; + } + if (method.isInstanceInitializer() && disallowInlining(method)) { + // Cannot guarantee that markForceInline() will work. + return false; + } + } + return true; + } + + private boolean mergeMayLeadToIllegalAccesses(DexClass clazz, DexClass singleSubclass) { + if (clazz.type.isSamePackage(singleSubclass.type)) { + return false; + } + // TODO(christofferqa): To merge [clazz] into a class from another package we need to ensure: + // (A) All accesses to [clazz] and its members from inside the current package of [clazz] will + // continue to work. This is guaranteed if [clazz] is public and all members of [clazz] are + // either private or public. + // (B) All accesses from [clazz] to classes or members from the current package of [clazz] will + // continue to work. This is guaranteed if the methods of [clazz] do not access any private + // or protected classes or members from the current package of [clazz]. + return true; } private void addProgramMethods(Set<Wrapper<DexMethod>> set, DexMethod method, @@ -228,6 +320,11 @@ } continue; } + // Field resolution first considers the direct interfaces of [targetClass] before it proceeds + // to the super class. + if (fieldResolutionMayChange(clazz, targetClass)) { + continue; + } // Guard against the case where we have two methods that may get the same signature // if we replace types. This is rare, so we approximate and err on the safe side here. if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses) @@ -273,6 +370,35 @@ return renamedMembersLense.build(graphLense); } + private boolean fieldResolutionMayChange(DexClass source, DexClass target) { + if (source.type == target.superType) { + // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class + // Target implements an interface that declares a static final field f, this should yield an + // IncompatibleClassChangeError. + // TODO(christofferqa): In the following we only check if a static field from an interface + // shadows an instance field from [source]. We could actually check if there is an iget/iput + // instruction whose resolution would be affected by the merge. The situation where a static + // field shadows an instance field is probably not widespread in practice, though. + FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get(); + Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>(); + for (DexType interfaceType : target.interfaces.values) { + DexClass clazz = appInfo.definitionFor(interfaceType); + for (DexEncodedField staticField : clazz.staticFields()) { + staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.field)); + } + } + for (DexEncodedField instanceField : source.instanceFields()) { + if (staticFieldsInInterfacesOfTarget.contains(equivalence.wrap(instanceField.field))) { + // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an + // interface would now hit an instance field from [source], so that an IncompatibleClass- + // ChangeError would no longer be thrown. Abort merge. + return true; + } + } + } + return false; + } + private class ClassMerger { private static final String CONSTRUCTOR_NAME = "constructor"; @@ -298,8 +424,7 @@ // targetClass that are not currently contained. // Step 1: Merge methods Set<Wrapper<DexMethod>> existingMethods = new HashSet<>(); - addAll(existingMethods, target.directMethods(), MethodSignatureEquivalence.get()); - addAll(existingMethods, target.virtualMethods(), MethodSignatureEquivalence.get()); + addAll(existingMethods, target.methods(), MethodSignatureEquivalence.get()); List<DexEncodedMethod> directMethods = new ArrayList<>(); for (DexEncodedMethod directMethod : source.directMethods()) { @@ -341,8 +466,7 @@ // Record that invoke-super instructions in the target class should be redirected to the // newly created direct method. - deferredRenamings.mapVirtualMethodToDirectInType( - virtualMethod.method, resultingDirectMethod.method, target.type); + redirectSuperCallsInTarget(virtualMethod.method, resultingDirectMethod.method); if (shadowedBy == null) { // In addition to the newly added direct method, create a virtual method such that we do @@ -380,8 +504,7 @@ } // Step 2: Merge fields Set<Wrapper<DexField>> existingFields = new HashSet<>(); - addAll(existingFields, target.instanceFields(), FieldSignatureEquivalence.get()); - addAll(existingFields, target.staticFields(), FieldSignatureEquivalence.get()); + addAll(existingFields, target.fields(), FieldSignatureEquivalence.get()); Collection<DexEncodedField> mergedStaticFields = mergeItems( Iterators.forArray(source.staticFields()), target.staticFields(), @@ -432,6 +555,36 @@ return deferredRenamings; } + private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) { + // If we merge class B into class C, and class C contains an invocation super.m(), then it + // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method + // C.m$B denotes the direct method that has been created in C for B.m). In particular, there + // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is + // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()". + // + // We handle this by adding a mapping for [target] and all of its supertypes. + DexClass holder = target; + while (holder != null && holder.isProgramClass()) { + DexMethod signatureInHolder = + application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name); + // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError. + if (resolutionSucceeds(signatureInHolder)) { + deferredRenamings.mapVirtualMethodToDirectInType( + signatureInHolder, newTarget, target.type); + holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null; + } else { + break; + } + } + } + + private boolean resolutionSucceeds(DexMethod targetMethod) { + DexClass enclosingClass = appInfo.definitionFor(targetMethod.holder); + return enclosingClass != null + && (enclosingClass.lookupVirtualMethod(targetMethod) != null + || appInfo.lookupSuperTarget(targetMethod, enclosingClass.type) != null); + } + private DexEncodedMethod buildBridgeMethod( DexEncodedMethod signature, DexMethod invocationTarget) { DexType holder = target.type; @@ -468,7 +621,7 @@ } private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void addAll( - Collection<Wrapper<S>> collection, T[] items, Equivalence<S> equivalence) { + Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) { for (T item : items) { collection.add(equivalence.wrap(item.getKey())); } @@ -836,6 +989,86 @@ } } + private static boolean disallowInlining(DexEncodedMethod method) { + // TODO(christofferqa): Determine the situations where markForceInline() may fail, and ensure + // that we always return true here in these cases. + MethodInlineDecision registry = new MethodInlineDecision(); + method.getCode().registerCodeReferences(registry); + return registry.isInliningDisallowed(); + } + + private static class MethodInlineDecision extends UseRegistry { + private boolean disallowInlining = false; + + public boolean isInliningDisallowed() { + return disallowInlining; + } + + private boolean allowInlining() { + return true; + } + + private boolean disallowInlining() { + disallowInlining = true; + return true; + } + + @Override + public boolean registerInvokeInterface(DexMethod method) { + return disallowInlining(); + } + + @Override + public boolean registerInvokeVirtual(DexMethod method) { + return disallowInlining(); + } + + @Override + public boolean registerInvokeDirect(DexMethod method) { + return allowInlining(); + } + + @Override + public boolean registerInvokeStatic(DexMethod method) { + return allowInlining(); + } + + @Override + public boolean registerInvokeSuper(DexMethod method) { + return allowInlining(); + } + + @Override + public boolean registerInstanceFieldWrite(DexField field) { + return allowInlining(); + } + + @Override + public boolean registerInstanceFieldRead(DexField field) { + return allowInlining(); + } + + @Override + public boolean registerNewInstance(DexType type) { + return allowInlining(); + } + + @Override + public boolean registerStaticFieldRead(DexField field) { + return allowInlining(); + } + + @Override + public boolean registerStaticFieldWrite(DexField field) { + return allowInlining(); + } + + @Override + public boolean registerTypeReference(DexType type) { + return allowInlining(); + } + } + public Collection<DexType> getRemovedClasses() { return Collections.unmodifiableCollection(mergedClasses.keySet()); }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java index 0f06ca8..a611105 100644 --- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java +++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -4,8 +4,6 @@ package com.android.tools.r8.utils; import static com.android.tools.r8.utils.FileUtils.isArchive; -import static com.android.tools.r8.utils.FileUtils.isClassFile; -import static com.android.tools.r8.utils.FileUtils.isDexFile; import com.android.tools.r8.DataDirectoryResource; import com.android.tools.r8.DataEntryResource; @@ -23,8 +21,6 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -56,17 +52,16 @@ ZipEntry entry = entries.nextElement(); try (InputStream stream = zipFile.getInputStream(entry)) { String name = entry.getName(); - Path path = Paths.get(name); Origin entryOrigin = new ArchiveEntryOrigin(name, origin); - if (archive.matchesFile(path)) { - if (isDexFile(path)) { + if (archive.matchesFile(name)) { + if (ZipUtils.isDexFile(name)) { if (!ignoreDexInArchive) { ProgramResource resource = OneShotByteResource.create( Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null); dexResources.add(resource); } - } else if (isClassFile(path)) { + } else if (ZipUtils.isClassFile(name)) { String descriptor = DescriptorUtils.guessTypeDescriptor(name); ProgramResource resource = OneShotByteResource.create( @@ -111,7 +106,7 @@ final Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); - Path name = Paths.get(entry.getName()); + String name = entry.getName(); if (archive.matchesFile(name) && !isProgramResourceName(name)) { if (entry.isDirectory()) { resourceBrowser.visit(DataDirectoryResource.fromZip(zipFile, entry)); @@ -129,7 +124,7 @@ } } - private boolean isProgramResourceName(Path name) { - return isClassFile(name) || (isDexFile(name) && !ignoreDexInArchive); + private boolean isProgramResourceName(String name) { + return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive); } }
diff --git a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java index f933b42..0c6cc14 100644 --- a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java +++ b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
@@ -6,12 +6,11 @@ import com.android.tools.r8.ArchiveClassFileProvider; import com.android.tools.r8.shaking.FilteredClassPath; import java.io.IOException; -import java.nio.file.Paths; // Internal filtered class-file provider. class FilteredArchiveClassFileProvider extends ArchiveClassFileProvider { FilteredArchiveClassFileProvider(FilteredClassPath archive) throws IOException { - super(archive.getPath(), entry -> archive.matchesFile(Paths.get(entry))); + super(archive.getPath(), entry -> archive.matchesFile(entry)); } }
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java index 849377b..fd50583 100644 --- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -3,6 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.utils; +import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; +import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION; +import static com.android.tools.r8.utils.FileUtils.MODULE_INFO_CLASS; + import com.android.tools.r8.errors.CompilationError; import com.google.common.io.ByteStreams; import java.io.File; @@ -92,4 +96,17 @@ stream.write(content); stream.closeEntry(); } + + public static boolean isDexFile(String entry) { + String name = entry.toLowerCase(); + return name.endsWith(DEX_EXTENSION); + } + + public static boolean isClassFile(String entry) { + String name = entry.toLowerCase(); + if (name.endsWith(MODULE_INFO_CLASS)) { + return false; + } + return name.endsWith(CLASS_EXTENSION); + } }
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java index 2ef0123..23dc7e0 100644 --- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java +++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -391,8 +391,9 @@ ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8); ZipEntry entry; while (null != (entry = zip.getNextEntry())) { - if (isClassFile(Paths.get(entry.getName()))) { - Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin); + String name = entry.getName(); + if (isClassFile(name)) { + Origin origin = new ArchiveEntryOrigin(name, zipOrigin); classfiles.add(new ClassFileContent(origin, readBytes(zip))); } }
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java index 2c2e8d7..7bce10a 100644 --- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java +++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -382,8 +382,9 @@ ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8); ZipEntry entry; while (null != (entry = zip.getNextEntry())) { - if (isClassFile(Paths.get(entry.getName()))) { - Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin); + String name = entry.getName(); + if (isClassFile(name)) { + Origin origin = new ArchiveEntryOrigin(name, zipOrigin); classfiles.add(new ClassFileContent(origin, readBytes(zip))); } }
diff --git a/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java new file mode 100644 index 0000000..6ceb0a2 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
@@ -0,0 +1,43 @@ +// Copyright (c) 2018 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 java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ArchiveClassFileProviderTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + public Path createZip() throws IOException { + Path tempRoot = temporaryFolder.getRoot().toPath(); + Path zipFile = tempRoot.resolve("zipfile.zip"); + ZipOutputStream zipStream = + new ZipOutputStream(new FileOutputStream(zipFile.toFile()), StandardCharsets.UTF_8); + ZipEntry entry = new ZipEntry("non-ascii:$\u02CF"); + zipStream.putNextEntry(entry); + zipStream.write(10); + zipStream.close(); + return zipFile; + } + + @Test + public void testSystemLocale() throws IOException, ResourceException { + // Set the locale used for Paths to ASCII which will make Path creation fail + // for non-ascii names. + System.setProperty("sun.jnu.encoding", "ASCII"); + Path zipFile = createZip(); + new ArchiveClassFileProvider(zipFile); + ArchiveProgramResourceProvider.fromArchive(zipFile).getProgramResources(); + } +}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java index 258aa43..ee82fc4 100644 --- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java +++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1920,11 +1920,6 @@ // 1) t03 // java.lang.AssertionError: expected null, but was:<[I@1b32f32> - .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", - match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1))) - // 1) t03 - // java.lang.AssertionError: reference is not enqueued after 2 sec - .put("lang.StackTraceElement.toString.StackTraceElement_toString_A01", match(runtimes(Version.DEFAULT))) // 1) t03 @@ -4959,8 +4954,9 @@ .put("lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01", match(runtimesUpTo(Version.V4_4_4))) - .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", - match(runtimesUpTo(Version.V4_4_4))) + .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", any()) + // 1) t03 + // java.lang.AssertionError: reference is not enqueued after 2 sec .put("lang.ref.WeakReference.enqueue.WeakReference_enqueue_A01", match(runtimesUpTo(Version.V4_4_4)))
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java index 6069b4c..da32cce 100644 --- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java +++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -31,7 +31,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; - @RunWith(Parameterized.class) public class MethodHandleTestRunner extends TestBase { static final Class<?> CLASS = MethodHandleTest.class; @@ -169,7 +168,9 @@ Arrays.asList( "-keep public class com.android.tools.r8.cf.MethodHandleTest {", " public static void main(...);", - "}"), + "}", + // Disallow merging MethodHandleTest$I into MethodHandleTest$Impl + "-keep public interface com.android.tools.r8.cf.MethodHandleTest$I"), Origin.unknown()); } try {
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java index 52e2313..7016163 100644 --- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -238,7 +238,6 @@ assertEquals(2, numberOfMoveExceptionInstructions); } - @Ignore("b/73958515") @Test public void testNoIllegalClassAccess() throws Exception { String main = "classmerging.SimpleInterfaceAccessTest"; @@ -258,18 +257,36 @@ "classmerging.pkg.SimpleInterfaceImplRetriever", "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl"); runTest(main, programFiles, preservedClassNames); + } + + @Ignore("b/73958515") + @Test + public void testNoIllegalClassAccessWithAccessModifications() throws Exception { // If access modifications are allowed then SimpleInterface should be merged into // SimpleInterfaceImpl. - preservedClassNames = + String main = "classmerging.SimpleInterfaceAccessTest"; + Path[] programFiles = + new Path[] { + CF_DIR.resolve("SimpleInterface.class"), + CF_DIR.resolve("SimpleInterfaceAccessTest.class"), + CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"), + CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class") + }; + ImmutableSet<String> preservedClassNames = ImmutableSet.of( "classmerging.SimpleInterfaceAccessTest", "classmerging.pkg.SimpleInterfaceImplRetriever", "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl"); + // Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as + // a result of inlining). runTest( main, programFiles, preservedClassNames, - getProguardConfig(EXAMPLE_KEEP, "-allowaccessmodification")); + getProguardConfig( + EXAMPLE_KEEP, + "-allowaccessmodification", + "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever")); } @Test @@ -328,6 +345,7 @@ } for (String rule : additionalRules) { builder.append(rule); + builder.append(System.lineSeparator()); } return builder.toString(); }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java new file mode 100644 index 0000000..2c1479e --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -0,0 +1,24 @@ +// Copyright (c) 2018, 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.graph; + +import com.android.tools.r8.AsmTestBase; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.invokespecial.Main; +import com.android.tools.r8.graph.invokespecial.TestClassDump; +import org.junit.Ignore; +import org.junit.Test; + +public class InvokeSpecialTest extends AsmTestBase { + + @Ignore("b/110175213") + @Test + public void testInvokeSpecial() throws Exception { + ensureSameOutput( + Main.class.getCanonicalName(), + ToolHelper.getClassAsBytes(Main.class), + TestClassDump.dump()); + } +}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java new file mode 100644 index 0000000..deb316d --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
@@ -0,0 +1,13 @@ +// Copyright (c) 2018, 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.graph.invokespecial; + +public class Main { + + public static void main(String[] args) { + TestClass x = new TestClass(); + x.m(true); + } +}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java new file mode 100644 index 0000000..c08091e --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
@@ -0,0 +1,15 @@ +// Copyright (c) 2018, 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.graph.invokespecial; + +public class TestClass { + + public void m(boolean recurse) { + System.out.println(recurse); + if (recurse) { + m(false); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java new file mode 100644 index 0000000..ae336834 --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
@@ -0,0 +1,67 @@ +// Copyright (c) 2018, 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.graph.invokespecial; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/- +// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to +// an invoke-special instruction. +public class TestClassDump implements Opcodes { + + public static byte[] dump() throws Exception { + + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + + cw.visit( + V1_8, + ACC_PUBLIC + ACC_SUPER, + "com/android/tools/r8/graph/invokespecial/TestClass", + null, + "java/lang/Object", + null); + + { + mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null); + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitVarInsn(ILOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false); + mv.visitVarInsn(ILOAD, 1); + Label l0 = new Label(); + mv.visitJumpInsn(IFEQ, l0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(ICONST_0); + // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL. + mv.visitMethodInsn( + INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false); + mv.visitLabel(l0); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + cw.visitEnd(); + + return cw.toByteArray(); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java index 74e3d5e..6ebb08b 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -83,13 +83,16 @@ .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL) .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC); - private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false; + private Consumer<InternalOptions> disableClassInliningAndMerging = o -> { + o.enableClassInlining = false; + o.enableClassMerging = false; + }; @Test public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception { String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_noUseOfProperties"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, MUTABLE_PROPERTY_CLASS.getClassName()); @@ -112,7 +115,7 @@ public void testMutableProperty_privateIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrivateProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, MUTABLE_PROPERTY_CLASS.getClassName()); @@ -134,7 +137,7 @@ public void testMutableProperty_protectedIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useProtectedProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, MUTABLE_PROPERTY_CLASS.getClassName()); @@ -157,7 +160,7 @@ public void testMutableProperty_internalIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useInternalProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, MUTABLE_PROPERTY_CLASS.getClassName()); @@ -180,7 +183,7 @@ public void testMutableProperty_publicIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePublicProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, MUTABLE_PROPERTY_CLASS.getClassName()); @@ -203,7 +206,7 @@ public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrimitiveProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, MUTABLE_PROPERTY_CLASS.getClassName()); @@ -228,7 +231,7 @@ public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception { String mainClass = addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_noUseOfProperties"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); @@ -252,7 +255,7 @@ public void testLateInitProperty_privateIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath( "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); @@ -275,7 +278,7 @@ public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_useProtectedLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); @@ -296,7 +299,7 @@ public void testLateInitProperty_internalIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath( "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); @@ -315,7 +318,7 @@ public void testLateInitProperty_publicIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath( "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); @@ -334,7 +337,7 @@ public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception { String mainClass = addMainToClasspath( "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, USER_DEFINED_PROPERTY_CLASS.getClassName()); @@ -351,7 +354,7 @@ public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath( "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject classSubject = checkClassIsKept(dexInspector, USER_DEFINED_PROPERTY_CLASS.getClassName()); @@ -377,7 +380,7 @@ public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception { String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, "properties.CompanionProperties"); @@ -408,7 +411,7 @@ public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception { String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, "properties.CompanionProperties"); @@ -442,7 +445,7 @@ public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception { String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_useInternalProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, "properties.CompanionProperties"); @@ -473,7 +476,7 @@ public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception { String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_usePublicProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, "properties.CompanionProperties"); @@ -505,7 +508,7 @@ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_usePrivateLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName()); ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName()); @@ -536,7 +539,7 @@ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_useInternalLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName()); ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName()); @@ -560,7 +563,7 @@ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_usePublicLateInitProp"); - runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> { + runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> { DexInspector dexInspector = new DexInspector(app); ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName()); ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
diff --git a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java index f27813e..7c243ae 100644 --- a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java +++ b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
@@ -34,16 +34,8 @@ private void testPath(List<String> filters, List<String> positives, List<String> negatives) { FilteredClassPath path = makeFilteredClassPath(filters); - Assert.assertTrue( - positives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get) - .allMatch(path::matchesFile)); - Assert.assertFalse( - negatives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get) - .allMatch(path::matchesFile)); - } - - private static String adaptFileSeparator(String s) { - return s.replace('/', File.separatorChar); + Assert.assertTrue(positives.stream().allMatch(path::matchesFile)); + Assert.assertFalse(negatives.stream().allMatch(path::matchesFile)); } private static FilteredClassPath makeFilteredClassPath(List<String> filters) { @@ -51,9 +43,7 @@ } private static FilteredClassPath makeFilteredClassPath(Path path, List<String> filters) { - return new FilteredClassPath(path, - filters.stream().map(FilteredClassPathTest::adaptFileSeparator) - .collect(ImmutableList.toImmutableList())); + return new FilteredClassPath(path, ImmutableList.copyOf(filters)); } @Test