Merge commit '3cd0e1c825fad39d4cc17189182ae0f46ad43621' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index e7f27d5..35d22bb 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -702,7 +702,9 @@
.getArtProfileOptions()
.setArtProfilesForRewriting(getArtProfilesForRewriting())
.setPassthrough(true);
- internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+ if (!getStartupProfileProviders().isEmpty()) {
+ internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+ }
internal.programClassConflictResolver =
ProgramClassCollection.wrappedConflictResolver(
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d4e7f3a..d57d450 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -68,6 +68,7 @@
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.optimize.AccessModifier;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
import com.android.tools.r8.optimize.RedundantBridgeRemover;
import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
@@ -460,7 +461,7 @@
// We can now remove redundant bridges. Note that we do not need to update the
// invoke-targets here, as the existing invokes will simply dispatch to the now
// visible super-method. MemberRebinding, if run, will then dispatch it correctly.
- new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
+ new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService);
}
}
@@ -688,10 +689,17 @@
}
}
+ // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
+ // the application has an applied oracle for looking up non-rebound references.
+ MemberRebindingIdentityLens memberRebindingIdentityLens =
+ MemberRebindingIdentityLensFactory.create(appView, executorService);
+ appView.setGraphLens(memberRebindingIdentityLens);
+
// Remove redundant bridges that have been inserted for member rebinding.
// This can only be done if we have AppInfoWithLiveness.
if (appView.appInfo().hasLiveness()) {
- new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
+ new RedundantBridgeRemover(appView.withLiveness())
+ .run(memberRebindingIdentityLens, executorService);
} else {
// If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
// we are not shrinking, we can't move visibility bridges. In principle, though, it would be
@@ -700,10 +708,6 @@
assert !options.isShrinking();
}
- // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
- // the application has an applied oracle for looking up non-rebound references.
- appView.setGraphLens(MemberRebindingIdentityLensFactory.create(appView, executorService));
-
if (appView.appInfo().hasLiveness()) {
SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing);
} else {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 81efb90..e579889 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1077,7 +1077,9 @@
internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
internal.getArtProfileOptions().setArtProfilesForRewriting(getArtProfilesForRewriting());
- internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+ if (!getStartupProfileProviders().isEmpty()) {
+ internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+ }
internal.programClassConflictResolver =
ProgramClassCollection.wrappedConflictResolver(
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
index 3b5b940..0eef997 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -9,10 +9,14 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
+import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileRulePredicate;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.startup.StartupProfileBuilder;
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
-import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.UTF8TextInputStream;
import java.io.IOException;
@@ -28,7 +32,23 @@
public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
try {
startupProfileBuilder.addHumanReadableArtProfile(
- new UTF8TextInputStream(path), ConsumerUtils.emptyConsumer());
+ new UTF8TextInputStream(path),
+ profileParserBuilder ->
+ profileParserBuilder.setRulePredicate(
+ new ArtProfileRulePredicate() {
+ @Override
+ public boolean testClassRule(
+ ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+ return false;
+ }
+
+ @Override
+ public boolean testMethodRule(
+ MethodReference methodReference,
+ ArtProfileMethodRuleInfo methodRuleInfo) {
+ return methodRuleInfo.isStartup();
+ }
+ }));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index b891c89..42f5d96 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -688,6 +688,13 @@
return resolveMethodOnLegacy(invokedMethod.getHolderType(), invokedMethod, isInterface);
}
+ public MethodResolutionResult resolveMethodOn(DexClass clazz, DexMethod method) {
+ assert checkIfObsolete();
+ return clazz.isInterface()
+ ? resolveMethodOnInterface(clazz, method)
+ : resolveMethodOnClass(clazz, method);
+ }
+
public MethodResolutionResult resolveMethodOnLegacy(DexClass clazz, DexMethod method) {
assert checkIfObsolete();
return clazz.isInterface()
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 5569e34..425c5d5 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.graph.DexCode.Try;
import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
@@ -178,8 +179,10 @@
IndexedItemCollection indexedItems,
ProgramMethod context,
LensCodeRewriterUtils rewriter) {
- getParentConstructor(context, rewriter.dexItemFactory())
- .collectIndexedItems(appView, indexedItems);
+ DexMethod parentConstructor = getParentConstructor(context, rewriter.dexItemFactory());
+ MethodLookupResult lookupResult =
+ appView.graphLens().lookupInvokeDirect(parentConstructor, context);
+ lookupResult.getReference().collectIndexedItems(appView, indexedItems);
}
@Override
@@ -370,7 +373,9 @@
GraphLens graphLens,
LensCodeRewriterUtils lensCodeRewriter,
ObjectToOffsetMapping mapping) {
- new DexInvokeDirect(1, getParentConstructor(context, mapping.dexItemFactory()), 0, 0, 0, 0, 0)
+ DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory());
+ MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context);
+ new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0)
.write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
new DexReturnVoid().write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
}
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 3069a60..0564b9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -893,7 +893,7 @@
}
public final boolean classInitializationMayHaveSideEffectsInContext(
- AppView<?> appView, ProgramDefinition context) {
+ AppView<?> appView, Definition context) {
// Types that are a super type of the current context are guaranteed to be initialized already.
return classInitializationMayHaveSideEffects(
appView, type -> appView.isSubtype(context.getContextType(), type).isTrue());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 93011d5..47fabfa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
+import com.android.tools.r8.kotlin.KotlinMetadataUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.structural.StructuralItem;
@@ -397,6 +398,7 @@
private DexAnnotationSet annotations = DexAnnotationSet.empty();
private FieldAccessFlags accessFlags;
private FieldTypeSignature genericSignature = FieldTypeSignature.noSignature();
+ private KotlinFieldLevelInfo kotlinInfo = KotlinMetadataUtils.getNoKotlinInfo();
private DexValue staticValue = null;
private ComputedApiLevel apiLevel = ComputedApiLevel.notSet();
private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
@@ -416,8 +418,8 @@
// Copy all the mutable state of a DexEncodedField here.
field = from.getReference();
accessFlags = from.accessFlags.copy();
- // TODO(b/169923358): Consider removing the fieldSignature here.
genericSignature = from.getGenericSignature();
+ kotlinInfo = from.getKotlinInfo();
annotations = from.annotations();
staticValue = from.staticValue;
apiLevel = from.getApiLevel();
@@ -518,6 +520,7 @@
apiLevel,
deprecated,
d8R8Synthesized);
+ dexEncodedField.setKotlinMemberInfo(kotlinInfo);
dexEncodedField.optimizationInfo = optimizationInfo;
buildConsumer.accept(dexEncodedField);
return dexEncodedField;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 9620ee4..3ee7c50 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -291,6 +291,7 @@
public final DexString optionalLongDescriptor = createString("Ljava/util/OptionalLong;");
public final DexString streamDescriptor = createString("Ljava/util/stream/Stream;");
public final DexString arraysDescriptor = createString("Ljava/util/Arrays;");
+ public final DexString threadLocalDescriptor = createString("Ljava/lang/ThreadLocal;");
public final DexString throwableDescriptor = createString(throwableDescriptorString);
public final DexString illegalAccessErrorDescriptor =
@@ -446,6 +447,7 @@
public final DexType optionalIntType = createStaticallyKnownType(optionalIntDescriptor);
public final DexType optionalLongType = createStaticallyKnownType(optionalLongDescriptor);
public final DexType streamType = createStaticallyKnownType(streamDescriptor);
+ public final DexType threadLocalType = createStaticallyKnownType(threadLocalDescriptor);
public final DexType bufferType = createStaticallyKnownType(bufferDescriptor);
public final DexType byteBufferType = createStaticallyKnownType(byteBufferDescriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
index f303854..e239b5e 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -3,15 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+
import com.android.tools.r8.utils.structural.StructuralItem;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -32,15 +36,17 @@
private final Map<DexType, TypeInfo> typeInfo;
+ private final DexDefinitionSupplier definitionSupplier;
private final DexItemFactory factory;
private SubtypingInfo(
Map<DexType, TypeInfo> typeInfo,
Map<DexType, Set<DexType>> subtypeMap,
- DexItemFactory factory) {
+ DexDefinitionSupplier definitionSupplier) {
this.typeInfo = typeInfo;
this.subtypeMap = subtypeMap;
- this.factory = factory;
+ this.definitionSupplier = definitionSupplier;
+ factory = definitionSupplier.dexItemFactory();
}
public static SubtypingInfo create(AppView<? extends AppInfoWithClassHierarchy> appView) {
@@ -60,7 +66,7 @@
Map<DexType, TypeInfo> typeInfo = new ConcurrentHashMap<>();
Map<DexType, Set<DexType>> subtypeMap = new IdentityHashMap<>();
populateSubtypeMap(classes, subtypeMap, typeInfo, definitions);
- return new SubtypingInfo(typeInfo, subtypeMap, definitions.dexItemFactory());
+ return new SubtypingInfo(typeInfo, subtypeMap, definitions);
}
private static void populateSuperType(
@@ -245,6 +251,16 @@
.forEach(fn);
}
+ public List<DexClass> computeReachableInterfacesWithDeterministicOrder() {
+ List<DexClass> interfaces = new ArrayList<>();
+ forAllInterfaceRoots(
+ type ->
+ definitionSupplier
+ .contextIndependentDefinitionForWithResolutionResult(type)
+ .forEachClassResolutionResult(interfaces::add));
+ return classesWithDeterministicOrder(interfaces);
+ }
+
private static class TypeInfo {
private final DexType type;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index e6faadc..a93e87f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -5,10 +5,19 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
@@ -17,13 +26,19 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites;
@@ -39,6 +54,7 @@
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -186,6 +202,9 @@
}
if (options.getMinApiLevel().isLessThan(AndroidApiLevel.O)) {
initializeAndroidOMethodProviders(factory);
+ if (typeIsPresent(factory.supplierType)) {
+ initializeAndroidOThreadLocalMethodProviderWithSupplier(factory);
+ }
}
if (options.getMinApiLevel().isLessThan(AndroidApiLevel.R)) {
if (typeIsPresentWithoutBackportsFrom(factory.setType, AndroidApiLevel.R)) {
@@ -1617,6 +1636,17 @@
new MethodGenerator(method, BackportedMethods::ObjectsMethods_requireNonNullSupplier));
}
+ private void initializeAndroidOThreadLocalMethodProviderWithSupplier(DexItemFactory factory) {
+ // ThreadLocal
+ DexType type = factory.threadLocalType;
+
+ // ThreadLocal.withInitial(Supplier)
+ DexString name = factory.createString("withInitial");
+ DexProto proto = factory.createProto(type, factory.supplierType);
+ DexMethod method = factory.createMethod(type, proto, name);
+ addProvider(new ThreadLocalWithInitialWithSupplierGenerator(method));
+ }
+
private void addProvider(MethodProvider generator) {
MethodProvider replaced = rewritable.put(generator.method, generator);
assert replaced == null;
@@ -1754,6 +1784,174 @@
}
}
+ /*
+ Generate code for the SynthesizedThreadLocalSubClass class below, and rewrite
+
+ ThreadLocal.withInitial(someSupplier);
+
+ to
+
+ new SynthesizedThreadLocalSubClass(someSupplier);
+
+ class SynthesizedThreadLocalSubClass extends ThreadLocal<Object> {
+ private Supplier<Object> supplier;
+
+ SynthesizedThreadLocalSubClass(Supplier<Object> supplier) {
+ this.supplier = supplier;
+ }
+
+ protected Object initialValue() {
+ return supplier.get();
+ }
+ }
+ */
+ private static class ThreadLocalWithInitialWithSupplierGenerator extends MethodGenerator {
+
+ ThreadLocalWithInitialWithSupplierGenerator(DexMethod method) {
+ super(method, null, method.name.toString());
+ }
+
+ @Override
+ protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+ return naming.THREAD_LOCAL;
+ }
+
+ @Override
+ public Collection<CfInstruction> rewriteInvoke(
+ CfInvoke invoke,
+ AppView<?> appView,
+ BackportedMethodDesugaringEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext,
+ LocalStackAllocator localStackAllocator) {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexProgramClass threadLocalSubclass =
+ appView
+ .getSyntheticItems()
+ .createClass(
+ kinds -> kinds.THREAD_LOCAL,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder -> new ThreadLocalSubclassGenerator(builder, appView));
+ eventConsumer.acceptBackportedClass(
+ threadLocalSubclass, methodProcessingContext.getMethodContext());
+ return ImmutableList.of(
+ new CfNew(threadLocalSubclass.type),
+ // Massage the stack with dup_x1 and swap:
+ // ..., SomeSupplier, ThreadLocalSubClass ->
+ // ..., ThreadLocalSubClass, ThreadLocalSubClass, SomeSupplier
+ new CfStackInstruction(Opcode.DupX1),
+ new CfStackInstruction(Opcode.Swap),
+ new CfInvoke(
+ Opcodes.INVOKESPECIAL,
+ factory.createMethod(
+ threadLocalSubclass.type,
+ factory.createProto(factory.voidType, factory.supplierType),
+ factory.constructorMethodName),
+ false));
+ }
+ }
+
+ private static class ThreadLocalSubclassGenerator {
+ private final DexItemFactory factory;
+ private final DexType type;
+ private final DexField supplierField;
+ private final DexMethod constructor;
+ private final DexMethod initialValueMethod;
+
+ ThreadLocalSubclassGenerator(SyntheticProgramClassBuilder builder, AppView<?> appView) {
+ this.factory = appView.dexItemFactory();
+ this.type = builder.getType();
+ this.supplierField =
+ factory.createField(
+ builder.getType(),
+ factory.supplierType,
+ factory.createString("initialValueSupplier"));
+ this.constructor =
+ factory.createMethod(
+ type,
+ factory.createProto(factory.voidType, factory.supplierType),
+ factory.constructorMethodName);
+ this.initialValueMethod =
+ factory.createMethod(
+ type, factory.createProto(factory.objectType), factory.createString("initialValue"));
+
+ builder.setSuperType(factory.threadLocalType);
+ synthesizeInstanceFields(builder);
+ synthesizeDirectMethods(builder);
+ synthesizeVirtualMethods(builder);
+ }
+
+ private void synthesizeInstanceFields(SyntheticProgramClassBuilder builder) {
+ // Instance field:
+ //
+ // private Supplier<Object> supplier
+ builder.setInstanceFields(
+ ImmutableList.of(
+ DexEncodedField.syntheticBuilder()
+ .setField(supplierField)
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build()));
+ }
+
+ private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
+ // Code for:
+ //
+ // SynthesizedThreadLocalSubClass(Supplier<Object> supplier) {
+ // this.supplier = supplier;
+ // }
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfStackInstruction(Opcode.Dup));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKESPECIAL,
+ factory.createMethod(
+ factory.threadLocalType,
+ factory.createProto(factory.voidType),
+ factory.constructorMethodName),
+ false));
+ instructions.add(new CfLoad(ValueType.fromDexType(factory.supplierType), 1));
+ instructions.add(new CfInstanceFieldWrite(supplierField));
+ instructions.add(new CfReturnVoid());
+
+ builder.setDirectMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(constructor)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+ .setCode(new CfCode(type, 2, 2, instructions))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ }
+
+ private void synthesizeVirtualMethods(SyntheticProgramClassBuilder builder) {
+ // Code for:
+ //
+ // protected Object initialValue() {
+ // return supplier.get();
+ // }
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfInstanceFieldRead(supplierField));
+ instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, factory.supplierMembers.get, true));
+ instructions.add(new CfReturn(ValueType.OBJECT));
+
+ builder.setVirtualMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(initialValueMethod)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PROTECTED | Constants.ACC_SYNTHETIC, false))
+ .setCode(new CfCode(type, 1, 1, instructions))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ }
+ }
+
private interface TemplateMethodFactory {
CfCode create(DexItemFactory factory, DexMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 6a9fb76..e44467e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -127,6 +127,13 @@
}
@Override
+ public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
+ backportedClass
+ .programMethods()
+ .forEach(method -> methodProcessor.scheduleMethodForProcessing(method, this));
+ }
+
+ @Override
public void acceptRecordMethod(ProgramMethod method) {
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
@@ -398,6 +405,11 @@
}
@Override
+ public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
+ // Intentionally empty. The method will be hit by tracing if required.
+ }
+
+ @Override
public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
synchronized (pendingInvokeSpecialBridges) {
pendingInvokeSpecialBridges.add(info);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java
index c62501f..11f9d56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java
@@ -4,9 +4,12 @@
package com.android.tools.r8.ir.desugar.backports;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
public interface BackportedMethodDesugaringEventConsumer {
void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context);
+
+ void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context);
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
index 44876ed..4f3a62e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -75,7 +75,7 @@
// Mapping from SSA value definition to the local name index in the constant pool.
private final Int2ReferenceMap<DebugLocalInfo> debugLocals = new Int2ReferenceOpenHashMap<>();
// Mapping from instruction to the end usage of SSA values with debug local info.
- private final Int2ReferenceMap<int[]> debugLocalEnds = new Int2ReferenceOpenHashMap();
+ private final Int2ReferenceMap<int[]> debugLocalEnds = new Int2ReferenceOpenHashMap<>();
// TODO(b/225838009): Reconsider this fixed space as the operand count for phis is much larger.
// Pre-allocated space for caching value indexes when writing instructions.
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index ef6142f..00c0d9f 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
-import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
import static com.android.tools.r8.utils.SymbolGenerationUtils.RESERVED_NAMES;
@@ -26,7 +25,6 @@
import com.android.tools.r8.utils.SymbolGenerationUtils;
import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
import com.android.tools.r8.utils.Timing;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -45,9 +43,9 @@
public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
assert appView.options().isMinifying();
- SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
+ SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
timing.begin("ComputeInterfaces");
- List<DexClass> interfaces = computeReachableInterfacesWithDeterministicOrder(subtypingInfo);
+ List<DexClass> interfaces = subtypingInfo.computeReachableInterfacesWithDeterministicOrder();
timing.end();
timing.begin("MinifyClasses");
ClassNameMinifier classNameMinifier =
@@ -99,19 +97,6 @@
timing.end();
}
- private List<DexClass> computeReachableInterfacesWithDeterministicOrder(
- SubtypingInfo subtypingInfo) {
- List<DexClass> interfaces = new ArrayList<>();
- subtypingInfo.forAllInterfaceRoots(
- type -> {
- DexClass clazz = appView.contextIndependentDefinitionFor(type);
- if (clazz != null) {
- interfaces.add(clazz);
- }
- });
- return classesWithDeterministicOrder(interfaces);
- }
-
abstract static class BaseMinificationNamingStrategy {
// We have to ensure that the names proposed by the minifier is not used in the obfuscation
diff --git a/src/main/java/com/android/tools/r8/naming/MinifierUtils.java b/src/main/java/com/android/tools/r8/naming/MinifierUtils.java
new file mode 100644
index 0000000..e259a67
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/MinifierUtils.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, 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.naming;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Set;
+
+public class MinifierUtils {
+
+ public static SubtypingInfo createSubtypingInfo(AppView<AppInfoWithLiveness> appView) {
+ Set<DexClass> classesToBuildSubtypeInformationFor =
+ SetUtils.newIdentityHashSet(appView.app().classes());
+ appView
+ .appInfo()
+ .getObjectAllocationInfoCollection()
+ .forEachInstantiatedLambdaInterfaces(
+ type -> {
+ DexClass lambdaInterface = appView.contextIndependentDefinitionFor(type);
+ if (lambdaInterface != null) {
+ classesToBuildSubtypeInformationFor.add(lambdaInterface);
+ }
+ });
+ appView.appInfo().forEachReferencedClasspathClass(classesToBuildSubtypeInformationFor::add);
+ return SubtypingInfo.create(classesToBuildSubtypeInformationFor, appView);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 174614b..d297031 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -35,14 +35,12 @@
import com.android.tools.r8.position.Position;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
-import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -50,7 +48,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiPredicate;
@@ -96,37 +93,15 @@
public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
ArrayDeque<Map<DexReference, MemberNaming>> nonPrivateMembers = new ArrayDeque<>();
Set<DexReference> notMappedReferences = new HashSet<>();
+ SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
timing.begin("MappingInterfaces");
- Set<DexClass> classesToBuildSubtypeInformationFor =
- SetUtils.newIdentityHashSet(appView.app().classes());
- appView
- .appInfo()
- .getObjectAllocationInfoCollection()
- .forEachInstantiatedLambdaInterfaces(
- type -> {
- DexClass lambdaInterface = appView.contextIndependentDefinitionFor(type);
- if (lambdaInterface != null) {
- classesToBuildSubtypeInformationFor.add(lambdaInterface);
- }
- });
- appView.appInfo().forEachReferencedClasspathClass(classesToBuildSubtypeInformationFor::add);
- SubtypingInfo subtypingInfo =
- SubtypingInfo.create(classesToBuildSubtypeInformationFor, appView);
- timing.begin("MappingInterfaces");
- Set<DexClass> interfaces = new TreeSet<>(Comparator.comparing(DexClass::getType));
- subtypingInfo.forAllInterfaceRoots(
- iFace -> {
- DexClass iFaceDefinition = appView.definitionFor(iFace);
- if (iFaceDefinition != null) {
- interfaces.add(iFaceDefinition);
- computeMapping(iFace, nonPrivateMembers, notMappedReferences, subtypingInfo);
- }
- });
-
- assert nonPrivateMembers.isEmpty();
+ List<DexClass> interfaces = subtypingInfo.computeReachableInterfacesWithDeterministicOrder();
+ interfaces.forEach(
+ iface ->
+ computeMapping(iface.getType(), nonPrivateMembers, notMappedReferences, subtypingInfo));
timing.end();
-
timing.begin("MappingClasses");
+ mappedClasses.addAll(appView.appInfo().classes());
subtypingInfo.forAllImmediateExtendsSubtypes(
factory.objectType,
subType -> {
@@ -193,13 +168,9 @@
ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
DexClass clazz = appView.definitionFor(type);
- // Keep track of classes that needs to get renamed.
- if (clazz != null) {
- if (clazz.isClasspathClass() && classNaming != null) {
- mappedClasses.add(clazz.asClasspathClass());
- } else if (clazz.isProgramClass()) {
- mappedClasses.add(clazz.asProgramClass());
- }
+ // Keep track of classpath classes that needs to get renamed.
+ if (clazz != null && clazz.isClasspathClass() && classNaming != null) {
+ mappedClasses.add(clazz.asClasspathClass());
}
Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index c7d53f9..ea14e07 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -46,6 +46,10 @@
return false;
}
+ public boolean isResidualSignatureMappingInformation() {
+ return false;
+ }
+
public MapVersionMappingInformation asMapVersionMappingInformation() {
return null;
}
@@ -74,6 +78,10 @@
return null;
}
+ public ResidualSignatureMappingInformation asResidualSignatureMappingInformation() {
+ return null;
+ }
+
public boolean isGlobalMappingInformation() {
return false;
}
@@ -138,6 +146,9 @@
case OutlineCallsiteMappingInformation.ID:
OutlineCallsiteMappingInformation.deserialize(version, object, onMappingInfo);
return;
+ case ResidualSignatureMappingInformation.ID:
+ ResidualSignatureMappingInformation.deserialize(version, object, onMappingInfo);
+ return;
default:
diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, id));
UnknownJsonMappingInformation.deserialize(id, object, onMappingInfo);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
index efdb788..4bcfe95 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
@@ -88,12 +88,12 @@
public static void deserialize(
MapVersion version, JsonObject object, Consumer<MappingInformation> onMappingInfo) {
if (isSupported(version)) {
- JsonObject postionsMapObject = object.getAsJsonObject(POSITIONS_KEY);
- if (postionsMapObject == null) {
+ JsonObject positionsMapObject = object.getAsJsonObject(POSITIONS_KEY);
+ if (positionsMapObject == null) {
throw new CompilationError("Expected '" + POSITIONS_KEY + "' to be present: " + object);
}
Int2IntSortedMap positionsMap = new Int2IntLinkedOpenHashMap();
- postionsMapObject
+ positionsMapObject
.entrySet()
.forEach(
entry -> {
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/ResidualSignatureMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/ResidualSignatureMappingInformation.java
new file mode 100644
index 0000000..5808b25
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/ResidualSignatureMappingInformation.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2022, 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.naming.mappinginformation;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public abstract class ResidualSignatureMappingInformation extends MappingInformation {
+
+ public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_EXPERIMENTAL;
+ public static final String ID = "com.android.tools.r8.residualsignature";
+ public static final String SIGNATURE_KEY = "signature";
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public String serialize() {
+ JsonObject object = new JsonObject();
+ object.add(SIGNATURE_KEY, new JsonPrimitive(serializeInternal()));
+ return object.toString();
+ }
+
+ protected abstract String serializeInternal();
+
+ @Override
+ public boolean allowOther(MappingInformation information) {
+ return !information.isResidualSignatureMappingInformation();
+ }
+
+ public static boolean isSupported(MapVersion version) {
+ return version.isGreaterThanOrEqualTo(SUPPORTED_VERSION);
+ }
+
+ public static void deserialize(
+ MapVersion version, JsonObject object, Consumer<MappingInformation> onMappingInfo) {
+ if (isSupported(version)) {
+ JsonObject signatureObject = object.getAsJsonObject(SIGNATURE_KEY);
+ if (signatureObject == null) {
+ throw new CompilationError("Expected '" + SIGNATURE_KEY + "' to be present: " + object);
+ }
+ String signatureString = signatureObject.getAsString();
+ if (signatureString.contains("(")) {
+ onMappingInfo.accept(
+ ResidualMethodSignatureMappingInformation.deserialize(signatureString));
+ } else {
+ onMappingInfo.accept(ResidualFieldSignatureMappingInformation.deserialize(signatureString));
+ }
+ }
+ }
+
+ public static class ResidualMethodSignatureMappingInformation
+ extends ResidualSignatureMappingInformation {
+
+ private static final String[] EMPTY_ARRAY = new String[0];
+
+ private final String returnType;
+ private final String[] parameters;
+
+ private ResidualMethodSignatureMappingInformation(String[] parameters, String returnType) {
+ assert DescriptorUtils.isDescriptor(returnType)
+ || DescriptorUtils.isVoidDescriptor(returnType);
+ this.returnType = returnType;
+ this.parameters = parameters;
+ }
+
+ public static ResidualMethodSignatureMappingInformation fromDexMethod(DexMethod method) {
+ String[] parameters =
+ ArrayUtils.mapAll(
+ method.getParameters().values, DexType::toDescriptorString, EMPTY_ARRAY);
+ return new ResidualMethodSignatureMappingInformation(
+ parameters, method.getReturnType().toDescriptorString());
+ }
+
+ @Override
+ protected String serializeInternal() {
+ return StringUtils.join("", Arrays.asList(parameters), BraceType.PARENS) + returnType;
+ }
+
+ public static ResidualMethodSignatureMappingInformation deserialize(String signature) {
+ return new ResidualMethodSignatureMappingInformation(
+ DescriptorUtils.getArgumentTypeDescriptors(signature),
+ DescriptorUtils.getReturnTypeDescriptor(signature));
+ }
+ }
+
+ public static class ResidualFieldSignatureMappingInformation
+ extends ResidualSignatureMappingInformation {
+
+ private final String type;
+
+ private ResidualFieldSignatureMappingInformation(String type) {
+ assert DescriptorUtils.isDescriptor(type);
+ this.type = type;
+ }
+
+ public static ResidualFieldSignatureMappingInformation fromDexField(DexField field) {
+ return new ResidualFieldSignatureMappingInformation(field.getType().toDescriptorString());
+ }
+
+ @Override
+ protected String serializeInternal() {
+ return type;
+ }
+
+ public static ResidualFieldSignatureMappingInformation deserialize(String signature) {
+ return new ResidualFieldSignatureMappingInformation(signature);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index e5b23f5..0cc861f 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -49,6 +49,11 @@
return new Builder(appView, previousLens);
}
+ public void addNonReboundMethodReference(
+ DexMethod nonReboundMethodReference, DexMethod reboundMethodReference) {
+ nonReboundMethodReferenceToDefinitionMap.put(nonReboundMethodReference, reboundMethodReference);
+ }
+
@Override
public boolean hasCodeRewritings() {
return false;
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
index 6c2af55..d9ab527 100644
--- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.ThreadUtils.processItems;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -13,6 +14,7 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
+import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Map;
@@ -28,17 +30,17 @@
this.appView = appView;
}
- private boolean isRedundantBridge(ProgramMethod method) {
+ private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) {
// Clean-up the predicate check.
if (appView.appInfo().isPinned(method.getReference())) {
- return false;
+ return null;
}
DexEncodedMethod definition = method.getDefinition();
// TODO(b/197490164): Remove if method is abstract.
BridgeInfo bridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
boolean isBridge = definition.isBridge() || bridgeInfo != null;
if (!isBridge || definition.isAbstract()) {
- return false;
+ return null;
}
InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor(appView, method);
method.registerCodeReferences(targetExtractor);
@@ -46,31 +48,28 @@
// javac-generated visibility forward bridge method has same descriptor (name, signature and
// return type).
if (target == null || !target.match(method.getReference())) {
- return false;
+ return null;
}
if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
- return false;
+ return null;
}
// This is a visibility forward, so check for the direct target.
- ProgramMethod targetMethod =
- appView
- .appInfo()
- .unsafeResolveMethodDueToDexFormatLegacy(target)
- .getResolvedProgramMethod();
+ DexClassAndMethod targetMethod =
+ appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(target).getResolutionPair();
if (targetMethod == null) {
- return false;
+ return null;
}
if (method.getAccessFlags().isPublic()) {
if (!targetMethod.getAccessFlags().isPublic()) {
- return false;
+ return null;
}
} else {
if (targetMethod.getAccessFlags().isProtected()
&& !targetMethod.getHolderType().isSamePackage(method.getHolderType())) {
- return false;
+ return null;
}
if (targetMethod.getAccessFlags().isPrivate()) {
- return false;
+ return null;
}
}
if (definition.isStatic()
@@ -78,9 +77,9 @@
&& method
.getHolder()
.classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
- return false;
+ return null;
}
- return true;
+ return targetMethod;
}
private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
@@ -90,7 +89,6 @@
if (kind == InvokeKind.DIRECT) {
return method.getDefinition().isInstanceInitializer()
&& appView.options().canHaveNonReboundConstructorInvoke()
- && appView.testing().enableRedundantConstructorBridgeRemoval
&& appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.getHolderType());
}
assert !method.getAccessFlags().isPrivate();
@@ -105,15 +103,44 @@
return false;
}
- public void run(ExecutorService executorService) throws ExecutionException {
+ public void run(
+ MemberRebindingIdentityLens memberRebindingIdentityLens, ExecutorService executorService)
+ throws ExecutionException {
+ assert memberRebindingIdentityLens == null
+ || memberRebindingIdentityLens == appView.graphLens();
+
// Collect all redundant bridges to remove.
+ RedundantBridgeRemovalLens.Builder lensBuilder = new RedundantBridgeRemovalLens.Builder();
Map<DexProgramClass, ProgramMethodSet> bridgesToRemove =
- computeBridgesToRemove(executorService);
+ computeBridgesToRemove(lensBuilder, executorService);
+ if (bridgesToRemove.isEmpty()) {
+ return;
+ }
+
pruneApp(bridgesToRemove, executorService);
+
+ if (!lensBuilder.isEmpty()) {
+ appView.setGraphLens(lensBuilder.build(appView));
+ }
+
+ if (memberRebindingIdentityLens != null) {
+ for (ProgramMethodSet bridgesToRemoveFromClass : bridgesToRemove.values()) {
+ for (ProgramMethod bridgeToRemove : bridgesToRemoveFromClass) {
+ DexClassAndMethod resolvedMethod =
+ appView
+ .appInfo()
+ .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
+ .getResolutionPair();
+ memberRebindingIdentityLens.addNonReboundMethodReference(
+ bridgeToRemove.getReference(), resolvedMethod.getReference());
+ }
+ }
+ }
}
private Map<DexProgramClass, ProgramMethodSet> computeBridgesToRemove(
- ExecutorService executorService) throws ExecutionException {
+ RedundantBridgeRemovalLens.Builder lensBuilder, ExecutorService executorService)
+ throws ExecutionException {
Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>();
processItems(
appView.appInfo().classes(),
@@ -121,8 +148,20 @@
ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
clazz.forEachProgramMethod(
method -> {
- if (isRedundantBridge(method)) {
+ DexClassAndMethod target = getTargetForRedundantBridge(method);
+ if (target != null) {
+ // Record that the redundant bridge should be removed.
bridgesToRemoveForClass.add(method);
+
+ // Rewrite invokes to the bridge to the target if it is accessible.
+ // TODO(b/173751869): Consider enabling this for constructors as well.
+ // TODO(b/245882297): Refine these visibility checks so that we also rewrite when
+ // the target is not public, but still accessible to call sites.
+ if (!method.getDefinition().isInstanceInitializer()
+ && target.getAccessFlags().isPublic()
+ && target.getHolder().isPublic()) {
+ lensBuilder.map(method, target);
+ }
}
});
if (!bridgesToRemoveForClass.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
index 1ce0703..6f7f61f 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
@@ -105,7 +105,8 @@
if (methodSignature == newMethodSignature) {
return previous;
}
- assert !previous.hasReboundReference();
+ assert !previous.hasReboundReference()
+ || previous.getReference() == previous.getReboundReference();
return MethodLookupResult.builder(this)
.setPrototypeChanges(
previous
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
new file mode 100644
index 0000000..a680b36
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
@@ -0,0 +1,150 @@
+// Copyright (c) 2022, 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.optimize.redundantbridgeremoval;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class RedundantBridgeRemovalLens extends NonIdentityGraphLens {
+
+ private final Set<DexType> interfaces;
+ private final Map<DexMethod, DexMethod> methodMap;
+
+ public RedundantBridgeRemovalLens(
+ AppView<?> appView, Set<DexType> interfaces, Map<DexMethod, DexMethod> methodMap) {
+ super(appView);
+ this.interfaces = interfaces;
+ this.methodMap = methodMap;
+ }
+
+ // Fields.
+
+ @Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return getPrevious().getOriginalFieldSignature(field);
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+ if (this == codeLens) {
+ return originalField;
+ }
+ return getPrevious().getRenamedFieldSignature(originalField, codeLens);
+ }
+
+ @Override
+ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+ return previous;
+ }
+
+ // Methods.
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+ if (this == applied) {
+ return originalMethod;
+ }
+ DexMethod previousMethodSignature =
+ getPrevious().getRenamedMethodSignature(originalMethod, applied);
+ return methodMap.getOrDefault(previousMethodSignature, previousMethodSignature);
+ }
+
+ @Override
+ public DexMethod getNextMethodSignature(DexMethod method) {
+ return method;
+ }
+
+ @Override
+ public DexMethod getPreviousMethodSignature(DexMethod method) {
+ return method;
+ }
+
+ @Override
+ protected MethodLookupResult internalDescribeLookupMethod(
+ MethodLookupResult previous, DexMethod context) {
+ if (methodMap.containsKey(previous.getReference())) {
+ DexMethod newReference = previous.getReference();
+ do {
+ newReference = methodMap.get(newReference);
+ } while (methodMap.containsKey(newReference));
+ if (previous.getType().isSuper() && interfaces.contains(newReference.getHolderType())) {
+ return previous;
+ }
+ return MethodLookupResult.builder(this)
+ .setReference(newReference)
+ .setReboundReference(newReference)
+ .setPrototypeChanges(previous.getPrototypeChanges())
+ .setType(previous.getType())
+ .build();
+ }
+ return previous;
+ }
+
+ @Override
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method, GraphLens codeLens) {
+ if (this == codeLens) {
+ return RewrittenPrototypeDescription.none();
+ }
+ return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+ }
+
+ // Types.
+
+ @Override
+ public DexType getOriginalType(DexType type) {
+ return getPrevious().getOriginalType(type);
+ }
+
+ @Override
+ public Iterable<DexType> getOriginalTypes(DexType type) {
+ return getPrevious().getOriginalTypes(type);
+ }
+
+ @Override
+ protected DexType internalDescribeLookupClassType(DexType previous) {
+ return previous;
+ }
+
+ // Misc.
+
+ @Override
+ public boolean isContextFreeForMethods() {
+ return getPrevious().isContextFreeForMethods();
+ }
+
+ public static class Builder {
+
+ private final Set<DexType> interfaces = Sets.newIdentityHashSet();
+ private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+
+ public synchronized Builder map(ProgramMethod from, DexClassAndMethod to) {
+ methodMap.put(from.getReference(), to.getReference());
+ if (to.getHolder().isInterface()) {
+ interfaces.add(to.getHolderType());
+ }
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return methodMap.isEmpty();
+ }
+
+ public RedundantBridgeRemovalLens build(AppView<?> appView) {
+ return new RedundantBridgeRemovalLens(appView, interfaces, methodMap);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
index 3abbbca..cc39eb9 100644
--- a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
+++ b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
@@ -9,8 +9,9 @@
import com.android.tools.r8.profile.art.diagnostic.HumanReadableArtProfileParserErrorDiagnostic;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.ClassReferenceUtils;
import com.android.tools.r8.utils.MethodReferenceUtils;
import com.android.tools.r8.utils.Reporter;
import java.io.BufferedReader;
@@ -65,12 +66,16 @@
}
public boolean parseRule(String rule) {
- ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
- ArtProfileMethodRuleInfoImpl.builder();
- rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setIsHot);
- rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setIsStartup);
- rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setIsPostStartup);
- return parseClassOrMethodDescriptor(rule, methodRuleInfoBuilder.build());
+ try {
+ ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
+ ArtProfileMethodRuleInfoImpl.builder();
+ rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setIsHot);
+ rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setIsStartup);
+ rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setIsPostStartup);
+ return parseClassOrMethodRule(rule, methodRuleInfoBuilder.build());
+ } catch (Throwable t) {
+ return false;
+ }
}
private static String parseFlag(String rule, char c, Action action) {
@@ -81,23 +86,30 @@
return rule;
}
- private boolean parseClassOrMethodDescriptor(
- String descriptor, ArtProfileMethodRuleInfoImpl methodRuleInfo) {
- int arrowStartIndex = descriptor.indexOf("->");
+ private boolean parseClassOrMethodRule(String rule, ArtProfileMethodRuleInfoImpl methodRuleInfo) {
+ int arrowStartIndex = rule.indexOf("->");
if (arrowStartIndex >= 0) {
- return parseMethodRule(descriptor, methodRuleInfo, arrowStartIndex);
+ return parseMethodRule(rule, methodRuleInfo, arrowStartIndex);
} else if (methodRuleInfo.isEmpty()) {
- return parseClassRule(descriptor);
+ return parseClassRule(rule);
} else {
return false;
}
}
private boolean parseClassRule(String descriptor) {
- ClassReference classReference = ClassReferenceUtils.parseClassDescriptor(descriptor);
- if (classReference == null) {
+ TypeReference typeReference = Reference.typeFromDescriptor(descriptor);
+ if (typeReference == null) {
return false;
}
+ if (typeReference.isArray()) {
+ typeReference = typeReference.asArray().getBaseType();
+ }
+ if (typeReference.isPrimitive()) {
+ return false;
+ }
+ assert typeReference.isClass();
+ ClassReference classReference = typeReference.asClass();
if (rulePredicate.testClassRule(classReference, ArtProfileClassRuleInfoImpl.empty())) {
profileBuilder.addClassRule(
classRuleBuilder -> classRuleBuilder.setClassReference(classReference));
@@ -106,7 +118,9 @@
}
private boolean parseMethodRule(
- String descriptor, ArtProfileMethodRuleInfoImpl methodRuleInfo, int arrowStartIndex) {
+ String rule, ArtProfileMethodRuleInfoImpl methodRuleInfo, int arrowStartIndex) {
+ int inlineCacheStartIndex = rule.indexOf('+', arrowStartIndex + 2);
+ String descriptor = inlineCacheStartIndex > 0 ? rule.substring(0, inlineCacheStartIndex) : rule;
MethodReference methodReference =
MethodReferenceUtils.parseSmaliString(descriptor, arrowStartIndex);
if (methodReference == null) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index e1ac8ba..59c8d20 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -57,6 +57,7 @@
// Locally generated synthetic classes.
public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda");
+ public final SyntheticKind THREAD_LOCAL = generator.forInstanceClass("ThreadLocal");
// TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT =
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 13f630e..216bd47 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -98,6 +98,15 @@
return results != null ? results.toArray(emptyArray) : original;
}
+ /** Rewrites the input array to the output array unconditionally. */
+ public static <T, S> S[] mapAll(T[] original, Function<T, S> mapper, S[] emptyArray) {
+ ArrayList<S> results = new ArrayList<>(original.length);
+ for (T t : original) {
+ results.add(mapper.apply(t));
+ }
+ return results.toArray(emptyArray);
+ }
+
public static <T> T[] filter(T[] original, Predicate<T> predicate, T[] emptyArray) {
return map(original, e -> predicate.test(e) ? e : null, emptyArray);
}
diff --git a/src/main/java/com/android/tools/r8/utils/BoxBase.java b/src/main/java/com/android/tools/r8/utils/BoxBase.java
index d38d92b..93f70da 100644
--- a/src/main/java/com/android/tools/r8/utils/BoxBase.java
+++ b/src/main/java/com/android/tools/r8/utils/BoxBase.java
@@ -6,6 +6,7 @@
import java.util.Comparator;
import java.util.Objects;
+import java.util.function.Function;
import java.util.function.Supplier;
public abstract class BoxBase<T> {
@@ -39,6 +40,12 @@
return oldValue;
}
+ public T getAndCompute(Function<T, T> newValue) {
+ T t = get();
+ set(newValue.apply(t));
+ return t;
+ }
+
void set(T value) {
this.value = value;
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 82d7e77..33a2afb 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -124,7 +124,8 @@
// Set to true to run compilation in a single thread and without randomly shuffling the input.
// This makes life easier when running R8 in a debugger.
- public static final boolean DETERMINISTIC_DEBUGGING = false;
+ public static final boolean DETERMINISTIC_DEBUGGING =
+ System.getProperty("com.android.tools.r8.deterministicdebugging") != null;
// Use a MethodCollection where most interleavings between reading and mutating is caught.
public static final boolean USE_METHOD_COLLECTION_CONCURRENCY_CHECKED = false;
@@ -1972,7 +1973,6 @@
public boolean enableDeadSwitchCaseElimination = true;
public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
public boolean enableMultiANewArrayDesugaringForClassFiles = false;
- public boolean enableRedundantConstructorBridgeRemoval = false;
public boolean enableSwitchToIfRewriting = true;
public boolean enableEnumUnboxingDebugLogs =
System.getProperty("com.android.tools.r8.enableEnumUnboxingDebugLogs") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 90d26b4..d71f6b5 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -54,6 +54,7 @@
import com.android.tools.r8.naming.ClassNaming;
import com.android.tools.r8.naming.ClassNaming.Builder;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.MapVersion;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -69,6 +70,7 @@
import com.android.tools.r8.naming.mappinginformation.MappingInformation;
import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation.ResidualMethodSignatureMappingInformation;
import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
@@ -640,16 +642,26 @@
methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
}
- // Don't emit pure identity mappings.
- if (mappedPositions.isEmpty()
- && methodMappingInfo.isEmpty()
- && obfuscatedNameDexString == originalMethod.name
- && originalMethod.holder == originalType) {
+ MapVersion mapFileVersion = appView.options().getMapFileVersion();
+ if (isIdentityMapping(
+ mapFileVersion,
+ mappedPositions,
+ methodMappingInfo,
+ method,
+ obfuscatedNameDexString,
+ originalMethod,
+ originalType)) {
assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
|| hasAtMostOnePosition(definition, appView.options())
|| appView.isCfByteCodePassThrough(definition);
continue;
}
+ // TODO(b/169953605): Ensure we emit the residual signature information.
+ if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)
+ && originalMethod != method.getReference()) {
+ methodMappingInfo.add(
+ ResidualMethodSignatureMappingInformation.fromDexMethod(method.getReference()));
+ }
MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
onDemandClassNamingBuilder.computeIfAbsent().addMemberEntry(memberNaming);
@@ -689,26 +701,16 @@
MappedPosition firstPosition = mappedPositions.get(i);
int j = i + 1;
MappedPosition lastPosition = firstPosition;
+ MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE;
for (; j < mappedPositions.size(); j++) {
// Break if this position cannot be merged with lastPosition.
MappedPosition currentPosition = mappedPositions.get(j);
- // We allow for ranges being mapped to the same line but not to other ranges:
- // 1:10:void foo():42:42 -> a
- // is OK since retrace(a(:7)) = 42, however, the following is not OK:
- // 1:10:void foo():42:43 -> a
- // since retrace(a(:7)) = 49, which is not correct.
- boolean isSingleLine = currentPosition.originalLine == firstPosition.originalLine;
- boolean differentDelta =
- currentPosition.originalLine - lastPosition.originalLine
- != currentPosition.obfuscatedLine - lastPosition.obfuscatedLine;
- boolean isMappingRangeToSingleLine =
- firstPosition.obfuscatedLine != lastPosition.obfuscatedLine
- && firstPosition.originalLine == lastPosition.originalLine;
+ mappedPositionRange =
+ mappedPositionRange.canAddNextMappingToRange(lastPosition, currentPosition);
// Note that currentPosition.caller and lastPosition.class must be deep-compared since
// multiple inlining passes lose the canonical property of the positions.
if (currentPosition.method != lastPosition.method
- || (!isSingleLine && differentDelta)
- || (!isSingleLine && isMappingRangeToSingleLine)
+ || mappedPositionRange.isOutOfRange()
|| !Objects.equals(currentPosition.caller, lastPosition.caller)
// Break when we see a mapped outline
|| currentPosition.outlineCallee != null
@@ -822,6 +824,28 @@
return classNameMapperBuilder.build();
}
+ private static boolean isIdentityMapping(
+ MapVersion mapFileVersion,
+ List<MappedPosition> mappedPositions,
+ List<MappingInformation> methodMappingInfo,
+ ProgramMethod method,
+ DexString obfuscatedNameDexString,
+ DexMethod originalMethod,
+ DexType originalType) {
+ if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)) {
+ // Don't emit pure identity mappings.
+ return mappedPositions.isEmpty()
+ && methodMappingInfo.isEmpty()
+ && originalMethod == method.getReference();
+ } else {
+ // Don't emit pure identity mappings.
+ return mappedPositions.isEmpty()
+ && methodMappingInfo.isEmpty()
+ && obfuscatedNameDexString == originalMethod.name
+ && originalMethod.holder == originalType;
+ }
+ }
+
private static boolean hasAtMostOnePosition(
DexEncodedMethod definition, InternalOptions options) {
if (!mustHaveResidualDebugInfo(definition, options)) {
@@ -1414,6 +1438,55 @@
}
}
+ private enum MappedPositionRange {
+ // Single line represent a mapping on the form X:X:<method>:Y:Y.
+ SINGLE_LINE,
+ // Range to single line allows for a range on the left hand side, X:X':<method>:Y:Y
+ RANGE_TO_SINGLE,
+ // Same delta is when we have a range on both sides and the delta (line mapping between them)
+ // is the same: X:X':<method>:Y:Y' and delta(X,X') = delta(Y,Y')
+ SAME_DELTA,
+ // Out of range encodes a mapping range that cannot be encoded.
+ OUT_OF_RANGE;
+
+ private boolean isSingleLine() {
+ return this == SINGLE_LINE;
+ }
+
+ private boolean isRangeToSingle() {
+ return this == RANGE_TO_SINGLE;
+ }
+
+ private boolean isOutOfRange() {
+ return this == OUT_OF_RANGE;
+ }
+
+ public MappedPositionRange canAddNextMappingToRange(
+ MappedPosition lastPosition, MappedPosition currentPosition) {
+ if (isOutOfRange()) {
+ return this;
+ }
+ // We allow for ranges being mapped to the same line but not to other ranges:
+ // 1:10:void foo():42:42 -> a
+ // is OK since retrace(a(:7)) = 42, however, the following is not OK:
+ // 1:10:void foo():42:43 -> a
+ // since retrace(a(:7)) = 49, which is not correct.
+ boolean hasSameRightHandSide = lastPosition.originalLine == currentPosition.originalLine;
+ if (hasSameRightHandSide) {
+ boolean hasSameLeftHandSide = lastPosition.obfuscatedLine == currentPosition.obfuscatedLine;
+ return hasSameLeftHandSide ? SINGLE_LINE : RANGE_TO_SINGLE;
+ }
+ if (isRangeToSingle()) {
+ // We cannot recover a delta encoding if we have had range to single encoding.
+ return OUT_OF_RANGE;
+ }
+ boolean sameDelta =
+ currentPosition.originalLine - lastPosition.originalLine
+ == currentPosition.obfuscatedLine - lastPosition.obfuscatedLine;
+ return sameDelta ? SAME_DELTA : OUT_OF_RANGE;
+ }
+ }
+
private static class OutlineFixupBuilder {
private static final int MINIFIED_POSITION_REMOVED = -1;
diff --git a/src/main/java/com/android/tools/r8/utils/StackTraceUtils.java b/src/main/java/com/android/tools/r8/utils/StackTraceUtils.java
new file mode 100644
index 0000000..009c490
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/StackTraceUtils.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2022, 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.utils;
+
+import com.android.tools.r8.Keep;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+
+@Keep
+public class StackTraceUtils {
+
+ private static final String pathToWriteStacktrace =
+ System.getProperty("com.android.tools.r8.internalPathToStacktraces");
+
+ private static final String SEPARATOR = "@@@@";
+
+ private static final PrintStream printStream = getStacktracePrintStream();
+
+ private static final int samplingInterval = getSamplingInterval();
+
+ private static int getSamplingInterval() {
+ String setSamplingInterval =
+ System.getProperty("com.android.tools.r8.internalStackTraceSamplingInterval");
+ if (setSamplingInterval == null) {
+ return 1000;
+ }
+ return Integer.parseInt(setSamplingInterval);
+ }
+
+ private static int counter = 0;
+
+ private static PrintStream getStacktracePrintStream() {
+ if (pathToWriteStacktrace == null) {
+ throw new RuntimeException("pathToWriteStacktrace is null");
+ }
+ try {
+ return new PrintStream(pathToWriteStacktrace, StandardCharsets.UTF_8.name());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Utility function with the only purpose of being able to print stack traces from various
+ * inserted points in R8. See RetraceStackTraceFunctionalCompositionTest.
+ */
+ public static void printCurrentStack(long identifier) {
+ if (counter++ < samplingInterval) {
+ new RuntimeException("------(" + identifier + "," + counter + ")------")
+ .printStackTrace(printStream);
+ printStream.println(SEPARATOR);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 40a27ee..6c616b5 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -39,6 +39,19 @@
return builder().withNoneRuntime().build();
}
+ /**
+ * Returns true if the runtime uses resolution to lookup the constructor targeted by a given
+ * invoke, so that it is valid to have non-rebound constructor invokes.
+ *
+ * <p>Example: If value `v` is an uninitialized instanceof type `T`, then calling `T.<init>()`
+ * succeeds on ART even if `T.<init>()` does not exists, as ART will resolve the constructor, find
+ * `Object.<init>()`, and use this method to initialize `v`. On the JVM and on Dalvik, this is a
+ * runtime error.
+ */
+ public boolean canHaveNonReboundConstructorInvoke() {
+ return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+ }
+
public boolean canUseDefaultAndStaticInterfaceMethods() {
assert isCfRuntime() || isDexRuntime();
assert !isCfRuntime() || apiLevel == null
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 8d78785..726d550 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -173,10 +173,6 @@
return addDontWarn("javax.annotation.Nullable");
}
- public T addDontWarnJavaLangReflectAnnotatedType() {
- return addDontWarn("java.lang.reflect.AnnotatedType");
- }
-
public T addDontWarnJavaLangInvokeLambdaMetadataFactory() {
return addDontWarn("java.lang.invoke.LambdaMetafactory");
}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index 7c3bb9d..66dbfec 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -63,9 +63,6 @@
.setMode(CompilationMode.DEBUG)
.setMinApi(parameters.getApiLevel())
.applyIf(
- kotlinTestParameters.isKotlinDev(),
- TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
- .applyIf(
parameters.isCfRuntime() && kotlinTestParameters.isKotlinDev(),
TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
.compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 7d13c36..ccff561 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -36,6 +36,7 @@
import com.google.common.collect.Sets.SetView;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -237,7 +238,7 @@
public ProcessResult runDoublePerFileCompilation(Backend firstRoundOutput, boolean intermediate)
throws Exception {
- List<byte[]> outputsRoundOne = new ArrayList<>();
+ List<byte[]> outputsRoundOne = Collections.synchronizedList(new ArrayList<>());
testForD8(firstRoundOutput)
.addProgramClasses(CLASSES)
.setMinApi(parameters.getApiLevel())
@@ -248,7 +249,9 @@
@Override
public void accept(
ByteDataView data, String descriptor, DiagnosticsHandler handler) {
- outputsRoundOne.add(data.copyByteData());
+ byte[] bytes = data.copyByteData();
+ assert bytes != null;
+ outputsRoundOne.add(bytes);
}
}
: new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
@@ -258,7 +261,9 @@
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
- outputsRoundOne.add(data.copyByteData());
+ byte[] bytes = data.copyByteData();
+ assert bytes != null;
+ outputsRoundOne.add(bytes);
}
@Override
@@ -270,6 +275,7 @@
List<Path> outputsRoundTwo = new ArrayList<>();
for (byte[] bytes : outputsRoundOne) {
+ assert bytes != null;
outputsRoundTwo.add(
testForD8(parameters.getBackend())
.applyIf(
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
new file mode 100644
index 0000000..929a3fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2022, 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.desugar.backports;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.LATEST;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ThreadLocalBackportTest extends DesugaredLibraryTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkExpected);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(
+ parameters.isCfRuntime()
+ ? ToolHelper.getJava8RuntimeJar()
+ : ToolHelper.getAndroidJar(LATEST))
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkExpected);
+ }
+
+ private void checkExpected(TestRunResult<?> result) {
+ result.applyIf(
+ parameters.getRuntime().isCf()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+ || parameters
+ .getRuntime()
+ .asDex()
+ .getMinApiLevel()
+ .isGreaterThanOrEqualTo(AndroidApiLevel.O),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ parameters.getRuntime().isDex()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .getMinApiLevel()
+ .isGreaterThanOrEqualTo(AndroidApiLevel.N)
+ && parameters.getRuntime().asDex().getMinApiLevel().isLessThan(AndroidApiLevel.O)
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+ r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Hello, world!");
+ System.out.println(threadLocal.get());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportWithDesugaredLibraryTest.java
new file mode 100644
index 0000000..67ee012
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportWithDesugaredLibraryTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, 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.desugar.backports;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ThreadLocalBackportWithDesugaredLibraryTest extends DesugaredLibraryTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+ @Parameter(2)
+ public CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ ImmutableList.of(JDK8, JDK11_LEGACY, JDK11_MINIMAL, JDK11, JDK11_PATH),
+ SPECIFICATIONS_WITH_CF2CF);
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testWithDesugaredLibrary() throws Exception {
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Hello, world!");
+ System.out.println(threadLocal.get());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index be94723..8c572cf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -99,12 +99,6 @@
.allowDiagnosticMessages()
.allowUnusedDontWarnKotlinReflectJvmInternal(
kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72))
- .applyIfR8TestBuilder(
- b -> {
- if (kotlinParameters.isKotlinDev()) {
- b.addDontWarnJavaLangReflectAnnotatedType();
- }
- })
.compile()
.inspect(
i -> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
index 8a540ba..2d5abb5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
@@ -74,7 +74,6 @@
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
- .enableExperimentalMapFileVersion()
.compile()
.run(
parameters.getRuntime(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
index fddf01c..a177c41 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
@@ -73,7 +73,6 @@
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
- .enableExperimentalMapFileVersion()
.compile()
.run(
parameters.getRuntime(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index f265abe..4032c83 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -9,7 +9,6 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -228,7 +227,9 @@
references(clazz, "testSimpleWithLazyInit", "void"));
ClassSubject simpleWithLazyInit = inspector.clazz(SimpleWithLazyInit.class);
- assertFalse(instanceMethods(simpleWithLazyInit).isEmpty());
+ assertEquals(
+ parameters.canHaveNonReboundConstructorInvoke(),
+ instanceMethods(simpleWithLazyInit).isEmpty());
assertThat(simpleWithLazyInit.clinit(), isPresent());
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
index 5497d76..718a3e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
@@ -136,7 +136,8 @@
@Override
public void inspectTestClass(ClassSubject clazz) {
- assertEquals(11, clazz.allMethods().size());
+ assertEquals(
+ parameters.canHaveNonReboundConstructorInvoke() ? 10 : 11, clazz.allMethods().size());
clazz.forAllMethods(
method ->
Assert.assertTrue(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
index 822f9f9..4a06340 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
@@ -24,8 +24,8 @@
@RunWith(Parameterized.class)
public abstract class UnusedArgumentsTestBase extends TestBase {
- private final TestParameters parameters;
- private final boolean minification;
+ protected final TestParameters parameters;
+ protected final boolean minification;
public UnusedArgumentsTestBase(TestParameters parameters, boolean minification) {
this.parameters = parameters;
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
index acd52f2..214b507 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -41,9 +41,6 @@
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
.addKeepRules(rules)
.applyIf(
- notShrinking && kotlinParameters.isKotlinDev(),
- TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
- .applyIf(
notShrinking && kotlinParameters.isKotlinDev() && parameters.isCfRuntime(),
TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
.addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 79af7bd..5272541 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -105,10 +105,7 @@
String renamedSuperTypeName = "com.android.tools.r8.kotlin.metadata.typealias_lib.FooBar";
Path libJar =
testForR8(parameters.getBackend())
- .addClasspathFiles(
- kotlinc.getKotlinStdlibJar(),
- kotlinc.getKotlinReflectJar(),
- kotlinc.getKotlinAnnotationJar())
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
.addProgramFiles(typeAliasLibJarMap.getForConfiguration(kotlinc, targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep class **.Impl { !private *; }")
@@ -126,8 +123,8 @@
.addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester$Companion { *; }")
.addKeepRules("-keep class " + PKG + ".typealias_lib.SubTypeOfAlias { *; }")
.addApplyMapping(superTypeName + " -> " + renamedSuperTypeName + ":")
+ .addKeepKotlinMetadata()
.addKeepAttributes(
- ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
ProguardKeepAttributes.SIGNATURE,
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index a5400d6..6078cef 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -102,9 +102,6 @@
.applyIf(
parameters.isCfRuntime() && kotlinParameters.isKotlinDev(),
TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
- .applyIf(
- kotlinParameters.isKotlinDev(),
- TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
.compile()
.assertNoErrorMessages()
// -keepattributes Signature is added in kotlin-reflect from version 1.4.20.
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
index 0855291..1a73766 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -59,7 +59,6 @@
.addKeepAttributeSourceFile()
.setMinApi(parameters.getApiLevel())
.enableInliningAnnotations()
- .enableExperimentalMapFileVersion()
.run(parameters.getRuntime(), Caller.class, getArgs())
.assertFailureWithErrorThatThrows(NullPointerException.class)
.inspectStackTrace(
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index e0a469c..57761fe 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.regress.b69825683;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -19,20 +20,20 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Regress69825683Test extends TestBase {
- private final TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public Regress69825683Test(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void outerConstructsInner() throws Exception {
Class<?> inner = com.android.tools.r8.regress.b69825683.outerconstructsinner.Outer.Inner.class;
@@ -52,7 +53,7 @@
" synthetic void <init>(...);",
"}",
"-keepunusedarguments class " + inner.getName() + " {",
- " synthetic void <init>(...);",
+ " void <init>(...);",
"}")
.addOptionsModification(options -> options.enableClassInlining = false)
.addDontObfuscate()
@@ -87,7 +88,10 @@
.setMinApi(parameters.getApiLevel())
// Run code to check that the constructor with synthetic class as argument is present.
.run(parameters.getRuntime(), clazz)
- .assertSuccessWithOutputThatMatches(startsWith(clazz.getName()))
+ .assertSuccessWithOutputThatMatches(
+ parameters.canHaveNonReboundConstructorInvoke()
+ ? equalTo("")
+ : startsWith(clazz.getName()))
.inspector();
List<FoundClassSubject> classes = inspector.allClasses();
diff --git a/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java b/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java
index 2cf6b4f..d10454d 100644
--- a/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java
+++ b/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java
@@ -8,6 +8,9 @@
import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import java.io.IOException;
@@ -15,8 +18,20 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
-public class B79498926 {
+@RunWith(Parameterized.class)
+public class B79498926 extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().build();
+ }
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -33,6 +48,6 @@
.setProgramConsumer(consumer)
.setDisableDesugaring(true)
.build());
- ToolHelper.runDex2Oat(outDex, outOat);
+ ToolHelper.runDex2Oat(outDex, outOat, parameters.getRuntime().asDex().getVm());
}
}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
new file mode 100644
index 0000000..09a05ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
@@ -0,0 +1,263 @@
+// Copyright (c) 2022, 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.retrace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StackTraceUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Supplier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class RetraceStackTraceFunctionalCompositionTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ private Path rewrittenR8Jar;
+ private static final int SAMPLING_SIZE = 50000;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ assumeTrue(ToolHelper.isLinux());
+ return getTestParameters().withDefaultCfRuntime().build();
+ }
+
+ private void insertPrintingOfStacktraces(Path path, Path outputPath) throws Exception {
+ String entryNameForClass = ZipUtils.zipEntryNameForClass(StackTraceUtils.class);
+ Box<Long> idBox = new Box<>(0L);
+ ZipUtils.map(
+ path,
+ outputPath,
+ (zipEntry, bytes) -> {
+ String entryName = zipEntry.getName();
+ // Only insert into the R8 namespace, this will provide a better sampling than inserting
+ // into all calls due to more inlining/outlining could have happened.
+ if (ZipUtils.isClassFile(entryName)
+ && !entryNameForClass.equals(entryName)
+ && entryName.contains("com/android/tools/r8/")) {
+ return transformer(bytes, null)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor sub =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (name.equals("<clinit>")) {
+ return sub;
+ } else {
+ return new InsertStackTraceCallTransformer(
+ sub, () -> idBox.getAndCompute(x -> x + 1));
+ }
+ }
+ ;
+ })
+ .transform();
+ }
+ return bytes;
+ });
+ }
+
+ private static class InsertStackTraceCallTransformer extends MethodTransformer {
+
+ private boolean insertedStackTraceCall = false;
+ private final Supplier<Long> idGenerator;
+
+ private InsertStackTraceCallTransformer(MethodVisitor visitor, Supplier<Long> idGenerator) {
+ this.mv = visitor;
+ this.idGenerator = idGenerator;
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
+ insertPrintIfFirstInstruction();
+ super.visitFieldInsn(opcode, owner, name, descriptor);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ insertPrintIfFirstInstruction();
+ super.visitInsn(opcode);
+ }
+
+ @Override
+ public void visitLdcInsn(Object value) {
+ insertPrintIfFirstInstruction();
+ super.visitLdcInsn(value);
+ }
+
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ insertPrintIfFirstInstruction();
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+
+ private void insertPrintIfFirstInstruction() {
+ if (!insertedStackTraceCall) {
+ super.visitLdcInsn(idGenerator.get());
+ super.visitMethodInsn(
+ INVOKESTATIC, binaryName(StackTraceUtils.class), "printCurrentStack", "(J)V", false);
+ insertedStackTraceCall = true;
+ }
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(maxStack + 2, maxLocals);
+ }
+ }
+
+ private Path getRewrittenR8Jar() throws Exception {
+ if (rewrittenR8Jar != null) {
+ return rewrittenR8Jar;
+ }
+ rewrittenR8Jar = temp.newFolder().toPath().resolve("r8_with_deps_with_prints.jar");
+ insertPrintingOfStacktraces(ToolHelper.R8_WITH_DEPS_JAR, rewrittenR8Jar);
+ return rewrittenR8Jar;
+ }
+
+ @Test
+ public void testR8RetraceAndComposition() throws Exception {
+ Path rewrittenR8Jar = getRewrittenR8Jar();
+ List<String> originalStackTraces = generateStackTraces(rewrittenR8Jar);
+ Map<String, List<String>> originalPartitions = partitionStacktraces(originalStackTraces);
+
+ List<String> originalStackTracesDeterministicCheck = generateStackTraces(rewrittenR8Jar);
+ Map<String, List<String>> deterministicPartitions =
+ partitionStacktraces(originalStackTracesDeterministicCheck);
+ comparePartitionedStackTraces(originalPartitions, deterministicPartitions);
+
+ // Compile rewritten R8 with R8 to obtain first level
+ Pair<Path, Path> r8OfR8 = compileR8WithR8(rewrittenR8Jar);
+ List<String> firstLevelStackTraces = generateStackTraces(r8OfR8.getFirst());
+
+ // If we retrace the entire file we should get the same result as the original.
+ List<String> retracedFirstLevelStackTraces = new ArrayList<>();
+ Retrace.run(
+ RetraceCommand.builder()
+ .setRetracedStackTraceConsumer(retracedFirstLevelStackTraces::addAll)
+ .setStackTrace(firstLevelStackTraces)
+ .setMappingSupplier(
+ ProguardMappingSupplier.builder()
+ .setProguardMapProducer(ProguardMapProducer.fromPath(r8OfR8.getSecond()))
+ .build())
+ .build());
+ Map<String, List<String>> firstRoundPartitions =
+ partitionStacktraces(retracedFirstLevelStackTraces);
+ comparePartitionedStackTraces(originalPartitions, firstRoundPartitions);
+ }
+
+ private void comparePartitionedStackTraces(
+ Map<String, List<String>> one, Map<String, List<String>> other) {
+ for (Entry<String, List<String>> keyStackTraceEntry : one.entrySet()) {
+ String oneAsString = StringUtils.lines(keyStackTraceEntry.getValue());
+ String otherAsString = StringUtils.lines(other.get(keyStackTraceEntry.getKey()));
+ assertEquals(oneAsString, otherAsString);
+ }
+ assertEquals(one.keySet(), other.keySet());
+ }
+
+ private Map<String, List<String>> partitionStacktraces(List<String> allStacktraces) {
+ Map<String, List<String>> partitions = new LinkedHashMap<>();
+ int lastIndex = 0;
+ for (int i = 0; i < allStacktraces.size(); i++) {
+ if (allStacktraces.get(i).contains("@@@@")) {
+ List<String> stackTrace = allStacktraces.subList(lastIndex, i);
+ String keyForStackTrace = getKeyForStackTrace(stackTrace);
+ List<String> existing = partitions.put(keyForStackTrace, stackTrace);
+ assertNull(existing);
+ lastIndex = i + 1;
+ i++;
+ }
+ }
+ return partitions;
+ }
+
+ private String getKeyForStackTrace(List<String> stackTrace) {
+ String identifier = "java.lang.RuntimeException: ------(";
+ String firstLine = stackTrace.get(0);
+ int index = firstLine.indexOf(identifier);
+ assertEquals(0, index);
+ String endIdentifier = ")------";
+ int endIndex = firstLine.indexOf(endIdentifier);
+ assertTrue(endIndex > 0);
+ return firstLine.substring(index + identifier.length(), endIndex);
+ }
+
+ private Pair<Path, Path> compileR8WithR8(Path r8Input) throws Exception {
+ Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+ Path jar = temp.newFolder().toPath().resolve("out.jar");
+ Path map = temp.newFolder().toPath().resolve("out.map");
+ testForR8(Backend.CF)
+ .setMode(CompilationMode.RELEASE)
+ .addProgramFiles(r8Input)
+ .addKeepRuleFiles(MAIN_KEEP)
+ .allowUnusedProguardConfigurationRules()
+ .addDontObfuscate()
+ .compile()
+ .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
+ .writeToZip(jar);
+ return Pair.create(jar, map);
+ }
+
+ private List<String> generateStackTraces(Path r8Jar) throws Exception {
+ File stacktraceOutput = new File(temp.newFolder(), "stacktraces.txt");
+ stacktraceOutput.createNewFile();
+ testForExternalR8(Backend.DEX, parameters.getRuntime())
+ .useProvidedR8(r8Jar)
+ .addProgramClasses(HelloWorld.class)
+ .addKeepMainRule(HelloWorld.class)
+ .setMinApi(AndroidApiLevel.B)
+ .addJvmFlag("-Dcom.android.tools.r8.deterministicdebugging=true")
+ .addJvmFlag("-Dcom.android.tools.r8.internalStackTraceSamplingInterval=" + SAMPLING_SIZE)
+ .addJvmFlag("-Dcom.android.tools.r8.internalPathToStacktraces=" + stacktraceOutput.toPath())
+ .compile();
+ return Files.readAllLines(stacktraceOutput.toPath(), StandardCharsets.UTF_8);
+ }
+
+ public static class HelloWorld {
+
+ public static void main(String[] args) {
+ System.out.println("Hello World");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiResidualSignatureTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiResidualSignatureTest.java
new file mode 100644
index 0000000..21e1905
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiResidualSignatureTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2022, 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.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFieldElement;
+import com.android.tools.r8.retrace.RetraceMethodElement;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiResidualSignatureTest extends RetraceApiTestBase {
+
+ public RetraceApiResidualSignatureTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ public static class ApiTest implements RetraceApiBinaryTest {
+
+ private final String mapping =
+ "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+ + "some.Class -> a:\n"
+ + " some.SuperType field -> a\n"
+ + " # { id: 'com.android.tools.r8.residualsignature', signature: 'Ljava/lang/Object;'"
+ + " }\n"
+ + " some.SubType field -> a\n"
+ + " # { id: 'com.android.tools.r8.residualsignature', signature: 'Lsome/SuperType;'"
+ + " }\n"
+ + " void foo(int, int) -> x\n"
+ + " # { id: 'com.android.tools.r8.residualsignature', signature: '(I)V' }\n"
+ + " void foo(int) -> x\n"
+ + " # { id: 'com.android.tools.r8.residualsignature', signature: '()V' }\n";
+
+ @Test
+ public void testResidualSignature() {
+ Retracer retracer =
+ Retracer.createDefault(
+ ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+ ClassReference aClass = Reference.classFromTypeName("a");
+ List<RetraceMethodElement> fooWithTwoArgs =
+ retracer.retraceMethod(Reference.methodFromDescriptor(aClass, "x", "(I)V")).stream()
+ .collect(Collectors.toList());
+ // TODO(b/169953605): Use the residual signature information to prune the result.
+ assertEquals(2, fooWithTwoArgs.size());
+ List<RetraceMethodElement> fooWithOneArg =
+ retracer.retraceMethod(Reference.methodFromDescriptor(aClass, "x", "()V")).stream()
+ .collect(Collectors.toList());
+ // TODO(b/169953605): Use the residual signature information to prune the result.
+ assertEquals(2, fooWithOneArg.size());
+ List<RetraceFieldElement> fieldWithSuperType =
+ retracer
+ .retraceField(
+ Reference.field(aClass, "a", Reference.typeFromTypeName("java.lang.Object")))
+ .stream()
+ .collect(Collectors.toList());
+ // TODO(b/169953605): Use the residual signature information to prune the result.
+ assertEquals(2, fieldWithSuperType.size());
+ List<RetraceFieldElement> fieldWithSubType =
+ retracer
+ .retraceField(
+ Reference.field(aClass, "a", Reference.typeFromTypeName("some.SuperType")))
+ .stream()
+ .collect(Collectors.toList());
+ // TODO(b/169953605): Use the residual signature information to prune the result.
+ assertEquals(2, fieldWithSubType.size());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index e1e73b8..327be4d 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -46,7 +46,7 @@
RetracePartitionRoundTripInlineTest.ApiTest.class);
public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
- ImmutableList.of();
+ ImmutableList.of(RetraceApiResidualSignatureTest.ApiTest.class);
private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
index 6ff7000..85f698d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -40,8 +39,6 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .addOptionsModification(
- options -> options.testing.enableRedundantConstructorBridgeRemoval = true)
.enableConstantArgumentAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
@@ -53,10 +50,6 @@
}
private void inspect(CodeInspector inspector) {
- boolean canHaveNonReboundConstructorInvoke =
- parameters.isDexRuntime()
- && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
-
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
assertEquals(2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
@@ -64,13 +57,13 @@
ClassSubject bClassSubject = inspector.clazz(B.class);
assertThat(bClassSubject, isPresent());
assertEquals(
- canHaveNonReboundConstructorInvoke ? 0 : 2,
+ parameters.canHaveNonReboundConstructorInvoke() ? 0 : 2,
bClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
ClassSubject cClassSubject = inspector.clazz(C.class);
assertThat(cClassSubject, isPresent());
assertEquals(
- canHaveNonReboundConstructorInvoke ? 0 : 2,
+ parameters.canHaveNonReboundConstructorInvoke() ? 0 : 2,
cClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
index c7f9a92..436e50e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
@@ -41,14 +41,12 @@
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addOptionsModification(
- options -> {
- options.testing.enableRedundantConstructorBridgeRemoval = true;
- options.testing.horizontalClassMergingTarget =
- (appView, candidates, target) ->
- Iterables.find(
- candidates,
- candidate -> candidate.getTypeName().equals(B.class.getTypeName()));
- })
+ options ->
+ options.testing.horizontalClassMergingTarget =
+ (appView, candidates, target) ->
+ Iterables.find(
+ candidates,
+ candidate -> candidate.getTypeName().equals(B.class.getTypeName())))
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertMergedInto(A.class, B.class).assertNoOtherClassesMerged())
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
index f914a87..4221794 100644
--- a/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
@@ -38,8 +38,6 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .addOptionsModification(
- options -> options.testing.enableRedundantConstructorBridgeRemoval = true)
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index a48c5e9..265e46a 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -9,47 +9,43 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.lang.reflect.Method;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
-@NoVerticalClassMerging
-interface I {
- default void m() {
- System.out.println("I::m");
- }
-}
-
-class C implements I {
-}
-
-class BridgeInliningTestRunner {
- public static void main(String[] args) throws Exception {
- C obj = new C();
- for (Method m : obj.getClass().getDeclaredMethods()) {
- m.invoke(obj);
- }
- }
-}
-
+@RunWith(Parameterized.class)
public class BridgeInliningTest extends TestBase {
- private static final Class<?> MAIN = BridgeInliningTestRunner.class;
- private static final String EXPECTED_OUTPUT = StringUtils.lines("I::m");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(AndroidApiLevel.N)
+ .build();
+ }
@Test
public void test() throws Exception {
- testForR8(Backend.DEX)
- .addProgramClasses(I.class, C.class, MAIN)
- .setMinApi(AndroidApiLevel.L)
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules("-keep interface " + I.class.getTypeName() + " { m(); }")
.enableNoVerticalClassMergingAnnotations()
- .addKeepMainRule(MAIN)
- .addKeepRules("-keep interface **.I { m(); }")
- .run(MAIN)
- .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I::m")
.inspect(this::inspect);
}
@@ -67,4 +63,21 @@
//});
}
+ static class Main {
+ public static void main(String[] args) throws Exception {
+ C obj = new C();
+ for (Method m : obj.getClass().getDeclaredMethods()) {
+ m.invoke(obj);
+ }
+ }
+ }
+
+ @NoVerticalClassMerging
+ interface I {
+ default void m() {
+ System.out.println("I::m");
+ }
+ }
+
+ static class C implements I {}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java b/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
index 07be6e7..1cff714 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -13,13 +15,34 @@
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public abstract class MethodsTestBase extends TestBase {
+ @Parameter(0)
+ public static TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+ }
+
public enum Shrinker {
Proguard,
R8Full,
- R8Compat
+ R8Compat;
+
+ public boolean isProguard() {
+ return this == Proguard;
+ }
+
+ public boolean isR8Full() {
+ return this == R8Full;
+ }
}
public abstract Collection<Class<?>> getClasses();
@@ -29,7 +52,8 @@
public void testOnR8(
List<String> keepRules, BiConsumer<CodeInspector, Shrinker> inspector, String expected)
throws Throwable {
- testForR8(Backend.DEX)
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
.enableNoVerticalClassMergingAnnotations()
.addProgramClasses(getClasses())
.addKeepRules(keepRules)
@@ -42,7 +66,7 @@
public void testOnR8Compat(
List<String> keepRules, BiConsumer<CodeInspector, Shrinker> inspector, String expected)
throws Throwable {
- testForR8Compat(Backend.DEX)
+ testForR8Compat(parameters.getBackend())
.enableNoVerticalClassMergingAnnotations()
.addProgramClasses(getClasses())
.addKeepRules(keepRules)
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
index 2ce8046..477a310 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
@@ -10,12 +10,15 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.shaking.methods.MethodsTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
import org.junit.Test;
@NoVerticalClassMerging
@@ -63,6 +66,24 @@
return Main.class;
}
+ private boolean willShrinkConstructors(Shrinker shrinker) {
+ return shrinker.isR8Full()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+ }
+
+ private static BiConsumer<CodeInspector, Shrinker> applyInspectorIf(
+ Predicate<Shrinker> predicate,
+ BiConsumer<CodeInspector, Shrinker> thenInspector,
+ BiConsumer<CodeInspector, Shrinker> elseInspector) {
+ return (inspector, shrinker) -> {
+ if (predicate.test(shrinker)) {
+ thenInspector.accept(inspector, shrinker);
+ } else {
+ elseInspector.accept(inspector, shrinker);
+ }
+ };
+ }
+
private void checkConstructors(CodeInspector inspector, Set<String> expected) {
ClassSubject superSubject = inspector.clazz(Super.class);
assertThat(superSubject, isPresent());
@@ -100,6 +121,11 @@
checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
}
+ private void checkAllMethodsAndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+ checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3"));
+ checkConstructors(inspector, ImmutableSet.of("SubSub"));
+ }
+
private void checkAllMethodsNoConstructorsInFullMode(CodeInspector inspector, Shrinker shrinker) {
checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3"));
checkNoConstructorsInFullMode(inspector, shrinker);
@@ -115,6 +141,11 @@
checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
}
+ private void checkM1AndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+ checkMethods(inspector, ImmutableSet.of("m1"));
+ checkConstructors(inspector, ImmutableSet.of("SubSub"));
+ }
+
private void checkOnlyM2(CodeInspector inspector, Shrinker shrinker) {
checkMethods(inspector, ImmutableSet.of("m2"));
checkNoConstructorsInFullMode(inspector, shrinker);
@@ -125,6 +156,11 @@
checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
}
+ private void checkM2AndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+ checkMethods(inspector, ImmutableSet.of("m2"));
+ checkConstructors(inspector, ImmutableSet.of("SubSub"));
+ }
+
private void checkOnlyM3(CodeInspector inspector, Shrinker shrinker) {
checkMethods(inspector, ImmutableSet.of("m3"));
checkNoConstructorsInFullMode(inspector, shrinker);
@@ -135,11 +171,19 @@
checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
}
+ private void checkM3AndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+ checkMethods(inspector, ImmutableSet.of("m3"));
+ checkConstructors(inspector, ImmutableSet.of("SubSub"));
+ }
+
@Test
public void testKeepAllMethodsWithWildcard() throws Throwable {
runTest(
"-keep class **.SubSub { *; }",
- this::checkAllMethodsAndAllConstructors,
+ applyInspectorIf(
+ this::willShrinkConstructors,
+ this::checkAllMethodsAndSubSubConstructor,
+ this::checkAllMethodsAndAllConstructors),
allMethodsOutput());
}
@@ -147,7 +191,10 @@
public void testKeepAllMethodsWithMethods() throws Throwable {
runTest(
"-keep class **.SubSub { <methods>; }",
- this::checkAllMethodsAndAllConstructors,
+ applyInspectorIf(
+ this::willShrinkConstructors,
+ this::checkAllMethodsAndSubSubConstructor,
+ this::checkAllMethodsAndAllConstructors),
allMethodsOutput());
}
@@ -163,7 +210,10 @@
public void testKeepDefaultConstructorAndAllMethodsWithNameWildcard() throws Throwable {
runTest(
"-keep class **.SubSub { <init>(); void m*(); }",
- this::checkAllMethodsAndAllConstructors,
+ applyInspectorIf(
+ this::willShrinkConstructors,
+ this::checkAllMethodsAndSubSubConstructor,
+ this::checkAllMethodsAndAllConstructors),
allMethodsOutput());
}
@@ -176,7 +226,10 @@
public void testKeepDefaultConstructorAndKeepM1() throws Throwable {
runTest(
"-keep class **.SubSub { <init>(); void m1(); }",
- this::checkM1AndAllConstructors,
+ applyInspectorIf(
+ this::willShrinkConstructors,
+ this::checkM1AndSubSubConstructor,
+ this::checkM1AndAllConstructors),
onlyM1Output());
}
@@ -189,7 +242,10 @@
public void testKeepDefaultConstructorAndKeepM2() throws Throwable {
runTest(
"-keep class **.SubSub { <init>(); void m2(); }",
- this::checkM2AndAllConstructors,
+ applyInspectorIf(
+ this::willShrinkConstructors,
+ this::checkM2AndSubSubConstructor,
+ this::checkM2AndAllConstructors),
onlyM2Output());
}
@@ -202,7 +258,10 @@
public void testKeepDefaultConstructorAndKeepM3() throws Throwable {
runTest(
"-keep class **.SubSub { <init>(); void m3(); }",
- this::checkM3AndAllConstructors,
+ applyInspectorIf(
+ this::willShrinkConstructors,
+ this::checkM3AndSubSubConstructor,
+ this::checkM3AndAllConstructors),
onlyM3Output());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
index e9af866..ad6b6ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
@@ -180,7 +180,7 @@
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
// TODO(160142903): @NeverInline does not seem to work on static interface methods.
// TODO(160144053): Using -keepclassmembers for this cause InterfaceWithStaticMethods to
- // be renamed.
+ // be renamed.
if (allowObfuscation) {
builder.add(
"-if class " + InterfaceWithStaticMethods.class.getTypeName(),
diff --git a/tools/cherry-pick.py b/tools/cherry-pick.py
index a51ed00..6a7109c 100755
--- a/tools/cherry-pick.py
+++ b/tools/cherry-pick.py
@@ -53,15 +53,7 @@
subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
subprocess.run(['git', 'cherry-pick', hash])
-
- commit_message = subprocess.check_output(['git', 'log', '--format=%B', '-n', '1', 'HEAD'])
- commit_lines = [l.strip() for l in commit_message.decode('UTF-8').split('\n')]
- for line in commit_lines:
- if line.startswith('Bug: '):
- normalized = line.replace('Bug: ', '').replace('b/', '')
- if len(normalized) > 0:
- bugs.add(normalized)
- confirm_and_upload(branch, args)
+ confirm_and_upload(branch, args, bugs)
count = count + 1
branch = 'cherry-%d' % count
@@ -96,18 +88,44 @@
message += 'Bug: b/%s\n' % bug
subprocess.run(['git', 'commit', '-a', '-m', message])
- confirm_and_upload(branch, args)
+ confirm_and_upload(branch, args, None)
if (not args.current_checkout):
- answer = input('Done, press enter to delete checkout in %s' % os.getcwd())
+ while True:
+ try:
+ answer = input("Type 'delete' to finish and delete checkout in %s: " % os.getcwd())
+ if answer == 'delete':
+ break
+ except KeyboardInterrupt:
+ pass
-def confirm_and_upload(branch, args):
+def confirm_and_upload(branch, args, bugs):
question = ('Ready to continue (cwd %s, will not upload to Gerrit)' % os.getcwd()
if args.no_upload else
'Ready to upload %s (cwd %s)' % (branch, os.getcwd()))
- answer = input(question + ' [y/N]?')
- if answer != 'y':
- print('Aborting new branch for %s' % branch_version)
- sys.exit(1)
+
+ while True:
+ try:
+ answer = input(question + ' [yes/abort]? ')
+ if answer == 'yes':
+ break
+ if answer == 'abort':
+ print('Aborting new branch for %s' % branch)
+ sys.exit(1)
+ except KeyboardInterrupt:
+ pass
+
+ # Compute the set of bug refs from the commit message after confirmation.
+ # If done before a conflicting cherry-pick status will potentially include
+ # references that are orthogonal to the pick.
+ if bugs != None:
+ commit_message = subprocess.check_output(['git', 'log', '--format=%B', '-n', '1', 'HEAD'])
+ commit_lines = [l.strip() for l in commit_message.decode('UTF-8').split('\n')]
+ for line in commit_lines:
+ if line.startswith('Bug: '):
+ normalized = line.replace('Bug: ', '').replace('b/', '')
+ if len(normalized) > 0:
+ bugs.add(normalized)
+
if (not args.no_upload):
subprocess.run(['git', 'cl', 'upload', '--bypass-hooks'])
diff --git a/tools/extractmarker.py b/tools/extractmarker.py
index b50a2fd..d8348dd 100755
--- a/tools/extractmarker.py
+++ b/tools/extractmarker.py
@@ -3,8 +3,30 @@
# 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.
+import argparse
import sys
import toolhelper
+def extractmarker(apk_or_dex, build=True):
+ stdout = toolhelper.run('extractmarker', [apk_or_dex], build=build, return_stdout=True)
+ return stdout
+
+def parse_options(argv):
+ result = argparse.ArgumentParser(
+ description='Relayout a given APK using a startup profile.')
+ result.add_argument('--no-build',
+ action='store_true',
+ default=False,
+ help='To disable building using gradle')
+ options, args = result.parse_known_args(argv)
+ return options, args
+
+def main(argv):
+ options, args = parse_options(argv)
+ build = not options.no_build
+ for apk_or_dex in args:
+ print(extractmarker(apk_or_dex, build=build))
+ build = False
+
if __name__ == '__main__':
- sys.exit(toolhelper.run('extractmarker', sys.argv[1:]))
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/relayout.py b/tools/startup/relayout.py
index 6220336..0ace441 100755
--- a/tools/startup/relayout.py
+++ b/tools/startup/relayout.py
@@ -11,8 +11,10 @@
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import apk_masseur
+import extractmarker
import toolhelper
import utils
+import zip_utils
def parse_options(argv):
result = argparse.ArgumentParser(
@@ -20,6 +22,15 @@
result.add_argument('--apk',
help='Path to the .apk',
required=True)
+ result.add_argument('--desugared-library',
+ choices=['auto', 'true', 'false'],
+ default='auto',
+ help='Whether the last dex file of the app is desugared '
+ 'library')
+ result.add_argument('--no-build',
+ action='store_true',
+ default=False,
+ help='To disable building using gradle')
result.add_argument('--out',
help='Destination of resulting apk',
required=True)
@@ -29,6 +40,29 @@
options, args = result.parse_known_args(argv)
return options, args
+def get_dex_to_relayout(options, temp):
+ marker = extractmarker.extractmarker(options.apk, build=not options.no_build)
+ if '~~L8' not in marker:
+ return [options.apk], None
+ dex_dir = os.path.join(temp, 'dex')
+ dex_predicate = \
+ lambda name : name.startswith('classes') and name.endswith('.dex')
+ extracted_dex_files = \
+ zip_utils.extract_all_that_matches(options.apk, dex_dir, dex_predicate)
+ desugared_library_dex = 'classes%s.dex' % len(extracted_dex_files)
+ assert desugared_library_dex in extracted_dex_files
+ return [
+ os.path.join(dex_dir, name) \
+ for name in extracted_dex_files if name != desugared_library_dex], \
+ os.path.join(dex_dir, desugared_library_dex)
+
+def has_desugared_library_dex(options):
+ if options.desugared_library == 'auto':
+ marker = extractmarker.extractmarker(
+ options.apk, build=not options.no_build)
+ return '~~L8' in marker
+ return options.desugared_library == 'true'
+
def get_min_api(apk):
aapt = os.path.join(utils.getAndroidBuildTools(), 'aapt')
cmd = [aapt, 'dump', 'badging', apk]
@@ -46,14 +80,21 @@
'--min-api', str(get_min_api(options.apk)),
'--output', dex,
'--no-desugaring',
- '--release',
- options.apk]
+ '--release']
+ dex_to_relayout, desugared_library_dex = get_dex_to_relayout(options, temp)
+ d8_args.extend(dex_to_relayout)
extra_args = ['-Dcom.android.tools.r8.startup.profile=%s' % options.profile]
toolhelper.run(
'd8',
d8_args,
+ build=not options.no_build,
extra_args=extra_args,
main='com.android.tools.r8.D8')
+ if desugared_library_dex is not None:
+ dex_files = [name for name in \
+ zip_utils.get_names_that_matches(dex, lambda x : True)]
+ zip_utils.add_file_to_zip(
+ desugared_library_dex, 'classes%s.dex' % str(len(dex_files) + 1), dex)
apk_masseur.masseur(options.apk, dex=dex, out=options.out)
if __name__ == '__main__':
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index c728953..881f6dc 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -63,10 +63,10 @@
stdout, stderr = proc.communicate()
finally:
timer.cancel()
- result = stdout if return_stdout else proc.returncode
+ result = stdout.decode('utf-8') if return_stdout else proc.returncode
else:
result = (
- subprocess.check_output(cmd)
+ subprocess.check_output(cmd).decode('utf-8')
if return_stdout
else subprocess.call(cmd, stdout=stdout, stderr=stderr))
duration = int((time.time() - start) * 1000)
diff --git a/tools/zip_utils.py b/tools/zip_utils.py
index b0571f2..795119b 100644
--- a/tools/zip_utils.py
+++ b/tools/zip_utils.py
@@ -8,3 +8,13 @@
def add_file_to_zip(file, destination, zip_file):
with zipfile.ZipFile(zip_file, 'a') as zip:
zip.write(file, destination)
+
+def extract_all_that_matches(zip_file, destination, predicate):
+ with zipfile.ZipFile(zip_file) as zip:
+ names_to_extract = [name for name in zip.namelist() if predicate(name)]
+ zip.extractall(path=destination, members=names_to_extract)
+ return names_to_extract
+
+def get_names_that_matches(zip_file, predicate):
+ with zipfile.ZipFile(zip_file) as zip:
+ return [name for name in zip.namelist() if predicate(name)]