Merge "Ensure that golem does not have a daemon running"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 46f4876..b7c538f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -352,7 +352,7 @@
.rewrittenWithLense(application.asDirect(), appView.graphLense()));
timing.end();
}
- appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
+ appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness, options).run());
if (options.enableVerticalClassMerging) {
timing.begin("ClassMerger");
VerticalClassMerger verticalClassMerger =
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 22790b4..75ce373 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -412,6 +412,10 @@
return null;
}
+ public boolean isSerializable(AppInfo appInfo) {
+ return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
+ }
+
public boolean isExternalizable(AppInfo appInfo) {
return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.externalizableType);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 4b52a9e..0da9ead 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -95,17 +95,6 @@
this.debugInfo = debugInfo;
}
- public boolean hasDebugPositions() {
- if (debugInfo != null) {
- for (DexDebugEvent event : debugInfo.events) {
- if (event instanceof DexDebugEvent.Default) {
- return true;
- }
- }
- }
- return false;
- }
-
public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
if (debugInfo == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f2cc53f..22a771b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -401,12 +401,6 @@
}
}
- public boolean hasDebugPositions() {
- checkIfObsolete();
- assert code != null && code.isDexCode();
- return code.asDexCode().hasDebugPositions();
- }
-
public int getClassFileVersion() {
checkIfObsolete();
assert classFileVersion >= 0;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d3355b4..44a7a8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -406,7 +406,13 @@
if (other.getClass() != getClass()) {
return false;
}
- if (!identicalNonValueParts(other)) {
+ // In debug mode or if the instruction can throw we must account for positions, in release mode
+ // we do want to share non-throwing instructions even if their positions differ.
+ if (instructionTypeCanThrow() || allocator.getOptions().debug) {
+ if (!identicalNonValueParts(other)) {
+ return false;
+ }
+ } else if (!identicalNonValueNonPositionParts(other)) {
return false;
}
if (isInvokeDirect() && !asInvokeDirect().sameConstructorReceiverValue(other.asInvoke())) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index eb140c4..e6791f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -253,7 +253,8 @@
changed = true;
int otherPredIndex = blockToIndex.get(wrapper);
BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
- assert Objects.equals(pred.getPosition(), otherPred.getPosition());
+ assert !allocator.getOptions().debug
+ || Objects.equals(pred.getPosition(), otherPred.getPosition());
pred.clearCatchHandlers();
pred.getInstructions().clear();
equivalence.clearComputedHash(pred);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index 5dcc7b8..ba7963b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -151,8 +151,8 @@
MemberNaming lookupByOriginalItem(DexField field) {
for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) {
FieldSignature signature = entry.getKey();
- if (signature.name.equals(field.name.toString())
- && signature.type.equals(field.type.getName())) {
+ if (signature.name.equals(field.name.toSourceString())
+ && signature.type.equals(field.type.toSourceString())) {
return entry.getValue();
}
}
@@ -162,11 +162,11 @@
protected MemberNaming lookupByOriginalItem(DexMethod method) {
for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) {
MethodSignature signature = entry.getKey();
- if (signature.name.equals(method.name.toString())
- && signature.type.equals(method.proto.returnType.toString())
+ if (signature.name.equals(method.name.toSourceString())
+ && signature.type.equals(method.proto.returnType.toSourceString())
&& Arrays.equals(signature.parameters,
Arrays.stream(method.proto.parameters.values)
- .map(DexType::toString).toArray(String[]::new))) {
+ .map(DexType::toSourceString).toArray(String[]::new))) {
return entry.getValue();
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 25df058..bbf8259 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -22,8 +22,8 @@
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -128,42 +128,80 @@
private void applyMemberMapping(DexType from, ChainedClassNaming classNaming) {
DexClass clazz = appInfo.definitionFor(from);
- if (clazz == null) return;
+ if (clazz == null) {
+ return;
+ }
- final Set<MemberNaming> appliedMemberNaming = Sets.newIdentityHashSet();
+ // We regard mappings as _complete_ if they cover literally everything, but that's too ideal.
+ // When visiting members with member mappings, obviously, there are two incomplete cases:
+ // no matched member or no matched mapping.
+ //
+ // 1. No matched member
+ // class A { // : X
+ // void foo(); // : a
+ // }
+ //
+ // class B extends A { // : Y
+ // @Override void foo(); // no mapping
+ // }
+ //
+ // For this case, we have chained class naming and move upward to search for super class's
+ // member mapping. One corner case we should be careful here is to resolve on the correct
+ // mapping, e.g.,
+ //
+ // class B extends A { // : Y
+ // private void foo(); // no mapping, should be not renamed to a
+ // }
+
+ final Set<MemberNaming.Signature> appliedMemberSignature = new HashSet<>();
clazz.forEachField(encodedField -> {
MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
if (memberNaming != null) {
- appliedMemberNaming.add(memberNaming);
+ appliedMemberSignature.add(memberNaming.getOriginalSignature());
applyFieldMapping(encodedField.field, memberNaming);
}
});
clazz.forEachMethod(encodedMethod -> {
- MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedMethod.method);
+ MemberNaming memberNaming =
+ classNaming.lookupByOriginalItem(encodedMethod.method, encodedMethod.isPrivateMethod());
if (memberNaming != null) {
- appliedMemberNaming.add(memberNaming);
+ appliedMemberSignature.add(memberNaming.getOriginalSignature());
applyMethodMapping(encodedMethod.method, memberNaming);
}
});
+ // 2. No matched mapping
+ // class A { // : X
+ // void foo(); // : a
+ // }
+ //
+ // class B extends A { // : Y
+ // // no overriding, but has mapping: void foo() -> a
+ // }
+ //
// We need to handle a class that extends another class where some members are not overridden,
// resulting in absence of definitions. References to those members need to be redirected via
// the lense as well.
+ // The caveat is, since such members don't exist, we pretend to see their definitions.
+ // We should ensure that they indeed don't exist. Otherwise, legitimately different members,
+ // e.g., private methods with same names, could be mapped to a wrong renamed name.
classNaming.forAllFieldNaming(memberNaming -> {
- if (!appliedMemberNaming.contains(memberNaming)) {
- DexField pretendedOriginalField =
- ((FieldSignature) memberNaming.getOriginalSignature())
- .toDexField(appInfo.dexItemFactory, from);
- applyFieldMapping(pretendedOriginalField, memberNaming);
+ FieldSignature signature = (FieldSignature) memberNaming.getOriginalSignature();
+ if (!appliedMemberSignature.contains(signature)) {
+ DexField pretendedOriginalField = signature.toDexField(appInfo.dexItemFactory, from);
+ if (appInfo.definitionFor(pretendedOriginalField) == null) {
+ applyFieldMapping(pretendedOriginalField, memberNaming);
+ }
}
});
classNaming.forAllMethodNaming(memberNaming -> {
- if (!appliedMemberNaming.contains(memberNaming)) {
- DexMethod pretendedOriginalMethod =
- ((MethodSignature) memberNaming.getOriginalSignature())
- .toDexMethod(appInfo.dexItemFactory, from);
- applyMethodMapping(pretendedOriginalMethod, memberNaming);
+ MethodSignature signature = (MethodSignature) memberNaming.getOriginalSignature();
+ if (!appliedMemberSignature.contains(signature)) {
+ DexMethod pretendedOriginalMethod = signature.toDexMethod(appInfo.dexItemFactory, from);
+ if (appInfo.definitionFor(pretendedOriginalMethod) == null) {
+ applyMethodMapping(pretendedOriginalMethod, memberNaming);
+ }
}
});
}
@@ -229,6 +267,15 @@
}
}
+ protected MemberNaming lookupByOriginalItem(DexMethod method, boolean isPrivate) {
+ // If the current method is overridable, use chained mappings.
+ if (!isPrivate) {
+ return lookupByOriginalItem(method);
+ }
+ // Otherwise, just look up the current class's mappings only.
+ return super.lookupByOriginalItem(method);
+ }
+
@Override
protected MemberNaming lookupByOriginalItem(DexMethod method) {
MemberNaming memberNaming = super.lookupByOriginalItem(method);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
index ce659e1..684c79b 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
@@ -19,20 +19,20 @@
static ProguardMapError keptTypeWasRenamed(DexType type, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + type + createMessageForConflict(keptName, rename));
+ type + createMessageForConflict(keptName, rename));
}
static ProguardMapError keptMethodWasRenamed(DexMethod method, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + method.toSourceString() + createMessageForConflict(keptName, rename));
+ method.toSourceString() + createMessageForConflict(keptName, rename));
}
static ProguardMapError keptFieldWasRenamed(DexField field, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + field.toSourceString() + createMessageForConflict(keptName, rename));
+ field.toSourceString() + createMessageForConflict(keptName, rename));
}
private static String createMessageForConflict(String keptName, String rename) {
- return " is not being kept as '" + keptName + "', but remapped to '" + rename + "'";
+ return " is not being kept as " + keptName + ", but remapped to " + rename;
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 0b6a84d..3c300b6 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -12,8 +12,10 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collections;
@@ -27,12 +29,15 @@
private final AppInfoWithLiveness appInfo;
private final GraphLense lense;
+ private final InternalOptions options;
+
private final MemberRebindingLense.Builder builder;
- public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
+ public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
assert appView.graphLense().isContextFreeForMethods();
this.appInfo = appView.appInfo();
this.lense = appView.graphLense();
+ this.options = options;
this.builder = MemberRebindingLense.builder(appInfo);
}
@@ -114,8 +119,8 @@
return appInfo.resolveMethod(method.getHolder(), method).asResultOfResolve();
}
- private void computeMethodRebinding(Set<DexMethod> methods,
- Function<DexMethod, DexEncodedMethod> lookupTarget) {
+ private void computeMethodRebinding(
+ Set<DexMethod> methods, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) {
for (DexMethod method : methods) {
// We can safely ignore array types, as the corresponding methods are defined in a library.
if (!method.getHolder().isClassType()) {
@@ -130,33 +135,107 @@
// Rebind to the lowest library class or program class.
if (target != null && target.method != method) {
DexClass targetClass = appInfo.definitionFor(target.method.holder);
+
+ // In Java bytecode, it is only possible to target interface methods that are in one of
+ // the immediate super-interfaces via a super-invocation (see IndirectSuperInterfaceTest).
+ // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
+ // bridge method when we are about to rebind to an interface method that is not the
+ // original target.
+ if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
+ target =
+ insertBridgeForInterfaceMethod(
+ method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+ }
+
// If the target class is not public but the targeted method is, we might run into
// visibility problems when rebinding.
- if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) {
- // If the original class is public and this method is public, it might have been called
- // from anywhere, so we need a bridge. Likewise, if the original is in a different
- // package, we might need a bridge, too.
- String packageDescriptor =
- originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
- if (packageDescriptor == null
- || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
- DexProgramClass bridgeHolder = findBridgeMethodHolder(originalClass, targetClass,
- packageDescriptor);
- assert bridgeHolder != null;
- DexEncodedMethod bridgeMethod =
- target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
- bridgeHolder.addMethod(bridgeMethod);
- assert lookupTarget.apply(method) == bridgeMethod;
- target = bridgeMethod;
- }
+ if (mayNeedBridgeForVisibility(target, targetClass)) {
+ target =
+ insertBridgeForVisibilityIfNeeded(
+ method, target, originalClass, targetClass, lookupTarget);
}
+
builder.map(method, lense.lookupMethod(validTargetFor(target.method, method)));
}
}
}
- private DexProgramClass findBridgeMethodHolder(DexClass originalClass, DexClass targetClass,
- String packageDescriptor) {
+ private boolean needsBridgeForInterfaceMethod(
+ DexClass originalClass, DexClass targetClass, Type invokeType) {
+ return options.isGeneratingClassFiles()
+ && invokeType == Type.SUPER
+ && targetClass != originalClass
+ && targetClass.accessFlags.isInterface();
+ }
+
+ private DexEncodedMethod insertBridgeForInterfaceMethod(
+ DexMethod method,
+ DexEncodedMethod target,
+ DexProgramClass originalClass,
+ DexClass targetClass,
+ Function<DexMethod, DexEncodedMethod> lookupTarget) {
+ // If `targetClass` is a class, then insert the bridge method on the upper-most super class that
+ // implements the interface. Otherwise, if it is an interface, then insert the bridge method
+ // directly on the interface (because that interface must be the immediate super type, assuming
+ // that the super-invocation is not broken in advance).
+ //
+ // Note that, to support compiling from DEX to CF, we would need to rewrite the targets of
+ // invoke-super instructions that hit indirect interface methods such that they always target
+ // a method in an immediate super-interface, since this works on Art but not on the JVM.
+ DexProgramClass bridgeHolder =
+ findHolderForInterfaceMethodBridge(originalClass, targetClass.type);
+ assert bridgeHolder != null;
+ assert bridgeHolder != targetClass;
+ DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+ bridgeHolder.addMethod(bridgeMethod);
+ assert lookupTarget.apply(method) == bridgeMethod;
+ return bridgeMethod;
+ }
+
+ private DexProgramClass findHolderForInterfaceMethodBridge(DexProgramClass clazz, DexType iface) {
+ if (clazz.accessFlags.isInterface()) {
+ return clazz;
+ }
+ DexClass superClass = appInfo.definitionFor(clazz.superType);
+ if (superClass == null
+ || superClass.isLibraryClass()
+ || !superClass.type.isSubtypeOf(iface, appInfo)) {
+ return clazz;
+ }
+ return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
+ }
+
+ private boolean mayNeedBridgeForVisibility(DexEncodedMethod target, DexClass targetClass) {
+ return !targetClass.accessFlags.isPublic() && target.accessFlags.isPublic();
+ }
+
+ private DexEncodedMethod insertBridgeForVisibilityIfNeeded(
+ DexMethod method,
+ DexEncodedMethod target,
+ DexClass originalClass,
+ DexClass targetClass,
+ Function<DexMethod, DexEncodedMethod> lookupTarget) {
+ // If the original class is public and this method is public, it might have been called
+ // from anywhere, so we need a bridge. Likewise, if the original is in a different
+ // package, we might need a bridge, too.
+ String packageDescriptor =
+ originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
+ if (packageDescriptor == null
+ || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
+ DexProgramClass bridgeHolder =
+ findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
+ assert bridgeHolder != null;
+ DexEncodedMethod bridgeMethod =
+ target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+ bridgeHolder.addMethod(bridgeMethod);
+ assert lookupTarget.apply(method) == bridgeMethod;
+ return bridgeMethod;
+ }
+ return target;
+ }
+
+ private DexProgramClass findHolderForVisibilityBridge(
+ DexClass originalClass, DexClass targetClass, String packageDescriptor) {
if (originalClass == targetClass || originalClass.isLibraryClass()) {
return null;
}
@@ -164,12 +243,12 @@
// Recurse through supertype chain.
if (originalClass.superType.isSubtypeOf(targetClass.type, appInfo)) {
DexClass superClass = appInfo.definitionFor(originalClass.superType);
- newHolder = findBridgeMethodHolder(superClass, targetClass, packageDescriptor);
+ newHolder = findHolderForVisibilityBridge(superClass, targetClass, packageDescriptor);
} else {
for (DexType iface : originalClass.interfaces.values) {
if (iface.isSubtypeOf(targetClass.type, appInfo)) {
DexClass interfaceClass = appInfo.definitionFor(iface);
- newHolder = findBridgeMethodHolder(interfaceClass, targetClass, packageDescriptor);
+ newHolder = findHolderForVisibilityBridge(interfaceClass, targetClass, packageDescriptor);
}
}
}
@@ -234,15 +313,15 @@
public GraphLense run() {
// Virtual invokes are on classes, so use class resolution.
- computeMethodRebinding(appInfo.virtualInvokes, this::classLookup);
+ computeMethodRebinding(appInfo.virtualInvokes, this::classLookup, Type.VIRTUAL);
// Interface invokes are always on interfaces, so use interface resolution.
- computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup);
+ computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup, Type.INTERFACE);
// Super invokes can be on both kinds, decide using the holder class.
- computeMethodRebinding(appInfo.superInvokes, this::anyLookup);
+ computeMethodRebinding(appInfo.superInvokes, this::anyLookup, Type.SUPER);
// Direct invokes (private/constructor) can also be on both kinds.
- computeMethodRebinding(appInfo.directInvokes, this::anyLookup);
+ computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
// Likewise static invokes.
- computeMethodRebinding(appInfo.staticInvokes, this::anyLookup);
+ computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
computeFieldRebinding(
mergeFieldAccessContexts(appInfo.staticFieldReads, appInfo.staticFieldWrites),
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0be9f91..e0cf415 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -291,6 +291,18 @@
pinnedItems.add(item);
}
+ private void enqueueFirstNonSerializableClassInitializer(DexClass clazz, KeepReason reason) {
+ assert clazz.isProgramClass() && clazz.isSerializable(appInfo);
+ // Clime up the class hierarchy. Break out if the definition is not found, or hit the library
+ // classes, which are kept by definition, or encounter the first non-serializable class.
+ while (clazz != null && clazz.isProgramClass() && clazz.isSerializable(appInfo)) {
+ clazz = appInfo.definitionFor(clazz.superType);
+ }
+ if (clazz != null && clazz.isProgramClass() && clazz.hasDefaultInitializer()) {
+ workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+ }
+ }
+
private void enqueueHolderIfDependentNonStaticMember(
DexClass holder, Map<DexDefinition, ProguardKeepRule> dependentItems) {
// Check if any dependent members are not static, and in that case enqueue the class as well.
@@ -697,14 +709,19 @@
}
// We also need to add the corresponding <clinit> to the set of live methods, as otherwise
// static field initialization (and other class-load-time sideeffects) will not happen.
+ KeepReason reason = KeepReason.reachableFromLiveType(type);
if (!holder.isLibraryClass() && holder.hasNonTrivialClassInitializer()) {
DexEncodedMethod clinit = holder.getClassInitializer();
if (clinit != null) {
assert clinit.method.holder == holder.type;
- markDirectStaticOrConstructorMethodAsLive(clinit, KeepReason.reachableFromLiveType(type));
+ markDirectStaticOrConstructorMethodAsLive(clinit, reason);
}
}
+ if (holder.isProgramClass() && holder.isSerializable(appInfo)) {
+ enqueueFirstNonSerializableClassInitializer(holder, reason);
+ }
+
// If this type has deferred annotations, we have to process those now, too.
Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
if (annotations != null) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 66801c0..e592a9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1288,6 +1288,21 @@
return field.toTypeSubstitutedField(newSignature);
}
+
+ private void makeStatic(DexEncodedMethod method) {
+ method.accessFlags.setStatic();
+
+ Code code = method.getCode();
+ if (code.isJarCode()) {
+ MethodNode node = code.asJarCode().getNode();
+ node.access |= Opcodes.ACC_STATIC;
+ node.desc = method.method.proto.toDescriptorString();
+ } else {
+ // Due to member rebinding we may have inserted bridge methods with synthesized code.
+ // Currently, there is no easy way to make such code static.
+ abortMerge = true;
+ }
+ }
}
private static void makePrivate(DexEncodedMethod method) {
@@ -1297,17 +1312,6 @@
method.accessFlags.setPrivate();
}
- private static void makeStatic(DexEncodedMethod method) {
- method.accessFlags.setStatic();
-
- Code code = method.getCode();
- if (code.isJarCode()) {
- MethodNode node = code.asJarCode().getNode();
- node.access |= Opcodes.ACC_STATIC;
- node.desc = method.method.proto.toDescriptorString();
- }
- }
-
private DexProto getStaticProto(DexType receiverType, DexProto proto) {
DexType[] parameterTypes = new DexType[proto.parameters.size() + 1];
parameterTypes[0] = receiverType;
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index d77adfd..8fa85bf 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -65,7 +65,7 @@
} catch (ResourceException e) {
throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
} catch (AssertionError e) {
- throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()));
+ throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()), e);
}
reporter.failIfPendingErrors();
} catch (AbortException e) {
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 5a1bd06..928058e 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -108,7 +108,7 @@
}
private <T extends Throwable> T addSuppressedExceptions(T t) {
- suppressedExceptions.forEach(throwable -> t.addSuppressed(throwable));
+ suppressedExceptions.forEach(t::addSuppressed);
return t;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index d7a5732..1eddbd3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -29,7 +29,6 @@
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -77,7 +76,6 @@
assertThat(classSubject, isPresent());
}
- @Ignore("todo: jsjeon")
@Test
public void test_differentLocals() throws Throwable {
ClassSubject classSubject = inspector.clazz(MAIN);
@@ -131,7 +129,6 @@
);
}
- @Ignore("todo: jsjeon")
@Test
public void test_sameLocal() throws Throwable {
ClassSubject classSubject = inspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
index b280242..9552a61 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.memberrebinding;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8;
@@ -16,6 +15,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -24,26 +24,115 @@
@RunWith(Parameterized.class)
public class IndirectSuperInterfaceTest extends TestBase {
- public interface Interface {
+ private static final List<Class<?>> CLASSES =
+ ImmutableList.of(
+ InterfaceA.class, InterfaceASub.class, A.class,
+ InterfaceB.class, InterfaceBSub.class, B.class,
+ InterfaceC.class, InterfaceCSub.class, C.class,
+ InterfaceD.class, InterfaceDSub.class, D.class,
+ TestClass.class);
+
+ // Test A: class A extends an empty class that implements a non-empty interface.
+
+ interface InterfaceA {
@NeverInline
- default void foo() {
- System.out.print("Interface::foo ");
+ default String method() {
+ return "InterfaceA::method";
}
}
- public static class A implements Interface {
+ static class InterfaceASub implements InterfaceA {
// Intentionally empty.
}
- public static class B extends A {
+ static class A extends InterfaceASub {
@Override
- public void foo() {
- System.out.print("B::foo ");
- super.foo();
+ public String method() {
+ return "A::method -> " + super.method();
}
+ }
+
+ // Test B: class B implements an empty interface that extends a non-empty interface.
+
+ interface InterfaceB {
+ @NeverInline
+ default String method() {
+ return "InterfaceB::method";
+ }
+ }
+
+ interface InterfaceBSub extends InterfaceB {
+ // Intentionally empty.
+ }
+
+ static class B implements InterfaceBSub {
+ @Override
+ public String method() {
+ return "B::method -> " + InterfaceBSub.super.method();
+ }
+ }
+
+ // Test C: class C extends a non-empty class that implements a non-empty interface.
+
+ interface InterfaceC {
+ @NeverInline
+ default String method() {
+ return "InterfaceC::method";
+ }
+ }
+
+ static class InterfaceCSub implements InterfaceC {
+ // This method is intentionally not annotated with @NeverInline. If we were to inline this
+ // method we would risk introducing a super-invocation to InterfaceC.method() in C.method,
+ // which would lead to an IncompatibleClassChangeError on the JVM.
+ // (See also Art978_virtual_interfaceTest.)
+ @Override
+ public String method() {
+ return InterfaceC.super.method();
+ }
+ }
+
+ static class C extends InterfaceCSub {
+ @Override
+ public String method() {
+ return "C::method -> " + super.method();
+ }
+ }
+
+ // Test D: class D implements a non-empty empty interface that extends a non-empty interface.
+
+ interface InterfaceD {
+ @NeverInline
+ default String method() {
+ return "InterfaceD::method";
+ }
+ }
+
+ interface InterfaceDSub extends InterfaceD {
+ // This method is intentionally not annotated with @NeverInline. If we were to inline this
+ // method we would risk introducing a super-invocation to InterfaceC.method() in C.method,
+ // which would lead to an IncompatibleClassChangeError on the JVM.
+ // (See also Art978_virtual_interfaceTest.)
+ @Override
+ default String method() {
+ return InterfaceD.super.method();
+ }
+ }
+
+ static class D implements InterfaceDSub {
+ @Override
+ public String method() {
+ return "D::method -> " + InterfaceDSub.super.method();
+ }
+ }
+
+ static class TestClass {
public static void main(String[] args) {
- new B().foo();
+ System.out.println(new A().method());
+ System.out.println(new B().method());
+ System.out.println(new C().method());
+ System.out.print(new D().method());
}
}
@@ -51,7 +140,7 @@
@Parameters(name = "{0}")
public static Backend[] setup() {
- return new Backend[] {Backend.CF, Backend.DEX};
+ return Backend.values();
}
public IndirectSuperInterfaceTest(Backend backend) {
@@ -60,36 +149,38 @@
@Test
public void test() throws Exception {
- String expected = "B::foo Interface::foo ";
- String reference = runOnJava(B.class);
- assertEquals(expected, reference);
+ String expected =
+ String.join(
+ System.lineSeparator(),
+ "A::method -> InterfaceA::method",
+ "B::method -> InterfaceB::method",
+ "C::method -> InterfaceC::method",
+ "D::method -> InterfaceD::method");
+ assertEquals(expected, runOnJava(TestClass.class));
AndroidAppConsumers sink = new AndroidAppConsumers();
- Builder builder =
- R8Command.builder()
- .addClassProgramData(ToolHelper.getClassAsBytes(Interface.class), Origin.unknown())
- .addClassProgramData(ToolHelper.getClassAsBytes(A.class), Origin.unknown())
- .addClassProgramData(ToolHelper.getClassAsBytes(B.class), Origin.unknown())
- .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
- .addLibraryFiles(runtimeJar(backend))
- .addProguardConfiguration(
- ImmutableList.of(
- "-keep class " + Interface.class.getTypeName(),
- "-keep class " + A.class.getTypeName(),
- keepMainProguardConfigurationWithInliningAnnotation(B.class)),
- Origin.unknown());
+ Builder builder = R8Command.builder();
+ for (Class<?> clazz : CLASSES) {
+ builder.addClassProgramData(ToolHelper.getClassAsBytes(clazz), Origin.unknown());
+ }
+ builder
+ .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+ .addLibraryFiles(runtimeJar(backend))
+ .addProguardConfiguration(
+ ImmutableList.of(
+ // Keep all classes to prevent changes to the class hierarchy (e.g., due to
+ // vertical class merging).
+ "-keep class " + InterfaceA.class.getPackage().getName() + ".*",
+ keepMainProguardConfigurationWithInliningAnnotation(TestClass.class)),
+ Origin.unknown());
ToolHelper.allowTestProguardOptions(builder);
if (backend == Backend.DEX) {
builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
}
R8.run(builder.build());
- ProcessResult result = runOnVMRaw(sink.build(), B.class, backend);
-
- // TODO(b/117407667): Assert the test does not fail once fixed.
- assertTrue(result.toString(), result.exitCode == (backend == Backend.DEX ? 0 : 1));
- if (result.exitCode == 0) {
- assertEquals(reference, result.stdout);
- }
+ ProcessResult result = runOnVMRaw(sink.build(), TestClass.class, backend);
+ assertEquals(result.toString(), 0, result.exitCode);
+ assertEquals(expected, result.stdout);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
new file mode 100644
index 0000000..102a6a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
@@ -0,0 +1,230 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+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 com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MemberResolutionAsmTest extends AsmTestBase {
+ private final Backend backend;
+
+ @Parameterized.Parameters(name = "backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MemberResolutionAsmTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ // class HasMapping { // : X
+ // HasMapping() {
+ // foo();
+ // }
+ //
+ // void foo() { // : a
+ // System.out.println("HasMapping#foo");
+ // }
+ // }
+ //
+ // class NoMapping extends HasMapping { // : Y
+ // NoMapping() {
+ // super();
+ // foo();
+ // }
+ //
+ // private void foo() { // no mapping
+ // System.out.println("NoMapping#foo");
+ // }
+ // }
+ //
+ // class NoMappingMain {
+ // public static void main(String[] args) {
+ // new NoMapping();
+ // }
+ // }
+ @Test
+ public void test_noMapping() throws Exception {
+ String main = "NoMappingMain";
+ AndroidApp input = buildAndroidApp(
+ HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ "HasMapping -> X:",
+ " void foo() -> a",
+ "NoMapping -> Y:"
+ // Intentionally missing a mapping for `private` foo().
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ List<byte[]> classBytes = ImmutableList.of(
+ HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+ ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+ assertEquals(0, outputBefore.exitCode);
+ String outputAfter = runOnVM(processedApp, main, backend);
+ assertEquals(outputBefore.stdout.trim(), outputAfter.trim());
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz("HasMapping");
+ assertThat(base, isPresent());
+ assertThat(base, isRenamed());
+ assertEquals("X", base.getFinalName());
+ MethodSubject x = base.method("void", "foo", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("a", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz("NoMapping");
+ assertThat(sub, isPresent());
+ assertThat(sub, isRenamed());
+ assertEquals("Y", sub.getFinalName());
+ MethodSubject y = sub.method("void", "foo", ImmutableList.of());
+ assertThat(y, isPresent());
+ assertThat(y, not(isRenamed()));
+ assertEquals("foo", y.getFinalName());
+ }
+
+ // class A { // : X
+ // A() {
+ // x();
+ // y();
+ // }
+ //
+ // private void x() { // : y
+ // System.out.println("A#x");
+ // }
+ //
+ // public void y() { // : x
+ // System.out.println("A#y");
+ // }
+ // }
+ //
+ // class B extends A { // : Y
+ // }
+ //
+ // class Main {
+ // public static void main(String[] args) {
+ // new B().x(); // IllegalAccessError
+ // }
+ // }
+ @Test
+ public void test_swapping() throws Exception {
+ String main = "Main";
+ AndroidApp input = buildAndroidApp(
+ ADump.dump(), BDump.dump(), MainDump.dump());
+
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ "A -> X:",
+ " void x() -> y",
+ " void y() -> x",
+ "B -> Y:"
+ // Intentionally missing mappings for non-overridden members
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ List<byte[]> classBytes = ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
+ ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+ assertNotEquals(0, outputBefore.exitCode);
+ String expectedErrorMessage = "IllegalAccessError";
+ String expectedErrorSignature = "A.x()V";
+ assertThat(outputBefore.stderr, containsString(expectedErrorMessage));
+ assertThat(outputBefore.stderr, containsString(expectedErrorSignature));
+ ProcessResult outputAfter = runOnVMRaw(processedApp, main, backend);
+ assertNotEquals(0, outputAfter.exitCode);
+ expectedErrorSignature = "X.y()V";
+ if (backend == Backend.DEX) {
+ expectedErrorSignature = "void X.y()";
+ if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
+ expectedErrorMessage ="IncompatibleClassChangeError";
+ }
+ if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+ expectedErrorMessage ="illegal method access";
+ expectedErrorSignature = "LX;.y ()V";
+ }
+ }
+ assertThat(outputAfter.stderr, containsString(expectedErrorMessage));
+ assertThat(outputAfter.stderr, containsString(expectedErrorSignature));
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz("A");
+ assertThat(base, isPresent());
+ assertThat(base, isRenamed());
+ assertEquals("X", base.getFinalName());
+ MethodSubject x = base.method("void", "x", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("y", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz("B");
+ assertThat(sub, isPresent());
+ assertThat(sub, isRenamed());
+ assertEquals("Y", sub.getFinalName());
+ MethodSubject subX = sub.method("void", "x", ImmutableList.of());
+ assertThat(subX, not(isPresent()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
new file mode 100644
index 0000000..61e51be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// AbstractChecker -> X:
+abstract class AbstractChecker {
+ // String tag -> p
+ private String tag = "PrivateInitialTag_AbstractChecker";
+
+ // check() -> x
+ private void check() {
+ System.out.println("AbstractChecker#check:" + tag);
+ }
+
+ // foo() -> a
+ protected void foo() {
+ check();
+ }
+}
+
+// ConcreteChecker -> Y:
+class ConcreteChecker extends AbstractChecker {
+ // This should not be conflict with AbstractChecker#tag due to the access control.
+ // String tag -> q
+ private String tag = "PrivateInitialTag_ConcreteChecker";
+
+ ConcreteChecker(String tag){
+ this.tag = tag;
+ }
+
+ // This should not be conflict with AbstractChecker#check due to the access control.
+ // check() -> y
+ private void check() {
+ System.out.println("ConcreteChecker#check:" + tag);
+ }
+
+ // foo() -> a
+ @Override
+ protected void foo() {
+ super.foo();
+ check();
+ }
+}
+
+class MemberResolutionTestMain {
+ public static void main(String[] args) {
+ ConcreteChecker c = new ConcreteChecker("NewTag");
+ c.foo();
+ }
+}
+
+@RunWith(Parameterized.class)
+public class MemberResolutionTest extends TestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class);
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MemberResolutionTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testPrivateMethodsWithSameName() throws Exception {
+ String pkg = this.getClass().getPackage().getName();
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ pkg + ".AbstractChecker -> " + pkg + ".X:",
+ " java.lang.String tag -> p",
+ " void check() -> x",
+ " void foo() -> a",
+ pkg + ".ConcreteChecker -> " + pkg + ".Y:",
+ " java.lang.String tag -> q",
+ " void check() -> y",
+ " void foo() -> a"
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ AndroidApp app = readClasses(CLASSES);
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(MemberResolutionTestMain.class),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ String outputBefore = runOnJava(MemberResolutionTestMain.class);
+ String outputAfter = runOnVM(processedApp, MemberResolutionTestMain.class, backend);
+ assertEquals(outputBefore, outputAfter);
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz(AbstractChecker.class);
+ assertThat(base, isPresent());
+ FieldSubject p = base.field("java.lang.String", "tag");
+ assertThat(p, isPresent());
+ assertThat(p, isRenamed());
+ assertEquals("p", p.getFinalName());
+ MethodSubject x = base.method("void", "check", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("x", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz(ConcreteChecker.class);
+ assertThat(sub, isPresent());
+ FieldSubject q = sub.field("java.lang.String", "tag");
+ assertThat(q, isPresent());
+ assertThat(q, isRenamed());
+ assertEquals("q", q.getFinalName());
+ MethodSubject y = sub.method("void", "check", ImmutableList.of());
+ assertThat(y, isPresent());
+ assertThat(y, isRenamed());
+ assertEquals("y", y.getFinalName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
new file mode 100644
index 0000000..ae46c2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class HasMapping {
+// HasMapping() {
+// foo();
+// }
+//
+// void foo() {
+// System.out.println("HasMapping#foo");
+// }
+// }
+class HasMappingDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "HasMapping", null, "java/lang/Object", null);
+
+ classWriter.visitSource("HasMapping.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "HasMapping", "foo", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("HasMapping#foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class NoMapping extends HasMapping {
+// NoMapping() {
+// super();
+// bar();
+// }
+//
+// private void bar() {
+// System.out.println("NoMapping#foo");
+// }
+// }
+//
+// then renamed bar() to foo() to introduce name clash.
+class NoMappingDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "NoMapping", null, "HasMapping", null);
+
+ classWriter.visitSource("NoMapping.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(12, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "HasMapping", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(13, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "foo", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(14, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(9, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("NoMapping#foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(10, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// public class NoMappingMain {
+// public static void main(String[] args) {
+// new NoMapping();
+// }
+// }
+class NoMappingMainDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8, ACC_PUBLIC | ACC_SUPER, "NoMappingMain", null, "java/lang/Object", null);
+
+ classWriter.visitSource("NoMappingMain.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(19, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(22, label0);
+ methodVisitor.visitTypeInsn(NEW, "NoMapping");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "<init>", "()V", false);
+ methodVisitor.visitInsn(POP);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(23, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
new file mode 100644
index 0000000..0dea699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class A {
+// A() {
+// x();
+// y();
+// }
+//
+// private void x() {
+// System.out.println("A#x");
+// }
+//
+// public void y() {
+// System.out.println("A#y");
+// }
+// }
+class ADump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "x", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "A", "y", "()V", false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(5, label3);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "x", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(7, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("A#x");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(8, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "y", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(11, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("A#y");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(12, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class B extends A {
+// }
+class BDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "B", null, "A", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(15, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// public class Main {
+// public static void main(String[] args) {
+// new B().y();
+// }
+// }
+//
+// then replaced use of y() with x() to introduce IllegalAccessError.
+class MainDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(20, label0);
+ methodVisitor.visitTypeInsn(NEW, "B");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "x", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(21, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
new file mode 100644
index 0000000..353952b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.release;
+
+public class ShareCommonCodeOnDistinctPositionsTest {
+
+ public static void main(String[] args) {
+ int x;
+ int len = args.length;
+ if (len > 42) {
+ x = (len - 2) + len * 2;
+ } else {
+ x = (len - 2) + len * 2;
+ }
+ System.out.println(x);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
new file mode 100644
index 0000000..6bfd3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.release;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.LineNumberTable;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ShareCommonCodeOnDistinctPositionsTestRunner extends TestBase {
+
+ private static final Class CLASS = ShareCommonCodeOnDistinctPositionsTest.class;
+
+ @Parameters
+ public static Backend[] parameters() {
+ return Backend.values();
+ }
+
+ private final Backend backend;
+
+ public ShareCommonCodeOnDistinctPositionsTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws CompilationFailedException, IOException, ExecutionException {
+ AndroidAppConsumers sink = new AndroidAppConsumers();
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addLibraryFiles(runtimeJar(backend))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(CLASS))
+ .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+ .setDisableMinification(true)
+ .setDisableTreeShaking(true)
+ .build(),
+ options -> options.lineNumberOptimization = LineNumberOptimization.OFF);
+ CodeInspector inspector = new CodeInspector(sink.build());
+ MethodSubject method = inspector.clazz(CLASS).mainMethod();
+ // Check that the two shared lines are not in the output (they have no throwing instructions).
+ LineNumberTable lineNumberTable = method.getLineNumberTable();
+ IntCollection lines = lineNumberTable.getLines();
+ assertFalse(lines.contains(12));
+ assertFalse(lines.contains(14));
+ // Check that the two lines have been shared, e.g., there may be only one multiplication left.
+ assertEquals(
+ "Expected only one multiplcation due to instruction sharing.",
+ // TODO(b/117539423): Implement support for sharing optimizations in the CF backend.
+ backend == Backend.DEX ? 1 : 2,
+ Streams.stream(method.iterateInstructions())
+ .filter(InstructionSubject::isMultiplication)
+ .count());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index cf14d8b..4ee3011 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -74,10 +74,25 @@
protected AndroidApp runShrinker(
Shrinker mode, List<Class> programClasses, Iterable<String> proguardConfigs)
throws Exception {
- return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguardConfigs));
+ return runShrinker(
+ mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), null);
}
- protected AndroidApp runShrinker(Shrinker mode, List<Class> programClasses, String proguardConfig)
+ protected AndroidApp runShrinker(
+ Shrinker mode,
+ List<Class> programClasses,
+ Iterable<String> proguardConfigs,
+ Consumer<InternalOptions> configure)
+ throws Exception {
+ return runShrinker(
+ mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), configure);
+ }
+
+ protected AndroidApp runShrinker(
+ Shrinker mode,
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure)
throws Exception {
proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
switch (mode) {
@@ -86,42 +101,56 @@
case PROGUARD6:
return runProguard6(programClasses, proguardConfig, proguardMap);
case PROGUARD6_THEN_D8:
- return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
+ return runProguard6AndD8(programClasses, proguardConfig, proguardMap, configure);
case R8_COMPAT:
- return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.DEX);
+ return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
case R8_COMPAT_CF:
- return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.CF);
+ return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
case R8:
- return runR8(programClasses, proguardConfig, proguardMap, Backend.DEX);
+ return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
case R8_CF:
- return runR8(programClasses, proguardConfig, proguardMap, Backend.CF);
+ return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
}
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
protected CodeInspector inspectAfterShrinking(
- Shrinker mode, List<Class> programClasses, List<String> proguardConfigs) throws Exception {
- return inspectAfterShrinking(
- mode, programClasses, String.join(System.lineSeparator(), proguardConfigs));
+ Shrinker mode, List<Class> programClasses, List<String> proguardConfigs)
+ throws Exception {
+ return inspectAfterShrinking(mode, programClasses, proguardConfigs, null);
}
protected CodeInspector inspectAfterShrinking(
- Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+ Shrinker mode,
+ List<Class> programClasses,
+ List<String> proguardConfigs,
+ Consumer<InternalOptions> configure)
+ throws Exception {
+ return inspectAfterShrinking(
+ mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), configure);
+ }
+
+ protected CodeInspector inspectAfterShrinking(
+ Shrinker mode,
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure)
+ throws Exception {
switch (mode) {
case PROGUARD5:
return inspectProguard5Result(programClasses, proguardConfig);
case PROGUARD6:
return inspectProguard6Result(programClasses, proguardConfig);
case PROGUARD6_THEN_D8:
- return inspectProguard6AndD8Result(programClasses, proguardConfig);
+ return inspectProguard6AndD8Result(programClasses, proguardConfig, configure);
case R8_COMPAT:
- return inspectR8CompatResult(programClasses, proguardConfig, Backend.DEX);
+ return inspectR8CompatResult(programClasses, proguardConfig, configure, Backend.DEX);
case R8_COMPAT_CF:
- return inspectR8CompatResult(programClasses, proguardConfig, Backend.CF);
+ return inspectR8CompatResult(programClasses, proguardConfig, configure, Backend.CF);
case R8:
- return inspectR8Result(programClasses, proguardConfig, Backend.DEX);
+ return inspectR8Result(programClasses, proguardConfig, configure, Backend.DEX);
case R8_CF:
- return inspectR8Result(programClasses, proguardConfig, Backend.CF);
+ return inspectR8Result(programClasses, proguardConfig, configure, Backend.CF);
}
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
@@ -151,14 +180,19 @@
}
protected CodeInspector inspectR8Result(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return new CodeInspector(runR8(programClasses, proguardConfig, null, backend));
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend)
+ throws Exception {
+ return new CodeInspector(runR8(programClasses, proguardConfig, null, configure, backend));
}
protected AndroidApp runR8Compat(
List<Class> programClasses,
String proguardConfig,
Path proguardMap,
+ Consumer<InternalOptions> configure,
Backend backend)
throws Exception {
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
@@ -175,12 +209,15 @@
builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
}
- return ToolHelper.runR8(builder.build());
+ return ToolHelper.runR8(builder.build(), configure);
}
protected CodeInspector inspectR8CompatResult(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, backend));
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend) throws Exception {
+ return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, configure, backend));
}
protected AndroidApp runProguard5(
@@ -278,7 +315,11 @@
}
protected AndroidApp runProguard6AndD8(
- List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+ List<Class> programClasses,
+ String proguardConfig,
+ Path proguardMap,
+ Consumer<InternalOptions> configure)
+ throws Exception {
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -292,14 +333,15 @@
if (result.exitCode != 0) {
fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
}
- return ToolHelper.runD8(readJar(proguardedJar));
+ return ToolHelper.runD8(readJar(proguardedJar), configure);
}
protected CodeInspector inspectProguard6AndD8Result(
- List<Class> programClasses, String proguardConfig) throws Exception {
+ List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
+ throws Exception {
proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
return new CodeInspector(
- runProguard6AndD8(programClasses, proguardConfig, proguardMap), proguardMap);
+ runProguard6AndD8(programClasses, proguardConfig, proguardMap, configure), proguardMap);
}
protected void verifyClassesPresent(
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index e20b80a..67417e9 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -310,14 +310,7 @@
assertThat(init, isPresent());
}
- @Test
- public void testSerializable() throws Exception {
- // TODO(b/116735204): R8 should keep default ctor() of first non-serializable superclass of
- // serializable class.
- if (shrinker.isR8()) {
- return;
- }
-
+ private void testSerializable(boolean enableVerticalClassMerging) throws Exception {
String javaOutput = runOnJava(SerializableTestMain.class);
List<String> config = ImmutableList.of(
@@ -331,7 +324,9 @@
" java.lang.Object readResolve();",
"}");
- AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config);
+ AndroidApp processedApp =
+ runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config,
+ o -> o.enableVerticalClassMerging = enableVerticalClassMerging);
// TODO(b/117302947): Need to update ART binary.
if (shrinker.generatesCf()) {
String output = runOnVM(
@@ -346,9 +341,33 @@
// ...
// * Have access to the no-arg constructor of its first non-serializable superclass
CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
- ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+ ClassSubject classSubject;
+ if (shrinker.isR8() && enableVerticalClassMerging) {
+ // Vertical class merging.
+ classSubject = codeInspector.clazz(SerializableDataClass.class);
+ } else {
+ classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+ }
assertThat(classSubject, isPresent());
MethodSubject init = classSubject.init(ImmutableList.of());
assertThat(init, isPresent());
}
+
+ @Test
+ public void testSerializable_withVerticalClassMerging() throws Exception {
+ if (!shrinker.isR8()) {
+ // Already covered by the other tests.
+ return;
+ }
+ // TODO(b/117514095): Vertical class merging should preserve non/serializable behavior.
+ if (shrinker.isR8()) {
+ return;
+ }
+ testSerializable(true);
+ }
+
+ @Test
+ public void testSerializable_withoutVerticalClassMerging() throws Exception {
+ testSerializable(false);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index 777561d..a537d40 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -147,13 +147,14 @@
Class mainClass, List<Class> programClasses, String proguardConfiguration,
TriConsumer<Class, List<Class>, CodeInspector> r8Checker,
TriConsumer<Class, List<Class>, CodeInspector> proguardChecker) throws Exception {
- CodeInspector inspector = inspectR8CompatResult(programClasses, proguardConfiguration, backend);
+ CodeInspector inspector =
+ inspectR8CompatResult(programClasses, proguardConfiguration, null, backend);
r8Checker.accept(mainClass, programClasses, inspector);
if (isRunProguard()) {
inspector = inspectProguard6Result(programClasses, proguardConfiguration);
proguardChecker.accept(mainClass, programClasses, inspector);
- inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration);
+ inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration, null);
proguardChecker.accept(mainClass, programClasses, inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 89d5079..e420e1e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -45,13 +46,9 @@
return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
}
- @Override
- protected AndroidApp runR8(
- List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
- throws Exception {
+ private void configure(InternalOptions options) {
// Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
- return runR8(
- programClasses, proguardConfig, proguardMap, o -> o.enableInlining = false, backend);
+ options.enableInlining = false;
}
@Test
@@ -68,7 +65,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -97,7 +94,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -136,7 +133,7 @@
" !public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -176,7 +173,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -208,7 +205,7 @@
" !public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 5893782..3a95a6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -11,6 +11,7 @@
import static org.junit.Assert.assertThat;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -19,6 +20,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -62,8 +64,13 @@
@Override
protected CodeInspector inspectR8Result(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend)
+ throws Exception {
+ return super.inspectR8Result(
+ programClasses, adaptConfiguration(proguardConfig), configure, backend);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 25acb23..3f9c857 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.shaking.ifrule;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -43,8 +45,12 @@
@Override
protected CodeInspector inspectR8Result(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> config,
+ Backend backend) throws Exception {
+ return super.inspectR8Result(
+ programClasses, adaptConfiguration(proguardConfig), config, backend);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index 165a68f..9cada6f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -88,13 +88,6 @@
options.enableVerticalClassMerging = enableClassMerging;
}
- @Override
- protected AndroidApp runR8(
- List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
- throws Exception {
- return super.runR8(programClasses, proguardConfig, proguardMap, this::configure, backend);
- }
-
private void check(AndroidApp app) throws Exception {
CodeInspector inspector = new CodeInspector(app);
ClassSubject clazzA = inspector.clazz(A.class);
@@ -133,7 +126,7 @@
"-dontobfuscate"
);
- check(runShrinker(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config, this::configure));
}
@Test
@@ -149,7 +142,7 @@
"-dontobfuscate"
);
- check(runShrinker(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config, this::configure));
}
@Test
@@ -165,6 +158,6 @@
"-dontobfuscate"
);
- check(runShrinker(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config, this::configure));
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index 51c2c35..a2ab372 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -84,8 +84,7 @@
.add(" public static void main(java.lang.String[]);")
.add("}")
.add(additionalKeepRules);
- String config = String.join(System.lineSeparator(), builder.build());
- CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, builder.build());
inspection.accept(inspector);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index adee8cf..15e8803 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -80,8 +80,8 @@
}
@Override
- public boolean hasLineNumberTable() {
- return false;
+ public LineNumberTable getLineNumberTable() {
+ return null;
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index dedb839..067280a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstString;
@@ -212,4 +213,13 @@
public boolean isLoad() {
return instruction instanceof CfLoad;
}
+
+ @Override
+ public boolean isMultiplication() {
+ if (!(instruction instanceof CfArithmeticBinop)) {
+ return false;
+ }
+ int opcode = ((CfArithmeticBinop) instruction).getAsmOpcode();
+ return Opcodes.IMUL <= opcode && opcode <= Opcodes.DMUL;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e1d74c1..ca9ce6a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -46,6 +46,16 @@
import com.android.tools.r8.code.IputObject;
import com.android.tools.r8.code.IputShort;
import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.MulDouble;
+import com.android.tools.r8.code.MulDouble2Addr;
+import com.android.tools.r8.code.MulFloat;
+import com.android.tools.r8.code.MulFloat2Addr;
+import com.android.tools.r8.code.MulInt;
+import com.android.tools.r8.code.MulInt2Addr;
+import com.android.tools.r8.code.MulIntLit16;
+import com.android.tools.r8.code.MulIntLit8;
+import com.android.tools.r8.code.MulLong;
+import com.android.tools.r8.code.MulLong2Addr;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.PackedSwitch;
@@ -265,4 +275,18 @@
public boolean isSparseSwitch() {
return instruction instanceof SparseSwitch;
}
+
+ @Override
+ public boolean isMultiplication() {
+ return instruction instanceof MulInt
+ || instruction instanceof MulIntLit8
+ || instruction instanceof MulIntLit16
+ || instruction instanceof MulInt2Addr
+ || instruction instanceof MulFloat
+ || instruction instanceof MulFloat2Addr
+ || instruction instanceof MulLong
+ || instruction instanceof MulLong2Addr
+ || instruction instanceof MulDouble
+ || instruction instanceof MulDouble2Addr;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index f205594..f4f1660 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -6,20 +6,26 @@
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugPositionState;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.JarCode;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Arrays;
import java.util.Iterator;
-import java.util.ListIterator;
import java.util.function.Predicate;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.LineNumberNode;
public class FoundMethodSubject extends MethodSubject {
@@ -144,40 +150,6 @@
}
@Override
- public boolean hasLineNumberTable() {
- Code code = getMethod().getCode();
- if (code.isDexCode()) {
- DexCode dexCode = code.asDexCode();
- if (dexCode.getDebugInfo() != null) {
- for (DexDebugEvent event : dexCode.getDebugInfo().events) {
- if (event instanceof DexDebugEvent.Default) {
- return true;
- }
- }
- }
- return false;
- }
- if (code.isCfCode()) {
- for (CfInstruction insn : code.asCfCode().getInstructions()) {
- if (insn instanceof CfPosition) {
- return true;
- }
- }
- return false;
- }
- if (code.isJarCode()) {
- ListIterator<AbstractInsnNode> it = code.asJarCode().getNode().instructions.iterator();
- while (it.hasNext()) {
- if (it.next() instanceof LineNumberNode) {
- return true;
- }
- }
- return false;
- }
- throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
- }
-
- @Override
public boolean hasLocalVariableTable() {
Code code = getMethod().getCode();
if (code.isDexCode()) {
@@ -207,6 +179,60 @@
}
@Override
+ public LineNumberTable getLineNumberTable() {
+ Code code = getMethod().getCode();
+ if (code.isDexCode()) {
+ return getDexLineNumberTable(code.asDexCode());
+ }
+ if (code.isCfCode()) {
+ return getCfLineNumberTable(code.asCfCode());
+ }
+ if (code.isJarCode()) {
+ return getJarLineNumberTable(code.asJarCode());
+ }
+ throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+ }
+
+ private LineNumberTable getJarLineNumberTable(JarCode code) {
+ throw new Unimplemented("No support for inspecting the line number table for JarCode");
+ }
+
+ private LineNumberTable getCfLineNumberTable(CfCode code) {
+ int currentLine = -1;
+ Reference2IntMap<InstructionSubject> lineNumberTable =
+ new Reference2IntOpenHashMap<>(code.getInstructions().size());
+ for (CfInstruction insn : code.getInstructions()) {
+ if (insn instanceof CfPosition) {
+ currentLine = ((CfPosition) insn).getPosition().line;
+ }
+ if (currentLine != -1) {
+ lineNumberTable.put(new CfInstructionSubject(insn), currentLine);
+ }
+ }
+ return currentLine == -1 ? null : new LineNumberTable(lineNumberTable);
+ }
+
+ private LineNumberTable getDexLineNumberTable(DexCode code) {
+ DexDebugInfo debugInfo = code.getDebugInfo();
+ if (debugInfo == null) {
+ return null;
+ }
+ Reference2IntMap<InstructionSubject> lineNumberTable =
+ new Reference2IntOpenHashMap<>(code.instructions.length);
+ DexDebugPositionState state =
+ new DexDebugPositionState(debugInfo.startLine, getMethod().method);
+ Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
+ for (Instruction insn : code.instructions) {
+ int offset = insn.getOffset();
+ while (state.getCurrentPc() < offset && iterator.hasNext()) {
+ iterator.next().accept(state);
+ }
+ lineNumberTable.put(new DexInstructionSubject(insn), state.getCurrentLine());
+ }
+ return new LineNumberTable(lineNumberTable);
+ }
+
+ @Override
public String toString() {
return dexMethod.toSourceString();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index fb0e0d3..6890684 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -65,4 +65,6 @@
boolean isPackedSwitch();
boolean isSparseSwitch();
+
+ boolean isMultiplication();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
new file mode 100644
index 0000000..7c9c30f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+
+public class LineNumberTable {
+ private final Reference2IntMap<InstructionSubject> lineNumberTable;
+
+ public LineNumberTable(Reference2IntMap<InstructionSubject> lineNumberTable) {
+ this.lineNumberTable = lineNumberTable;
+ }
+
+ public IntCollection getLines() {
+ return lineNumberTable.values();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 55030ca..1e21552 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -33,7 +33,11 @@
return null;
}
- public abstract boolean hasLineNumberTable();
+ public boolean hasLineNumberTable() {
+ return getLineNumberTable() != null;
+ }
+
+ public abstract LineNumberTable getLineNumberTable();
public abstract boolean hasLocalVariableTable();
}