Merge commit '369a21ca5fd3338017bc72e1814a67dc7d34eed9' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index a34b8ac..d8fc991 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -119,6 +119,12 @@
return super.addClasspathResourceProvider(provider);
}
+ /** Set input proguard map used for distribution of classes in multi-dex. */
+ public Builder setProguardInputMapFile(Path proguardInputMap) {
+ getAppBuilder().setProguardMapInputData(proguardInputMap);
+ return self();
+ }
+
/**
* Indicate if compilation is to intermediate results, i.e., intended for later merging.
*
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index ac56a10..93d9058 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -29,6 +29,7 @@
"--output",
"--lib",
"--classpath",
+ "--pg-map",
MIN_API_FLAG,
"--main-dex-list",
"--main-dex-list-output",
@@ -128,6 +129,7 @@
+ "# Minimum Android API level compatibility, default: "
+ AndroidApiLevel.getDefault().getLevel()
+ ".",
+ " --pg-map <file> # Use <file> as a mapping file for distribution.",
" --intermediate # Compile an intermediate result intended for later",
" # merging.",
" --file-per-class # Produce a separate dex file per class.",
@@ -217,6 +219,8 @@
outputMode = OutputMode.DexFilePerClassFile;
} else if (arg.equals("--classfile")) {
outputMode = OutputMode.ClassFile;
+ } else if (arg.equals("--pg-map")) {
+ builder.setProguardInputMapFile(Paths.get(nextArg));
} else if (arg.equals("--output")) {
if (outputPath != null) {
builder.error(
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 87c4b30..656c8f8 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -52,6 +52,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -389,10 +390,15 @@
public void print(CfInvokeDynamic invoke) {
indent();
+ DexCallSite callSite = invoke.getCallSite();
+ DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
builder.append(opcodeName(Opcodes.INVOKEDYNAMIC)).append(' ');
- builder.append(invoke.getCallSite().methodName);
- builder.append(invoke.getCallSite().methodProto.toDescriptorString());
- DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+ builder.append(callSite.methodName);
+ builder.append(callSite.methodProto.toDescriptorString());
+ DexMethodHandle handle = callSite.bootstrapArgs.get(1).asDexValueMethodHandle().getValue();
+ builder.append(", handle:");
+ builder.append(handle.toSourceString());
+ builder.append(", itf: ").append(handle.isInterface);
builder.append(", bsm:");
appendMethod(bootstrapMethod.asMethod());
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index ca70ab0..5190adb 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -89,7 +89,9 @@
public final LazyLoadedDexApplication read(ExecutorService executorService) throws IOException {
return read(
- null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
+ inputApp.getProguardMapInputData(),
+ executorService,
+ ProgramClassCollection.defaultConflictResolver(options.reporter));
}
public final LazyLoadedDexApplication read(
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7d2dbdc..a419f4a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -219,6 +219,10 @@
return transaction.getNumberOfFields();
}
+ public int getNumberOfClasses() {
+ return transaction.getNumberOfClasses();
+ }
+
void throwIfFull(boolean hasMainDexList, Reporter reporter) {
if (!isFull()) {
return;
@@ -458,7 +462,8 @@
fillStrategy,
0,
writer.initClassLens,
- writer.namingLens)
+ writer.namingLens,
+ options)
.call();
}
}
@@ -523,7 +528,8 @@
fillStrategy,
fileIndexOffset,
writer.initClassLens,
- writer.namingLens)
+ writer.namingLens,
+ options)
.call();
}
addFeatureSplitFiles(featureSplitClasses, fillStrategy);
@@ -750,6 +756,10 @@
return methods.size() + base.getNumberOfMethods();
}
+ int getNumberOfClasses() {
+ return classes.size() + base.classes.size();
+ }
+
int getNumberOfFields() {
return fields.size() + base.getNumberOfFields();
}
@@ -787,9 +797,6 @@
&& types.isEmpty() && strings.isEmpty();
}
- int getNumberOfClasses() {
- return classes.size() + base.classes.size();
- }
}
/**
@@ -919,6 +926,7 @@
private final Map<DexProgramClass, String> originalNames;
private final DexItemFactory dexItemFactory;
private final FillStrategy fillStrategy;
+ private final InternalOptions options;
private final VirtualFileCycler cycler;
PackageSplitPopulator(
@@ -929,11 +937,13 @@
FillStrategy fillStrategy,
int fileIndexOffset,
InitClassLens initClassLens,
- NamingLens namingLens) {
+ NamingLens namingLens,
+ InternalOptions options) {
this.classes = new ArrayList<>(classes);
this.originalNames = originalNames;
this.dexItemFactory = dexItemFactory;
this.fillStrategy = fillStrategy;
+ this.options = options;
this.cycler = new VirtualFileCycler(files, initClassLens, namingLens, fileIndexOffset);
}
@@ -1005,7 +1015,7 @@
nonPackageClasses.add(clazz);
continue;
}
- if (current.isFilledEnough(fillStrategy) || current.isFull()) {
+ if (isFullEnough(current, options)) {
current.abortTransaction();
// We allow for a final rollback that has at most 20% of classes in it.
// This is a somewhat random number that was empirically chosen.
@@ -1049,6 +1059,14 @@
return newPackageAssignments;
}
+ private boolean isFullEnough(VirtualFile current, InternalOptions options) {
+ if (options.testing.limitNumberOfClassesPerDex > 0
+ && current.getNumberOfClasses() > options.testing.limitNumberOfClassesPerDex) {
+ return true;
+ }
+ return current.isFilledEnough(fillStrategy) || current.isFull();
+ }
+
private void addNonPackageClasses(
VirtualFileCycler cycler, List<DexProgramClass> nonPackageClasses) {
cycler.restart();
@@ -1076,9 +1094,8 @@
private VirtualFile getVirtualFile(VirtualFileCycler cycler) {
VirtualFile current = null;
- while (cycler.hasNext()
- && (current = cycler.next()).isFilledEnough(fillStrategy)) {}
- if (current == null || current.isFilledEnough(fillStrategy)) {
+ while (cycler.hasNext() && isFullEnough(current = cycler.next(), options)) {}
+ if (current == null || isFullEnough(current, options)) {
current = cycler.addFile();
}
return current;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index bb3405a..f166e3c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -189,11 +189,12 @@
return;
}
startLine = position.line;
- emittedPosition = new Position(position.line, null, method.method, null);
+ emittedPosition =
+ new Position(position.line, null, position.getOutermostCaller().method, null);
}
assert emittedPc != pc;
int previousPc = emittedPc == NO_PC_INFO ? 0 : emittedPc;
- emitAdvancementEvents(previousPc, emittedPosition, pc, position, events, factory);
+ emitAdvancementEvents(previousPc, emittedPosition, pc, position, events, factory, false);
emittedPc = pc;
emittedPosition = position;
if (localsChanged()) {
@@ -225,7 +226,8 @@
int nextPc,
Position nextPosition,
List<DexDebugEvent> events,
- DexItemFactory factory) {
+ DexItemFactory factory,
+ boolean optimizingLineNumbers) {
assert previousPc >= 0;
int pcDelta = nextPc - previousPc;
assert !previousPosition.isNone() || nextPosition.isNone();
@@ -235,8 +237,14 @@
if (nextPosition.file != previousPosition.file) {
events.add(factory.createSetFile(nextPosition.file));
}
- if (nextPosition.method != previousPosition.method
- || nextPosition.callerPosition != previousPosition.callerPosition) {
+ // The LineNumberOptimizer maps new positions based on the outer most caller with
+ // callerPosition == null.
+ assert null != nextPosition.callerPosition
+ || null != previousPosition.callerPosition
+ || nextPosition.method == previousPosition.method
+ || optimizingLineNumbers;
+ if (nextPosition.callerPosition != previousPosition.callerPosition
+ || nextPosition.method != previousPosition.method) {
events.add(factory.createSetInlineFrame(nextPosition.method, nextPosition.callerPosition));
}
if (lineDelta < Constants.DBG_LINE_BASE
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 b087d77..ff7e38c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -36,7 +36,6 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.ValueType;
@@ -873,37 +872,7 @@
cfCode.addFakeThisParameter(appView.dexItemFactory());
}
}
-
- public static void setOriginalMethodPosition(Code code, DexMethod originalMethod) {
- if (code.isDexCode()) {
- DexCode dexCode = code.asDexCode();
- DexDebugInfo debugInfo = dexCode.getDebugInfo();
- if (debugInfo == null) {
- return;
- }
- for (DexDebugEvent event : debugInfo.events) {
- if (event.isSetInlineFrame() && event.asSetInlineFrame().hasOuterPosition(originalMethod)) {
- return;
- }
- }
- DexDebugEvent[] newEvents = new DexDebugEvent[debugInfo.events.length + 1];
- newEvents[0] = new SetInlineFrame(originalMethod, null);
- System.arraycopy(debugInfo.events, 0, newEvents, 1, debugInfo.events.length);
- dexCode.setDebugInfo(new DexDebugInfo(debugInfo.startLine, debugInfo.parameters, newEvents));
- } else {
- assert code.isCfCode();
- CfCode cfCode = code.asCfCode();
- for (CfInstruction instruction : cfCode.instructions) {
- if (instruction.isPosition()) {
- assert instruction.asPosition().getPosition().getOutermostCaller().method
- == originalMethod;
- return;
- }
- }
- assert false : "The original position should be present in the CF code.";
- }
- }
-
+
private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
checkIfObsolete();
Signature signature = MethodSignature.fromDexMethod(method);
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index aa1d5a9..2cf3328 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -51,6 +51,7 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -1760,6 +1761,71 @@
return canonicalize(strings, new DexString(source));
}
+ public static String escapeMemberString(String str) {
+ return str.replace('.', '$');
+ }
+
+ public DexString createFreshMemberString(String baseName, DexType holder, int index) {
+ StringBuilder sb =
+ new StringBuilder()
+ .append(baseName)
+ .append('$')
+ .append(escapeMemberString(holder.toSourceString()));
+
+ if (index > 0) {
+ sb.append("$").append(index);
+ }
+
+ return createString(sb.toString());
+ }
+
+ /**
+ * Find a fresh method name that is not used by any other method. The method name takes the form
+ * "basename$holdername$index".
+ *
+ * @param tryString callback to check if the method name is in use.
+ */
+ public <T extends DexMember<?, ?>> T createFreshMember(
+ String baseName, DexType holder, Function<DexString, Optional<T>> tryString) {
+ int index = 0;
+ while (true) {
+ DexString name = createFreshMemberString(baseName, holder, index++);
+ Optional<T> result = tryString.apply(name);
+ if (result.isPresent()) {
+ return result.get();
+ }
+ }
+ }
+
+ /**
+ * Tries to find a method name for insertion into the class {@code target} of the form
+ * baseName$holder$n, where {@code baseName} and {@code holder} are supplied by the user, and
+ * {@code n} is picked to be the first number so that {@code isFresh.apply(method)} returns {@code
+ * true}.
+ *
+ * @param holder indicates where the method originates from.
+ */
+ public DexMethod createFreshMethodName(
+ String baseName,
+ DexType holder,
+ DexProto proto,
+ DexType target,
+ Predicate<DexMethod> isFresh) {
+ DexMethod method =
+ createFreshMember(
+ baseName,
+ holder,
+ name -> {
+ DexMethod tryMethod = createMethod(target, proto, name);
+ if (isFresh.test(tryMethod)) {
+ return Optional.of(tryMethod);
+ } else {
+ return Optional.empty();
+ }
+ });
+ return method;
+ }
+
public DexString lookupString(int size, byte[] content) {
return strings.get(new DexString(size, content));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index a4be032..6c37bc9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -206,10 +206,7 @@
MethodHandleType type,
DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
boolean isInterface) {
- this.type = type;
- this.fieldOrMethod = fieldOrMethod;
- this.isInterface = isInterface;
- this.rewrittenTarget = null;
+ this(type, fieldOrMethod, isInterface, null);
}
public DexMethodHandle(
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 08e5190..d803a42 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,12 +73,13 @@
return classpathClasses;
}
- public DexDefinitionSupplier getDefinitionsSupplier(SyntheticItems syntheticItems) {
+ public DexDefinitionSupplier getDefinitionsSupplier(
+ SyntheticDefinitionsProvider syntheticDefinitionsProvider) {
DirectMappedDexApplication self = this;
return new DexDefinitionSupplier() {
@Override
public DexClass definitionFor(DexType type) {
- return syntheticItems.definitionFor(type, self::definitionFor);
+ return syntheticDefinitionsProvider.definitionFor(type, self::definitionFor);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 2c50bf8..2828097 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -7,12 +7,18 @@
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.ExtraParameter;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.Ordering;
import it.unimi.dsi.fastutil.ints.Int2ReferenceRBTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.function.Consumer;
public class RewrittenPrototypeDescription {
@@ -325,20 +331,20 @@
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
- private final int extraUnusedNullParameters;
+ private final List<ExtraParameter> extraParameters;
private final ArgumentInfoCollection argumentInfoCollection;
private final RewrittenTypeInfo rewrittenReturnInfo;
private RewrittenPrototypeDescription() {
- this(0, null, ArgumentInfoCollection.empty());
+ this(Collections.emptyList(), null, ArgumentInfoCollection.empty());
}
private RewrittenPrototypeDescription(
- int extraUnusedNullParameters,
+ List<ExtraParameter> extraParameters,
RewrittenTypeInfo rewrittenReturnInfo,
ArgumentInfoCollection argumentsInfo) {
assert argumentsInfo != null;
- this.extraUnusedNullParameters = extraUnusedNullParameters;
+ this.extraParameters = extraParameters;
this.rewrittenReturnInfo = rewrittenReturnInfo;
this.argumentInfoCollection = argumentsInfo;
}
@@ -350,12 +356,14 @@
DexType returnType = method.proto.returnType;
RewrittenTypeInfo returnInfo =
returnType.isAlwaysNull(appView) ? RewrittenTypeInfo.toVoid(returnType, appView) : null;
- return new RewrittenPrototypeDescription(0, returnInfo, removedArgumentsInfo);
+ return new RewrittenPrototypeDescription(
+ Collections.emptyList(), returnInfo, removedArgumentsInfo);
}
public static RewrittenPrototypeDescription createForRewrittenTypes(
RewrittenTypeInfo returnInfo, ArgumentInfoCollection rewrittenArgumentsInfo) {
- return new RewrittenPrototypeDescription(0, returnInfo, rewrittenArgumentsInfo);
+ return new RewrittenPrototypeDescription(
+ Collections.emptyList(), returnInfo, rewrittenArgumentsInfo);
}
public static RewrittenPrototypeDescription none() {
@@ -363,13 +371,17 @@
}
public boolean isEmpty() {
- return extraUnusedNullParameters == 0
+ return extraParameters.isEmpty()
&& rewrittenReturnInfo == null
&& argumentInfoCollection.isEmpty();
}
- public int numberOfExtraUnusedNullParameters() {
- return extraUnusedNullParameters;
+ public Collection<ExtraParameter> getExtraParameters() {
+ return extraParameters;
+ }
+
+ public int numberOfExtraParameters() {
+ return extraParameters.size();
}
public boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
@@ -420,7 +432,7 @@
assert rewrittenReturnInfo == null;
return !hasBeenChangedToReturnVoid(appView)
? new RewrittenPrototypeDescription(
- extraUnusedNullParameters,
+ extraParameters,
RewrittenTypeInfo.toVoid(oldReturnType, appView),
argumentInfoCollection)
: this;
@@ -428,7 +440,7 @@
public RewrittenPrototypeDescription withRemovedArguments(ArgumentInfoCollection other) {
return new RewrittenPrototypeDescription(
- extraUnusedNullParameters, rewrittenReturnInfo, argumentInfoCollection.combine(other));
+ extraParameters, rewrittenReturnInfo, argumentInfoCollection.combine(other));
}
public RewrittenPrototypeDescription withExtraUnusedNullParameter() {
@@ -437,12 +449,23 @@
public RewrittenPrototypeDescription withExtraUnusedNullParameters(
int numberOfExtraUnusedNullParameters) {
- if (numberOfExtraUnusedNullParameters == 0) {
+ List<ExtraParameter> parameters =
+ Collections.nCopies(numberOfExtraUnusedNullParameters, new ExtraUnusedNullParameter());
+ return withExtraParameters(parameters);
+ }
+
+ public RewrittenPrototypeDescription withExtraParameter(ExtraParameter parameter) {
+ return withExtraParameters(Collections.singletonList(parameter));
+ }
+
+ public RewrittenPrototypeDescription withExtraParameters(List<ExtraParameter> parameters) {
+ if (parameters.isEmpty()) {
return this;
}
+ List<ExtraParameter> newExtraParameters = new ArrayList<>();
+ newExtraParameters.addAll(extraParameters);
+ newExtraParameters.addAll(parameters);
return new RewrittenPrototypeDescription(
- extraUnusedNullParameters + numberOfExtraUnusedNullParameters,
- rewrittenReturnInfo,
- argumentInfoCollection);
+ newExtraParameters, rewrittenReturnInfo, argumentInfoCollection);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/SyntheticDefinitionsProvider.java b/src/main/java/com/android/tools/r8/graph/SyntheticDefinitionsProvider.java
new file mode 100644
index 0000000..82e37ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SyntheticDefinitionsProvider.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import java.util.function.Function;
+
+public interface SyntheticDefinitionsProvider {
+ DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
index 20c073a..3df048f 100644
--- a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
@@ -11,9 +11,9 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
-public class SyntheticItems {
+public class SyntheticItems implements SyntheticDefinitionsProvider {
- public static class CommittedItems {
+ public static class CommittedItems implements SyntheticDefinitionsProvider {
// Set of all types that represent synthesized items.
private final ImmutableSet<DexType> syntheticTypes;
@@ -24,6 +24,11 @@
SyntheticItems toSyntheticItems() {
return new SyntheticItems(syntheticTypes);
}
+
+ @Override
+ public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
+ return baseDefinitionFor.apply(type);
+ }
}
// Thread safe collection of synthesized classes that are not yet committed to the application.
@@ -48,6 +53,7 @@
return Collections.unmodifiableCollection(pendingClasses.values());
}
+ @Override
public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
DexProgramClass pending = pendingClasses.get(type);
if (pending != null) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
new file mode 100644
index 0000000..b182afb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.utils.IntBox;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Generate code of the form: <code>
+ * MyClass(int constructorId, [args]) {
+ * switch (constructorId) {
+ * case 0:
+ * this.Constructor$A([args]);
+ * return;
+ * case 1:
+ * this.Constructor$B([args]);
+ * return;
+ * ...
+ * default:
+ * throw null;
+ * }
+ * }
+ * </code>
+ */
+public class ConstructorEntryPoint extends SyntheticSourceCode {
+ private final Collection<DexMethod> typeConstructors;
+
+ public ConstructorEntryPoint(
+ Collection<DexMethod> typeConstructors, DexMethod method, Position callerPosition) {
+ super(method.holder, method, callerPosition);
+
+ this.typeConstructors = typeConstructors;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ int typeConstructorCount = typeConstructors.size();
+ int idRegister = getParamRegister(0);
+
+ int[] keys = new int[typeConstructorCount];
+ int[] offsets = new int[typeConstructorCount];
+ IntBox fallthrough = new IntBox();
+ int switchIndex = lastInstructionIndex();
+ add(
+ builder -> builder.addSwitch(idRegister, keys, fallthrough.get(), offsets),
+ builder -> endsSwitch(builder, switchIndex, fallthrough.get(), offsets));
+
+ fallthrough.set(nextInstructionIndex());
+ int nullRegister = nextRegister(ValueType.OBJECT);
+ add(builder -> builder.addNullConst(nullRegister));
+ add(builder -> builder.addThrow(nullRegister), endsBlock);
+
+ int index = 0;
+ for (DexMethod typeConstructor : typeConstructors) {
+ keys[index] = index;
+ offsets[index] = nextInstructionIndex();
+
+ add(
+ builder -> {
+ List<Value> arguments = new ArrayList<>(typeConstructor.getArity());
+ arguments.add(builder.getReceiverValue());
+ int paramIndex = 0;
+ for (Value argument : builder.getArgumentValues()) {
+ if (paramIndex++ >= typeConstructor.getArity()) {
+ break;
+ }
+ arguments.add(argument);
+ }
+ builder.addInvoke(
+ Type.DIRECT, typeConstructor, typeConstructor.proto, arguments, false);
+ });
+
+ add(IRBuilder::addReturn, endsBlock);
+
+ index++;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
new file mode 100644
index 0000000..d7e942d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, 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.horizontalclassmerging;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConstructorMerger {
+ private final AppView<?> appView;
+ private final DexProgramClass target;
+ private final Collection<DexEncodedMethod> constructors;
+ private final DexItemFactory dexItemFactory;
+
+ ConstructorMerger(
+ AppView<?> appView, DexProgramClass target, Collection<DexEncodedMethod> constructors) {
+ this.appView = appView;
+ this.target = target;
+ this.constructors = constructors;
+
+ // Constructors should not be empty and all constructors should have the same prototype.
+ assert !constructors.isEmpty();
+ assert constructors.stream().map(constructor -> constructor.proto()).distinct().count() == 1;
+
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ private DexMethod moveConstructor(DexEncodedMethod constructor) {
+ DexMethod method =
+ dexItemFactory.createFreshMethodName(
+ "constructor",
+ constructor.holder(),
+ constructor.proto(),
+ target.type,
+ tryMethod -> target.lookupMethod(tryMethod) == null);
+
+ if (constructor.holder() == target.type) {
+ target.removeMethod(constructor.toReference());
+ }
+
+ target.addDirectMethod(constructor.toTypeSubstitutedMethod(method));
+ return method;
+ }
+
+ private DexProto getNewConstructorProto() {
+ DexEncodedMethod firstConstructor = constructors.stream().findFirst().get();
+ DexProto oldProto = firstConstructor.getProto();
+
+ List<DexType> parameters = new ArrayList<>();
+ Collections.addAll(parameters, oldProto.parameters.values);
+ parameters.add(dexItemFactory.intType);
+ // TODO(b/165783587): add synthesised class to prevent constructor merge conflict
+ return dexItemFactory.createProto(oldProto.returnType, parameters);
+ }
+
+ private MethodAccessFlags getAccessFlags() {
+ // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
+ return MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, true);
+ }
+
+ public void merge() {
+ Map<DexType, DexMethod> typeConstructors = new IdentityHashMap<>();
+
+ for (DexEncodedMethod constructor : constructors) {
+ typeConstructors.put(constructor.holder(), moveConstructor(constructor));
+ }
+
+ DexProto newProto = getNewConstructorProto();
+
+ DexMethod newConstructor =
+ appView.dexItemFactory().createMethod(target.type, newProto, dexItemFactory.initMethodName);
+ SynthesizedCode synthesizedCode =
+ new SynthesizedCode(
+ callerPosition ->
+ new ConstructorEntryPoint(
+ typeConstructors.values(), newConstructor, callerPosition));
+ DexEncodedMethod newMethod =
+ new DexEncodedMethod(
+ newConstructor,
+ getAccessFlags(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ synthesizedCode);
+
+ target.addDirectMethod(newMethod);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 6583c9b..d242202 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -31,6 +31,10 @@
return singleNumberValues.computeIfAbsent(value, SingleNumberValue::new);
}
+ public SingleNumberValue createNullValue() {
+ return createSingleNumberValue(0);
+ }
+
public SingleStringValue createSingleStringValue(DexString string) {
return singleStringValues.computeIfAbsent(string, SingleStringValue::new);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 68447db..e3fa34e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -371,8 +371,10 @@
}
});
} else {
- // We replaced a dead, non-throwing instruction by a throwing instruction. Since this is
- // dead code, we don't need to worry about the catch handlers of the `throwBlock`.
+ // This is dead code and does not need to be guarded by catch handlers, except in the case
+ // of locking, which should be statically verifiable. Currently we copy over the handlers of
+ // the block in any case which is sound in all cases.
+ throwBlock.copyCatchHandlers(code, blockIterator, block, appView.options());
}
}
}
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 b287f84..1334a46 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
@@ -86,6 +86,12 @@
this.position = position;
}
+ public void forceOverwritePosition(Position position) {
+ assert position != null;
+ assert this.position != null;
+ this.position = position;
+ }
+
public String getPositionAsString() {
return position == null ? "???" : position.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
new file mode 100644
index 0000000..d698e7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, 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.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+
+public class ExtraConstantIntParameter extends ExtraParameter {
+ long value;
+
+ public ExtraConstantIntParameter(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public TypeElement getTypeElement(AppView<?> appView, DexType argType) {
+ assert argType == appView.dexItemFactory().intType;
+ return TypeElement.getInt();
+ }
+
+ @Override
+ public SingleNumberValue getValue(AppView<?> appView) {
+ return appView.abstractValueFactory().createSingleNumberValue(value);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
new file mode 100644
index 0000000..93f52df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, 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.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+
+public abstract class ExtraParameter {
+ public abstract TypeElement getTypeElement(AppView<?> appView, DexType argType);
+
+ public abstract SingleNumberValue getValue(AppView<?> appView);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
new file mode 100644
index 0000000..f478661
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, 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.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+
+public class ExtraUnusedNullParameter extends ExtraParameter {
+
+ @Override
+ public TypeElement getTypeElement(AppView<?> appView, DexType argType) {
+ return TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+ }
+
+ @Override
+ public SingleNumberValue getValue(AppView<?> appView) {
+ return appView.abstractValueFactory().createNullValue();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index dfce72e..806917c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -542,7 +542,7 @@
method.method.proto.parameters.values.length
+ argumentsInfo.numberOfRemovedArguments()
+ (method.isStatic() ? 0 : 1)
- - prototypeChanges.numberOfExtraUnusedNullParameters();
+ - prototypeChanges.numberOfExtraParameters();
int usedArgumentIndex = 0;
while (argumentIndex < originalNumberOfArguments) {
@@ -581,13 +581,17 @@
argumentIndex++;
}
- for (int i = 0; i < prototypeChanges.numberOfExtraUnusedNullParameters(); i++) {
+ for (ExtraParameter extraParameter : prototypeChanges.getExtraParameters()) {
DexType argType = method.method.proto.getParameter(usedArgumentIndex);
- assert argType.isClassType();
- TypeElement type = TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+ TypeElement type = extraParameter.getTypeElement(appView, argType);
register += type.requiredRegisters();
usedArgumentIndex++;
- addExtraUnusedNullArgument(register);
+ if (extraParameter instanceof ExtraUnusedNullParameter) {
+ assert argType.isClassType();
+ addExtraUnusedNullArgument(register);
+ } else {
+ addNonThisArgument(register, type);
+ }
}
flushArgumentInstructions();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 3e59e47..bd8ba8d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -59,6 +59,7 @@
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.CheckCast;
@@ -83,9 +84,11 @@
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
@@ -93,6 +96,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -284,14 +288,40 @@
? null
: makeOutValue(invoke, code);
- if (prototypeChanges.numberOfExtraUnusedNullParameters() > 0) {
- iterator.previous();
- Value nullInstruction =
- iterator.insertConstNullInstruction(code, appView.options());
- iterator.next();
- for (int i = 0; i < prototypeChanges.numberOfExtraUnusedNullParameters(); i++) {
- newInValues.add(nullInstruction);
- }
+ Map<SingleNumberValue, Map<DexType, Value>> parameterMap = new IdentityHashMap<>();
+
+ int parameterIndex = newInValues.size() - (actualInvokeType == STATIC ? 0 : 1);
+ for (ExtraParameter parameter : prototypeChanges.getExtraParameters()) {
+ DexType type = actualTarget.proto.getParameter(parameterIndex++);
+
+ SingleNumberValue numberValue = parameter.getValue(appView);
+
+ // Try to find an existing constant instruction, otherwise generate a new one.
+ Value value =
+ parameterMap
+ .computeIfAbsent(numberValue, ignore -> new IdentityHashMap<>())
+ .computeIfAbsent(
+ type,
+ ignore -> {
+ iterator.previous();
+ Instruction instruction =
+ numberValue.createMaterializingInstruction(
+ appView,
+ code,
+ TypeAndLocalInfoSupplier.create(
+ parameter.getTypeElement(appView, type), null));
+ assert !instruction.instructionTypeCanThrow();
+ instruction.setPosition(
+ appView.options().debug
+ ? invoke.getPosition()
+ : Position.none());
+ iterator.add(instruction);
+ iterator.next();
+ return instruction.outValue();
+ });
+
+ newInValues.add(value);
+
// TODO(b/164901008): Fix when the number of arguments overflows.
if (newInValues.size() > 255) {
throw new CompilationError(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 0b653f7..5a50c02 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.desugar;
-import static com.android.tools.r8.graph.DexEncodedMethod.setOriginalMethodPosition;
-
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.Instruction;
@@ -96,12 +94,6 @@
newFlags.promoteToStatic();
DexEncodedMethod.setDebugInfoWithFakeThisParameter(
code, companionMethod.getArity(), appView);
- if (!appView.options().isDesugaredLibraryCompilation()) {
- setOriginalMethodPosition(
- code, appView.graphLens().getOriginalMethodSignature(virtual.method));
- } else {
- assert appView.graphLens().isIdentityLens();
- }
DexEncodedMethod implMethod =
new DexEncodedMethod(
companionMethod,
@@ -142,12 +134,6 @@
: "Static interface method " + direct.toSourceString() + " is expected to "
+ "either be public or private in " + iface.origin;
DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
- if (!appView.options().isDesugaredLibraryCompilation()) {
- setOriginalMethodPosition(
- direct.getCode(), appView.graphLens().getOriginalMethodSignature(oldMethod));
- } else {
- assert appView.graphLens().isIdentityLens();
- }
DexEncodedMethod implMethod =
new DexEncodedMethod(
companionMethod,
@@ -175,12 +161,6 @@
}
DexEncodedMethod.setDebugInfoWithFakeThisParameter(
code, companionMethod.getArity(), appView);
- if (!appView.options().isDesugaredLibraryCompilation()) {
- setOriginalMethodPosition(
- code, appView.graphLens().getOriginalMethodSignature(oldMethod));
- } else {
- assert appView.graphLens().isIdentityLens();
- }
DexEncodedMethod implMethod =
new DexEncodedMethod(
companionMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index e40e0e4..605397a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -365,20 +366,22 @@
assert implHandle != null;
DexMethod implMethod = implHandle.asMethod();
- // Lambda$ method. We must always find it.
+ // Lambda$ method. We should always find it. If not found an ICCE can be expected to be thrown.
assert implMethod.holder == accessedFrom.getHolderType();
assert descriptor.verifyTargetFoundInClass(accessedFrom.getHolderType());
if (implHandle.type.isInvokeStatic()) {
- SingleResolutionResult resolution =
- appView
- .appInfoForDesugaring()
- .resolveMethod(implMethod, implHandle.isInterface)
- .asSingleResolution();
- assert resolution.getResolvedMethod().isStatic();
- assert resolution.getResolvedHolder().isProgramClass();
+ ResolutionResult resolution =
+ appView.appInfoForDesugaring().resolveMethod(implMethod, implHandle.isInterface);
+ if (resolution.isFailedResolution()) {
+ return new InvalidLambdaImplTarget(
+ implMethod, Type.STATIC, appView.dexItemFactory().icceType);
+ }
+ SingleResolutionResult result = resolution.asSingleResolution();
+ assert result.getResolvedMethod().isStatic();
+ assert result.getResolvedHolder().isProgramClass();
return new StaticLambdaImplTarget(
new ProgramMethod(
- resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod()));
+ result.getResolvedHolder().asProgramClass(), result.getResolvedMethod()));
}
assert implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
@@ -640,6 +643,21 @@
}
}
+ class InvalidLambdaImplTarget extends Target {
+
+ final DexType exceptionType;
+
+ public InvalidLambdaImplTarget(DexMethod callTarget, Type invokeType, DexType exceptionType) {
+ super(callTarget, invokeType);
+ this.exceptionType = exceptionType;
+ }
+
+ @Override
+ ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+ return null;
+ }
+ }
+
// Used for instance private lambda$ methods which need to be converted to public methods.
private class InstanceLambdaImplTarget extends Target {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index a5cf917..0d996d5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.desugar.LambdaClass.InvalidLambdaImplTarget;
+import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -166,6 +168,15 @@
DexType enforcedReturnType = descriptor().enforcedProto.returnType;
LambdaClass.Target target = lambda.target;
+ if (target instanceof InvalidLambdaImplTarget) {
+ add(
+ builder -> {
+ InvalidLambdaImplTarget invalidTarget = (InvalidLambdaImplTarget) target;
+ ExceptionThrowingSourceCode.build(builder, invalidTarget.exceptionType);
+ });
+ return;
+ }
+
DexMethod methodToCall = target.callTarget;
// Only constructor call should use direct invoke type since super
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index 2669724..e7c0b77 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -22,6 +22,7 @@
private final DexType nestConstructorType;
private final Map<DexField, DexMethod> getFieldMap;
private final Map<DexField, DexMethod> putFieldMap;
+ private final AppView<?> appView;
NestedPrivateMethodLens(
AppView<?> appView,
@@ -42,6 +43,7 @@
assert methodMap instanceof IdentityHashMap;
assert getFieldMap instanceof IdentityHashMap;
assert putFieldMap instanceof IdentityHashMap;
+ this.appView = appView;
this.nestConstructorType = nestConstructorType;
this.getFieldMap = getFieldMap;
this.putFieldMap = putFieldMap;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
index 8eaf454..fc48072 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
@@ -19,7 +19,8 @@
public class BasicBlockMuncher {
private static List<BasicBlockPeephole> nonDestructivePeepholes() {
- return ImmutableList.of(new MoveLoadUpPeephole(), new StoreLoadPeephole());
+ return ImmutableList.of(
+ new RemoveDebugPositionPeephole(), new MoveLoadUpPeephole(), new StoreLoadPeephole());
}
// The StoreLoadPeephole and StoreSequenceLoadPeephole are non-destructive but we would like it
@@ -48,13 +49,7 @@
new LinearFlowInstructionListIterator(
code, currentBlock, currentBlock.getInstructions().size());
boolean matched = false;
- while (matched || it.hasPrevious()) {
- if (!it.hasPrevious()) {
- matched = false;
- it =
- new LinearFlowInstructionListIterator(
- code, currentBlock, currentBlock.getInstructions().size());
- }
+ while (true) {
for (BasicBlockPeephole peepHole : peepholes) {
boolean localMatch = peepHole.match(it);
if (localMatch && peepHole.resetAfterMatch()) {
@@ -73,6 +68,13 @@
iterations++;
}
it.previous();
+ } else if (matched) {
+ matched = false;
+ it =
+ new LinearFlowInstructionListIterator(
+ code, currentBlock, currentBlock.getInstructions().size());
+ } else {
+ break;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
index d656455..5fef766 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
@@ -20,10 +20,12 @@
&& (t.outValue() == null || !t.outValue().hasLocalInfo());
}
- public static void resetNext(InstructionListIterator it, int count) {
+ public static Instruction resetNext(InstructionListIterator it, int count) {
+ Instruction instruction = null;
for (int i = 0; i < count; i++) {
- it.previous();
+ instruction = it.previous();
}
+ return instruction;
}
public static void resetPrevious(InstructionListIterator it, int count) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/RemoveDebugPositionPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/RemoveDebugPositionPeephole.java
new file mode 100644
index 0000000..99128d0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/RemoveDebugPositionPeephole.java
@@ -0,0 +1,94 @@
+// 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.ir.optimize.peepholes;
+
+import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Position;
+
+/**
+ * {@link RemoveDebugPositionPeephole} looks for the following two patterns:
+ *
+ * <pre>
+ * p: DebugPosition
+ * [q: Const]*
+ * q: Instr // TODO(b/166074600): This must currently be a Const.
+ * </pre>
+ *
+ * if p = q:
+ *
+ * <pre>
+ * [q: Const]*
+ * q: Instr
+ * </pre>
+ *
+ * if p != q and size([q: Const]*) > 0:
+ *
+ * <pre>
+ * [p: Const]*
+ * q: Instr
+ * </pre>
+ *
+ * This rewrite will eliminate debug positions that can be placed on a constant, retaining the line
+ * but avoiding the nop resulting in a remaining position instruction.
+ */
+public class RemoveDebugPositionPeephole implements BasicBlockPeephole {
+
+ private final Point debugPositionExp = new Point(Instruction::isDebugPosition);
+ private final Point secondInstructionExp =
+ new Point(
+ // TODO(b/166074600): It should be possible to match on any materializing instruction
+ // here. The phi-optimization seems to invalidate that by changing stack operations.
+ Instruction::isConstInstruction);
+
+ private final PeepholeLayout layout =
+ PeepholeLayout.lookForward(debugPositionExp, secondInstructionExp);
+
+ @Override
+ public boolean match(InstructionListIterator it) {
+ Match match = layout.test(it);
+ if (match == null) {
+ return false;
+ }
+ DebugPosition debugPosition = debugPositionExp.get(match).asDebugPosition();
+ Instruction secondInstruction = secondInstructionExp.get(match);
+
+ // If the position is the same on the following instruction it can simply be removed.
+ Position position = debugPosition.getPosition();
+ if (position.equals(secondInstruction.getPosition())) {
+ it.removeOrReplaceByDebugLocalRead();
+ return true;
+ }
+
+ boolean movedPosition = false;
+ it.next(); // skip debug position.
+ Instruction current = it.next(); // start loop at second instruction.
+ assert current == secondInstruction;
+ while (current.isConstInstruction() && it.hasNext()) {
+ Instruction next = it.next();
+ if (!next.getPosition().equals(current.getPosition())) {
+ break;
+ }
+ // The constant shares position with the next instruction so it subsumes the position of
+ // the debug position.
+ movedPosition = true;
+ current.forceOverwritePosition(position);
+ current = next;
+ }
+ it.previousUntil(i -> i == debugPosition);
+ if (movedPosition) {
+ it.next();
+ it.removeOrReplaceByDebugLocalRead();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean resetAfterMatch() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
index bec9df8..080b519 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import java.util.Collections;
// Source code representing simple forwarding method.
@@ -27,21 +28,22 @@
@Override
protected void prepareInstructions() {
- add(
- builder -> {
- DexItemFactory factory = builder.appView.dexItemFactory();
- DexProto initProto = factory.createProto(factory.voidType);
- DexMethod initMethod =
- factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
- builder.addNewInstance(register, exceptionType);
- builder.addInvoke(
- Type.DIRECT,
- initMethod,
- initMethod.proto,
- Collections.singletonList(ValueType.OBJECT),
- Collections.singletonList(register),
- false /* isInterface */);
- builder.addThrow(register);
- });
+ add(builder -> build(builder, exceptionType));
+ }
+
+ public static void build(IRBuilder builder, DexType exceptionType) {
+ DexItemFactory factory = builder.appView.dexItemFactory();
+ DexProto initProto = factory.createProto(factory.voidType);
+ DexMethod initMethod =
+ factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
+ builder.addNewInstance(register, exceptionType);
+ builder.addInvoke(
+ Type.DIRECT,
+ initMethod,
+ initMethod.proto,
+ Collections.singletonList(ValueType.OBJECT),
+ Collections.singletonList(register),
+ false /* isInterface */);
+ builder.addThrow(register);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 2d63c1e..48f0d33 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -73,8 +73,10 @@
this.keepInnerClassStructure = options.keepInnerClassStructure();
// Initialize top-level naming state.
- topLevelState = new Namespace(
- getPackageBinaryNameFromJavaType(options.getProguardConfiguration().getPackagePrefix()));
+ topLevelState =
+ new Namespace(
+ getPackageBinaryNameFromJavaType(
+ options.getProguardConfiguration().getPackagePrefix()));
states.put("", topLevelState);
@@ -212,9 +214,7 @@
}
}
- /**
- * Registers the given package prefix and all of parent packages as used.
- */
+ /** Registers the given package prefix and all of parent packages as used. */
private void registerPackagePrefixesAsUsed(String packagePrefix) {
// If -allowaccessmodification is not set, we may keep classes in their original packages,
// accounting for package-private accesses.
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index d133c3f..7773239 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
@@ -26,7 +25,6 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -131,22 +129,6 @@
}
@Override
- public boolean verifyNoOverlap(Map<DexType, DexString> map) {
- for (DexType alreadyRenamedInOtherLens : map.keySet()) {
- assert !renaming.containsKey(alreadyRenamedInOtherLens);
- assert !renaming.containsKey(
- appView.dexItemFactory().createType(map.get(alreadyRenamedInOtherLens)));
- }
- return true;
- }
-
- @Override
- public void forAllRenamedTypes(Consumer<DexType> consumer) {
- DexReference.filterDexType(DexReference.filterDexReference(renaming.keySet().stream()))
- .forEach(consumer);
- }
-
- @Override
public <T extends DexItem> Map<String, T> getRenamedItems(
Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
return renaming.keySet().stream()
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 7aab96a..3934b0f 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -24,7 +24,6 @@
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -41,8 +40,6 @@
*/
public abstract class NamingLens {
- protected boolean isSortingBeforeWriting;
-
public abstract String lookupPackageName(String packageName);
public abstract DexString lookupDescriptor(DexType type);
@@ -101,8 +98,6 @@
return dexItemFactory.createType(lookupDescriptor(type));
}
- public abstract boolean verifyNoOverlap(Map<DexType, DexString> map);
-
public boolean hasPrefixRewritingLogic() {
return false;
}
@@ -124,8 +119,6 @@
return DescriptorUtils.descriptorToInternalName(lookupDescriptor(type).toString());
}
- public abstract void forAllRenamedTypes(Consumer<DexType> consumer);
-
public abstract <T extends DexItem> Map<String, T> getRenamedItems(
Class<T> clazz, Predicate<T> predicate, Function<T, String> namer);
@@ -165,10 +158,6 @@
return true;
}
- public void setIsSortingBeforeWriting(boolean isSorting) {
- isSortingBeforeWriting = isSorting;
- }
-
private static class IdentityLens extends NamingLens {
private IdentityLens() {
@@ -201,21 +190,11 @@
}
@Override
- public boolean verifyNoOverlap(Map<DexType, DexString> map) {
- return true;
- }
-
- @Override
public String lookupPackageName(String packageName) {
return packageName;
}
@Override
- public void forAllRenamedTypes(Consumer<DexType> consumer) {
- // Intentionally left empty.
- }
-
- @Override
public <T extends DexItem> Map<String, T> getRenamedItems(
Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
return ImmutableMap.of();
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
index 482750f..9b51f27 100644
--- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.naming;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
@@ -17,7 +16,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -114,11 +112,6 @@
}
@Override
- public boolean verifyNoOverlap(Map<DexType, DexString> map) {
- throw new Unreachable("Multiple prefix rewriting lens not supported.");
- }
-
- @Override
public String lookupPackageName(String packageName) {
// Used for resource shrinking.
// Desugared libraries do not have resources.
@@ -138,18 +131,6 @@
}
@Override
- public void forAllRenamedTypes(Consumer<DexType> consumer) {
- // Used for printing the applyMapping map.
- // If compiling the program using a desugared library, nothing needs to be printed.
- // If compiling the desugared library, the mapping needs to be printed.
- // When debugging the program, both mapping files need to be merged.
- if (options.isDesugaredLibraryCompilation()) {
- appView.rewritePrefix.forAllRewrittenTypes(consumer);
- }
- namingLens.forAllRenamedTypes(consumer);
- }
-
- @Override
public <T extends DexItem> Map<String, T> getRenamedItems(
Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
Map<String, T> renamedItemsPrefixRewritting;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index ad27200..a29f8ef 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -579,9 +579,7 @@
@Override
public DexString lookupDescriptor(DexType type) {
- if (!isSortingBeforeWriting) {
- checkForUseOfNotMappedReference(type);
- }
+ checkForUseOfNotMappedReference(type);
return super.lookupDescriptor(type);
}
diff --git a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
index 3ec10a2..396d837 100644
--- a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
@@ -22,7 +22,6 @@
import com.google.common.collect.ImmutableMap;
import java.util.IdentityHashMap;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -160,16 +159,6 @@
}
@Override
- public boolean verifyNoOverlap(Map<DexType, DexString> map) {
- return true;
- }
-
- @Override
- public void forAllRenamedTypes(Consumer<DexType> consumer) {
- typeMappings.keySet().forEach(consumer);
- }
-
- @Override
public <T extends DexItem> Map<String, T> getRenamedItems(
Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
return typeMappings.keySet().stream()
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 70f4a81..8e65f12 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -155,7 +155,8 @@
retracer,
command.stackTrace,
command.diagnosticsHandler,
- command.regularExpression)
+ command.regularExpression,
+ command.isVerbose)
.retrace();
} else {
result =
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index a631ddf..f984045 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -90,7 +90,8 @@
new Element(
this,
mapper == null ? obfuscatedReference : Reference.classFromTypeName(mapper.originalName),
- mapper));
+ mapper,
+ false));
}
@Override
@@ -103,6 +104,11 @@
R create(Element element, T mappings, String obfuscatedName);
}
+ public boolean isAmbiguous() {
+ // Currently we have no way of producing ambiguous class results.
+ return false;
+ }
+
public static class Element {
private final RetraceClassResult classResult;
@@ -112,7 +118,8 @@
public Element(
RetraceClassResult classResult,
ClassReference classReference,
- ClassNamingForNameMapper mapper) {
+ ClassNamingForNameMapper mapper,
+ boolean isAmbiguous) {
this.classResult = classResult;
this.classReference = classReference;
this.mapper = mapper;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index d02913e..57b5354 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -40,7 +40,7 @@
return memberNamings != null;
}
- private boolean isAmbiguous() {
+ public boolean isAmbiguous() {
if (!hasRetraceResult()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index 611dea6..15479f9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -7,20 +7,22 @@
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.RetraceClassResult.Element;
-import com.android.tools.r8.retrace.RetraceRegularExpression.RetraceString.RetraceStringBuilder;
+import com.android.tools.r8.retrace.RetraceRegularExpression.ClassNameGroup.ClassNameGroupHandler;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Set;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -38,13 +40,14 @@
new SourceFileLineNumberGroup();
private final TypeNameGroup typeNameGroup = new TypeNameGroup();
private final BinaryNameGroup binaryNameGroup = new BinaryNameGroup();
- private final MethodNameGroup methodNameGroup = new MethodNameGroup();
- private final FieldNameGroup fieldNameGroup = new FieldNameGroup();
private final SourceFileGroup sourceFileGroup = new SourceFileGroup();
private final LineNumberGroup lineNumberGroup = new LineNumberGroup();
private final FieldOrReturnTypeGroup fieldOrReturnTypeGroup = new FieldOrReturnTypeGroup();
private final MethodArgumentsGroup methodArgumentsGroup = new MethodArgumentsGroup();
+ private final MethodNameGroup methodNameGroup;
+ private final FieldNameGroup fieldNameGroup;
+
private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
private static final int FIRST_CAPTURE_GROUP_INDEX = 0;
@@ -52,11 +55,14 @@
RetraceApi retracer,
List<String> stackTrace,
DiagnosticsHandler diagnosticsHandler,
- String regularExpression) {
+ String regularExpression,
+ boolean isVerbose) {
this.retracer = retracer;
this.stackTrace = stackTrace;
this.diagnosticsHandler = diagnosticsHandler;
this.regularExpression = regularExpression;
+ methodNameGroup = new MethodNameGroup(isVerbose);
+ fieldNameGroup = new FieldNameGroup(isVerbose);
}
public RetraceCommandLineResult retrace() {
@@ -72,40 +78,47 @@
List<String> result = new ArrayList<>();
for (String string : stackTrace) {
Matcher matcher = compiledPattern.matcher(string);
- List<RetraceString> retracedStrings =
- Lists.newArrayList(RetraceStringBuilder.create(string).build());
- if (matcher.matches()) {
- for (RegularExpressionGroupHandler handler : handlers) {
- retracedStrings = handler.handleMatch(retracedStrings, matcher, retracer);
- }
+ if (!matcher.matches()) {
+ result.add(string);
+ continue;
+ }
+ // Iterate through handlers to set contexts. That will allow us to process all handlers from
+ // left to right.
+ RetraceStringContext initialContext = RetraceStringContext.empty();
+ for (RegularExpressionGroupHandler handler : handlers) {
+ initialContext = handler.buildInitial(initialContext, matcher, retracer);
+ }
+ final RetraceString initialRetraceString = RetraceString.start(initialContext);
+ List<RetraceString> retracedStrings = Lists.newArrayList(initialRetraceString);
+ for (RegularExpressionGroupHandler handler : handlers) {
+ retracedStrings = handler.handleMatch(string, retracedStrings, matcher, retracer);
}
if (retracedStrings.isEmpty()) {
// We could not find a match. Output the identity.
result.add(string);
- } else {
- boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous;
- if (isAmbiguous) {
- retracedStrings.sort(new RetraceLineComparator());
- }
- ClassReference previousContext = null;
- for (RetraceString retracedString : retracedStrings) {
- String finalString = retracedString.getRetracedString();
- if (!isAmbiguous) {
- result.add(finalString);
- continue;
- }
- assert retracedString.getClassContext() != null;
- ClassReference currentContext = retracedString.getClassContext().getClassReference();
- if (currentContext.equals(previousContext)) {
- int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
- finalString =
- finalString.substring(0, firstNonWhitespaceCharacter)
- + "<OR> "
- + finalString.substring(firstNonWhitespaceCharacter);
- }
- previousContext = currentContext;
+ }
+ boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous();
+ if (isAmbiguous) {
+ retracedStrings.sort(new RetraceLineComparator());
+ }
+ ClassReference previousContext = null;
+ for (RetraceString retracedString : retracedStrings) {
+ String finalString = retracedString.builder.build(string);
+ if (!isAmbiguous) {
result.add(finalString);
+ continue;
}
+ assert retracedString.getClassContext() != null;
+ ClassReference currentContext = retracedString.getClassContext().getClassReference();
+ if (currentContext.equals(previousContext)) {
+ int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
+ finalString =
+ finalString.substring(0, firstNonWhitespaceCharacter)
+ + "<OR> "
+ + finalString.substring(firstNonWhitespaceCharacter);
+ }
+ previousContext = currentContext;
+ result.add(finalString);
}
}
return new RetraceCommandLineResult(result);
@@ -139,6 +152,7 @@
List<RegularExpressionGroupHandler> handlers,
int captureGroupIndex) {
int lastCommittedIndex = 0;
+ RegularExpressionGroupHandler lastHandler = null;
boolean seenPercentage = false;
boolean escaped = false;
for (int i = 0; i < regularExpression.length(); i++) {
@@ -146,7 +160,6 @@
assert !escaped;
final RegularExpressionGroup group = getGroupFromVariable(regularExpression.charAt(i));
refinedRegularExpression.append(regularExpression, lastCommittedIndex, i - 1);
- lastCommittedIndex = i + 1;
if (group.isSynthetic()) {
captureGroupIndex =
registerGroups(
@@ -159,8 +172,25 @@
.append(">")
.append(group.subExpression())
.append(")");
- handlers.add(group.createHandler(captureGroupName));
+ final RegularExpressionGroupHandler handler = group.createHandler(captureGroupName);
+ // If we see a pattern as %c.%m or %C/%m, then register the groups to allow delaying
+ // writing of the class string until we have the fully qualified member.
+ if (lastHandler != null
+ && handler.isQualifiedHandler()
+ && lastHandler.isClassNameGroupHandler()
+ && lastCommittedIndex == i - 3
+ && isTypeOrBinarySeparator(regularExpression, lastCommittedIndex, i - 2)) {
+ final ClassNameGroupHandler classNameGroupHandler =
+ lastHandler.asClassNameGroupHandler();
+ final QualifiedRegularExpressionGroupHandler qualifiedHandler =
+ handler.asQualifiedHandler();
+ classNameGroupHandler.setQualifiedHandler(qualifiedHandler);
+ qualifiedHandler.setClassNameGroupHandler(classNameGroupHandler);
+ }
+ lastHandler = handler;
+ handlers.add(handler);
}
+ lastCommittedIndex = i + 1;
seenPercentage = false;
} else {
seenPercentage = !escaped && regularExpression.charAt(i) == '%';
@@ -172,6 +202,18 @@
return captureGroupIndex;
}
+ private boolean isTypeOrBinarySeparator(String regularExpression, int startIndex, int endIndex) {
+ assert endIndex < regularExpression.length();
+ if (startIndex + 1 != endIndex) {
+ return false;
+ }
+ if (regularExpression.charAt(startIndex) != '\\') {
+ return false;
+ }
+ return regularExpression.charAt(startIndex + 1) == '.'
+ || regularExpression.charAt(startIndex + 1) == '/';
+ }
+
private RegularExpressionGroup getGroupFromVariable(char variable) {
switch (variable) {
case 'c':
@@ -197,223 +239,230 @@
}
}
- static class RetraceString {
-
+ static class RetraceStringContext {
private final Element classContext;
- private final ClassNameGroup classNameGroup;
private final ClassReference qualifiedContext;
+ private final String methodName;
private final RetraceMethodResult.Element methodContext;
- private final TypeReference typeOrReturnTypeContext;
- private final boolean hasTypeOrReturnTypeContext;
- private final String retracedString;
- private final int adjustedIndex;
- private final boolean isAmbiguous;
- private final int lineNumber;
+ private final int minifiedLineNumber;
+ private final int originalLineNumber;
private final String source;
+ private final boolean isAmbiguous;
- private RetraceString(
+ private RetraceStringContext(
Element classContext,
- ClassNameGroup classNameGroup,
ClassReference qualifiedContext,
+ String methodName,
RetraceMethodResult.Element methodContext,
- TypeReference typeOrReturnTypeContext,
- boolean hasTypeOrReturnTypeContext,
- String retracedString,
- int adjustedIndex,
- boolean isAmbiguous,
- int lineNumber,
- String source) {
+ int minifiedLineNumber,
+ int originalLineNumber,
+ String source,
+ boolean isAmbiguous) {
this.classContext = classContext;
- this.classNameGroup = classNameGroup;
this.qualifiedContext = qualifiedContext;
+ this.methodName = methodName;
this.methodContext = methodContext;
- this.typeOrReturnTypeContext = typeOrReturnTypeContext;
- this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
- this.retracedString = retracedString;
- this.adjustedIndex = adjustedIndex;
- this.isAmbiguous = isAmbiguous;
- this.lineNumber = lineNumber;
+ this.minifiedLineNumber = minifiedLineNumber;
+ this.originalLineNumber = originalLineNumber;
this.source = source;
+ this.isAmbiguous = isAmbiguous;
}
- String getRetracedString() {
- return retracedString;
+ private static RetraceStringContext empty() {
+ return new RetraceStringContext(null, null, null, null, NO_MATCH, NO_MATCH, null, false);
}
- boolean hasTypeOrReturnTypeContext() {
- return hasTypeOrReturnTypeContext;
+ private RetraceStringContext withClassContext(
+ Element classContext, ClassReference qualifiedContext) {
+ return new RetraceStringContext(
+ classContext,
+ qualifiedContext,
+ methodName,
+ methodContext,
+ minifiedLineNumber,
+ originalLineNumber,
+ source,
+ isAmbiguous);
}
- Element getClassContext() {
- return classContext;
+ private RetraceStringContext withMethodName(String methodName) {
+ return new RetraceStringContext(
+ classContext,
+ qualifiedContext,
+ methodName,
+ methodContext,
+ minifiedLineNumber,
+ originalLineNumber,
+ source,
+ isAmbiguous);
}
- RetraceMethodResult.Element getMethodContext() {
- return methodContext;
+ private RetraceStringContext withMethodContext(
+ RetraceMethodResult.Element methodContext,
+ ClassReference qualifiedContext,
+ boolean isAmbiguous) {
+ return new RetraceStringContext(
+ classContext,
+ qualifiedContext,
+ methodName,
+ methodContext,
+ minifiedLineNumber,
+ originalLineNumber,
+ source,
+ isAmbiguous);
}
- TypeReference getTypeOrReturnTypeContext() {
- return typeOrReturnTypeContext;
+ private RetraceStringContext withQualifiedContext(ClassReference qualifiedContext) {
+ return new RetraceStringContext(
+ classContext,
+ qualifiedContext,
+ methodName,
+ methodContext,
+ minifiedLineNumber,
+ originalLineNumber,
+ source,
+ isAmbiguous);
}
- public ClassReference getQualifiedContext() {
- return qualifiedContext;
+ public RetraceStringContext withSource(String source) {
+ return new RetraceStringContext(
+ classContext,
+ qualifiedContext,
+ methodName,
+ methodContext,
+ minifiedLineNumber,
+ originalLineNumber,
+ source,
+ isAmbiguous);
}
- RetraceStringBuilder transform() {
- return RetraceStringBuilder.create(this);
+ public RetraceStringContext withLineNumbers(int minifiedLineNumber, int originalLineNumber) {
+ return new RetraceStringContext(
+ classContext,
+ qualifiedContext,
+ methodName,
+ methodContext,
+ minifiedLineNumber,
+ originalLineNumber,
+ source,
+ isAmbiguous);
+ }
+ }
+
+ static class RetraceStringBuilder {
+
+ private final StringBuilder retracedString;
+ private int lastCommittedIndex;
+
+ private RetraceStringBuilder(String retracedString, int lastCommittedIndex) {
+ this.retracedString = new StringBuilder(retracedString);
+ this.lastCommittedIndex = lastCommittedIndex;
}
- public int getLineNumber() {
- return lineNumber;
+ private void appendRetracedString(
+ String source, String stringToAppend, int originalFromIndex, int originalToIndex) {
+ retracedString.append(source, lastCommittedIndex, originalFromIndex);
+ retracedString.append(stringToAppend);
+ lastCommittedIndex = originalToIndex;
}
- public String getSource() {
- return source;
+ private String build(String source) {
+ return retracedString.append(source, lastCommittedIndex, source.length()).toString();
+ }
+ }
+
+ private static class RetraceString {
+
+ private final RetraceStringBuilder builder;
+ private final RetraceStringContext context;
+
+ private RetraceString(RetraceStringBuilder builder, RetraceStringContext context) {
+ this.builder = builder;
+ this.context = context;
}
- static class RetraceStringBuilder {
+ private Element getClassContext() {
+ return context.classContext;
+ }
- private Element classContext;
- private ClassNameGroup classNameGroup;
- private ClassReference qualifiedContext;
- private RetraceMethodResult.Element methodContext;
- private TypeReference typeOrReturnTypeContext;
- private boolean hasTypeOrReturnTypeContext;
- private String retracedString;
- private int adjustedIndex;
- private boolean isAmbiguous;
- private int lineNumber;
- private String source;
+ private RetraceMethodResult.Element getMethodContext() {
+ return context.methodContext;
+ }
- private int maxReplaceStringIndex = NO_MATCH;
+ private String getSource() {
+ return context.source;
+ }
- private RetraceStringBuilder(
- Element classContext,
- ClassNameGroup classNameGroup,
- ClassReference qualifiedContext,
- RetraceMethodResult.Element methodContext,
- TypeReference typeOrReturnTypeContext,
- boolean hasTypeOrReturnTypeContext,
- String retracedString,
- int adjustedIndex,
- boolean isAmbiguous,
- int lineNumber,
- String source) {
- this.classContext = classContext;
- this.classNameGroup = classNameGroup;
- this.qualifiedContext = qualifiedContext;
- this.methodContext = methodContext;
- this.typeOrReturnTypeContext = typeOrReturnTypeContext;
- this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
- this.retracedString = retracedString;
- this.adjustedIndex = adjustedIndex;
- this.isAmbiguous = isAmbiguous;
- this.lineNumber = lineNumber;
- this.source = source;
- }
+ private int getLineNumber() {
+ return context.originalLineNumber;
+ }
- static RetraceStringBuilder create(String string) {
- return new RetraceStringBuilder(
- null, null, null, null, null, false, string, 0, false, 0, "");
- }
+ private boolean isAmbiguous() {
+ return context.isAmbiguous;
+ }
- static RetraceStringBuilder create(RetraceString string) {
- return new RetraceStringBuilder(
- string.classContext,
- string.classNameGroup,
- string.qualifiedContext,
- string.methodContext,
- string.typeOrReturnTypeContext,
- string.hasTypeOrReturnTypeContext,
- string.retracedString,
- string.adjustedIndex,
- string.isAmbiguous,
- string.lineNumber,
- string.source);
- }
+ private static RetraceString start(RetraceStringContext initialContext) {
+ return new RetraceString(new RetraceStringBuilder("", 0), initialContext);
+ }
- RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) {
- this.classContext = classContext;
- this.classNameGroup = classNameGroup;
- return this;
- }
+ private RetraceString updateContext(
+ Function<RetraceStringContext, RetraceStringContext> update) {
+ return new RetraceString(builder, update.apply(context));
+ }
- RetraceStringBuilder setMethodContext(RetraceMethodResult.Element methodContext) {
- this.methodContext = methodContext;
- return this;
- }
+ private RetraceString duplicate(RetraceStringContext newContext) {
+ return new RetraceString(
+ new RetraceStringBuilder(builder.retracedString.toString(), builder.lastCommittedIndex),
+ newContext);
+ }
- RetraceStringBuilder setTypeOrReturnTypeContext(TypeReference typeOrReturnTypeContext) {
- hasTypeOrReturnTypeContext = true;
- this.typeOrReturnTypeContext = typeOrReturnTypeContext;
- return this;
- }
-
- RetraceStringBuilder setQualifiedContext(ClassReference qualifiedContext) {
- this.qualifiedContext = qualifiedContext;
- return this;
- }
-
- RetraceStringBuilder setAmbiguous(boolean isAmbiguous) {
- this.isAmbiguous = isAmbiguous;
- return this;
- }
-
- RetraceStringBuilder setLineNumber(int lineNumber) {
- this.lineNumber = lineNumber;
- return this;
- }
-
- RetraceStringBuilder setSource(String source) {
- this.source = source;
- return this;
- }
-
- RetraceStringBuilder replaceInString(String oldString, String newString) {
- int oldStringStartIndex = retracedString.indexOf(oldString);
- assert oldStringStartIndex > NO_MATCH;
- int oldStringEndIndex = oldStringStartIndex + oldString.length();
- return replaceInStringRaw(newString, oldStringStartIndex, oldStringEndIndex);
- }
-
- RetraceStringBuilder replaceInString(String newString, int originalFrom, int originalTo) {
- return replaceInStringRaw(
- newString, originalFrom + adjustedIndex, originalTo + adjustedIndex);
- }
-
- RetraceStringBuilder replaceInStringRaw(String newString, int from, int to) {
- assert from <= to;
- assert from > maxReplaceStringIndex;
- String prefix = retracedString.substring(0, from);
- String postFix = retracedString.substring(to);
- this.retracedString = prefix + newString + postFix;
- this.adjustedIndex = adjustedIndex + newString.length() - (to - from);
- maxReplaceStringIndex = prefix.length() + newString.length();
- return this;
- }
-
- RetraceString build() {
- return new RetraceString(
- classContext,
- classNameGroup,
- qualifiedContext,
- methodContext,
- typeOrReturnTypeContext,
- hasTypeOrReturnTypeContext,
- retracedString,
- adjustedIndex,
- isAmbiguous,
- lineNumber,
- source);
- }
+ private RetraceString appendRetracedString(
+ String source, String stringToAppend, int originalFromIndex, int originalToIndex) {
+ builder.appendRetracedString(source, stringToAppend, originalFromIndex, originalToIndex);
+ return this;
}
}
private interface RegularExpressionGroupHandler {
List<RetraceString> handleMatch(
- List<RetraceString> strings, Matcher matcher, RetraceApi retracer);
+ String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer);
+
+ default RetraceStringContext buildInitial(
+ RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+ return context;
+ }
+
+ default boolean isClassNameGroupHandler() {
+ return false;
+ }
+
+ default ClassNameGroupHandler asClassNameGroupHandler() {
+ return null;
+ }
+
+ default boolean isQualifiedHandler() {
+ return false;
+ }
+
+ default QualifiedRegularExpressionGroupHandler asQualifiedHandler() {
+ return null;
+ }
+ }
+
+ private interface QualifiedRegularExpressionGroupHandler extends RegularExpressionGroupHandler {
+
+ @Override
+ default boolean isQualifiedHandler() {
+ return true;
+ }
+
+ @Override
+ default QualifiedRegularExpressionGroupHandler asQualifiedHandler() {
+ return this;
+ }
+
+ void setClassNameGroupHandler(ClassNameGroupHandler handler);
}
private abstract static class RegularExpressionGroup {
@@ -431,7 +480,10 @@
private static final String javaIdentifierSegment =
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
- private abstract static class ClassNameGroup extends RegularExpressionGroup {
+ private static final String METHOD_NAME_REGULAR_EXPRESSION =
+ "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
+
+ abstract static class ClassNameGroup extends RegularExpressionGroup {
abstract String getClassName(ClassReference classReference);
@@ -439,30 +491,101 @@
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
+ return new ClassNameGroupHandler(this, captureGroup);
+ }
+
+ static class ClassNameGroupHandler implements RegularExpressionGroupHandler {
+
+ private RetraceClassResult retraceClassResult = null;
+ private final ClassNameGroup classNameGroup;
+ private final String captureGroup;
+ private RegularExpressionGroupHandler qualifiedHandler;
+
+ public ClassNameGroupHandler(ClassNameGroup classNameGroup, String captureGroup) {
+ this.classNameGroup = classNameGroup;
+ this.captureGroup = captureGroup;
+ }
+
+ @Override
+ public List<RetraceString> handleMatch(
+ String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
return strings;
}
String typeName = matcher.group(captureGroup);
- RetraceClassResult retraceResult = retracer.retrace(classFromMatch(typeName));
- List<RetraceString> retracedStrings = new ArrayList<>();
+ RetraceClassResult retraceResult =
+ retraceClassResult == null
+ ? retracer.retrace(classNameGroup.classFromMatch(typeName))
+ : retraceClassResult;
+ assert !retraceResult.isAmbiguous();
+ List<RetraceString> retraceStrings = new ArrayList<>(strings.size());
for (RetraceString retraceString : strings) {
retraceResult.forEach(
element -> {
- retracedStrings.add(
- retraceString
- .transform()
- .setClassContext(element, this)
- .setMethodContext(null)
- .replaceInString(
- getClassName(element.getClassReference()),
- matcher.start(captureGroup),
- matcher.end(captureGroup))
- .build());
+ final RetraceString newRetraceString =
+ retraceString.updateContext(
+ context -> context.withClassContext(element, element.getClassReference()));
+ retraceStrings.add(newRetraceString);
+ if (qualifiedHandler == null) {
+ // If there is no qualified handler, commit right away.
+ newRetraceString.builder.appendRetracedString(
+ original,
+ classNameGroup.getClassName(element.getClassReference()),
+ startOfGroup,
+ matcher.end(captureGroup));
+ }
});
}
- return retracedStrings;
- };
+ return retraceStrings;
+ }
+
+ void commitClassName(
+ String original,
+ RetraceString retraceString,
+ ClassReference qualifiedContext,
+ Matcher matcher) {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return;
+ }
+ retraceString.builder.appendRetracedString(
+ original,
+ classNameGroup.getClassName(qualifiedContext),
+ matcher.start(captureGroup),
+ matcher.end(captureGroup));
+ }
+
+ @Override
+ public RetraceStringContext buildInitial(
+ RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+ // Reset the local class context since this the same handler is used for multiple lines.
+ retraceClassResult = null;
+ if (matcher.start(captureGroup) == NO_MATCH || context.classContext != null) {
+ return context;
+ }
+ String typeName = matcher.group(captureGroup);
+ retraceClassResult = retracer.retrace(classNameGroup.classFromMatch(typeName));
+ assert !retraceClassResult.isAmbiguous();
+ Box<RetraceStringContext> box = new Box<>();
+ retraceClassResult.forEach(
+ element -> box.set(context.withClassContext(element, element.getClassReference())));
+ return box.get();
+ }
+
+ public void setQualifiedHandler(RegularExpressionGroupHandler handler) {
+ assert handler.isQualifiedHandler();
+ this.qualifiedHandler = handler;
+ }
+
+ @Override
+ public boolean isClassNameGroupHandler() {
+ return true;
+ }
+
+ @Override
+ public ClassNameGroupHandler asClassNameGroupHandler() {
+ return this;
+ }
}
}
@@ -504,70 +627,114 @@
private static class MethodNameGroup extends RegularExpressionGroup {
+ private final boolean printVerbose;
+
+ public MethodNameGroup(boolean printVerbose) {
+ this.printVerbose = printVerbose;
+ }
+
@Override
String subExpression() {
- return "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
+ return METHOD_NAME_REGULAR_EXPRESSION;
}
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
- return strings;
+ return new QualifiedRegularExpressionGroupHandler() {
+
+ private ClassNameGroupHandler classNameGroupHandler;
+
+ @Override
+ public void setClassNameGroupHandler(ClassNameGroupHandler handler) {
+ classNameGroupHandler = handler;
}
- String methodName = matcher.group(captureGroup);
- List<RetraceString> retracedStrings = new ArrayList<>();
- for (RetraceString retraceString : strings) {
- if (retraceString.classContext == null) {
- retracedStrings.add(retraceString);
- continue;
+
+ @Override
+ public List<RetraceString> handleMatch(
+ String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
+ if (classNameGroupHandler != null) {
+ for (RetraceString string : strings) {
+ classNameGroupHandler.commitClassName(
+ original, string, string.context.qualifiedContext, matcher);
+ }
+ }
+ return strings;
}
- retraceString
- .getClassContext()
- .lookupMethod(methodName)
- .forEach(
- element -> {
- MethodReference methodReference = element.getMethodReference();
- if (retraceString.hasTypeOrReturnTypeContext()) {
- if (methodReference.getReturnType() == null
- && retraceString.getTypeOrReturnTypeContext() != null) {
- return;
- } else if (methodReference.getReturnType() != null
- && !methodReference
- .getReturnType()
- .equals(retraceString.getTypeOrReturnTypeContext())) {
- return;
- }
- }
- RetraceStringBuilder newRetraceString = retraceString.transform();
- ClassReference existingClass =
- retraceString.getClassContext().getClassReference();
- ClassReference holder = methodReference.getHolderClass();
- if (holder != existingClass) {
- // The element is defined on another holder.
- newRetraceString
- .replaceInString(
- newRetraceString.classNameGroup.getClassName(existingClass),
- newRetraceString.classNameGroup.getClassName(holder))
- .setQualifiedContext(holder);
- }
- newRetraceString
- .setMethodContext(element)
- .setAmbiguous(element.getRetraceMethodResult().isAmbiguous())
- .replaceInString(
- methodReference.getMethodName(),
- matcher.start(captureGroup),
- matcher.end(captureGroup));
- retracedStrings.add(newRetraceString.build());
- });
+ String methodName = matcher.group(captureGroup);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ retraceMethodForString(
+ retraceString,
+ methodName,
+ (element, newContext) -> {
+ final RetraceString newRetraceString = retraceString.duplicate(newContext);
+ if (classNameGroupHandler != null) {
+ classNameGroupHandler.commitClassName(
+ original,
+ newRetraceString,
+ element.getMethodReference().getHolderClass(),
+ matcher);
+ }
+ retracedStrings.add(
+ newRetraceString.appendRetracedString(
+ original,
+ printVerbose
+ ? RetraceUtils.methodDescriptionFromMethodReference(
+ element.getMethodReference(), false, true)
+ : element.getMethodReference().getMethodName(),
+ startOfGroup,
+ matcher.end(captureGroup)));
+ });
+ }
+ return retracedStrings;
}
- return retracedStrings;
+
+ @Override
+ public RetraceStringContext buildInitial(
+ RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH || context.methodName != null) {
+ return context;
+ }
+ return context.withMethodName(matcher.group(captureGroup));
+ }
};
}
+
+ private static void retraceMethodForString(
+ RetraceString retraceString,
+ String methodName,
+ BiConsumer<RetraceMethodResult.Element, RetraceStringContext> process) {
+ if (retraceString.context.classContext == null) {
+ return;
+ }
+ RetraceMethodResult retraceMethodResult =
+ retraceString.getClassContext().lookupMethod(methodName);
+ if (retraceString.context.minifiedLineNumber > NO_MATCH) {
+ retraceMethodResult =
+ retraceMethodResult.narrowByLine(retraceString.context.minifiedLineNumber);
+ }
+ retraceMethodResult.forEach(
+ element ->
+ process.accept(
+ element,
+ retraceString.context.withMethodContext(
+ element,
+ element.getMethodReference().getHolderClass(),
+ element.getRetraceMethodResult().isAmbiguous())));
+ }
}
private static class FieldNameGroup extends RegularExpressionGroup {
+ private final boolean printVerbose;
+
+ public FieldNameGroup(boolean printVerbose) {
+ this.printVerbose = printVerbose;
+ }
+
@Override
String subExpression() {
return javaIdentifierSegment;
@@ -575,44 +742,71 @@
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
- return strings;
+ return new QualifiedRegularExpressionGroupHandler() {
+
+ private ClassNameGroupHandler classNameGroupHandler;
+
+ @Override
+ public void setClassNameGroupHandler(ClassNameGroupHandler handler) {
+ classNameGroupHandler = handler;
}
- String methodName = matcher.group(captureGroup);
- List<RetraceString> retracedStrings = new ArrayList<>();
- for (RetraceString retraceString : strings) {
- if (retraceString.getClassContext() == null) {
- retracedStrings.add(retraceString);
- continue;
+
+ @Override
+ public List<RetraceString> handleMatch(
+ String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
+ if (classNameGroupHandler != null) {
+ for (RetraceString string : strings) {
+ classNameGroupHandler.commitClassName(
+ original, string, string.context.qualifiedContext, matcher);
+ }
+ }
+ return strings;
}
- retraceString
- .getClassContext()
- .lookupField(methodName)
- .forEach(
- element -> {
- RetraceStringBuilder newRetraceString = retraceString.transform();
- ClassReference existingClass =
- retraceString.getClassContext().getClassReference();
- ClassReference holder = element.getFieldReference().getHolderClass();
- if (holder != existingClass) {
- // The element is defined on another holder.
- newRetraceString
- .replaceInString(
- newRetraceString.classNameGroup.getClassName(existingClass),
- newRetraceString.classNameGroup.getClassName(holder))
- .setQualifiedContext(holder);
- }
- newRetraceString.replaceInString(
- element.getFieldReference().getFieldName(),
- matcher.start(captureGroup),
- matcher.end(captureGroup));
- retracedStrings.add(newRetraceString.build());
- });
+ String methodName = matcher.group(captureGroup);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ if (retraceString.getClassContext() == null) {
+ assert classNameGroupHandler == null;
+ return strings;
+ }
+ final RetraceFieldResult retraceFieldResult =
+ retraceString.getClassContext().lookupField(methodName);
+ assert !retraceFieldResult.isAmbiguous();
+ retraceFieldResult.forEach(
+ element -> {
+ if (classNameGroupHandler != null) {
+ classNameGroupHandler.commitClassName(
+ original,
+ retraceString,
+ element.getFieldReference().getHolderClass(),
+ matcher);
+ }
+ retracedStrings.add(
+ retraceString
+ .updateContext(
+ context ->
+ context.withQualifiedContext(
+ element.getFieldReference().getHolderClass()))
+ .appendRetracedString(
+ original,
+ getFieldString(element.getFieldReference()),
+ startOfGroup,
+ matcher.end(captureGroup)));
+ });
+ }
+ return retracedStrings;
}
- return retracedStrings;
};
}
+
+ private String getFieldString(FieldReference fieldReference) {
+ if (!printVerbose) {
+ return fieldReference.getFieldName();
+ }
+ return fieldReference.getFieldType().getTypeName() + " " + fieldReference.getFieldName();
+ }
}
private static class SourceFileGroup extends RegularExpressionGroup {
@@ -624,33 +818,35 @@
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
+ return (original, strings, matcher, retracer) -> {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
return strings;
}
String fileName = matcher.group(captureGroup);
- List<RetraceString> retracedStrings = new ArrayList<>();
+ List<RetraceString> retracedStrings = null;
for (RetraceString retraceString : strings) {
- if (retraceString.classContext == null) {
- retracedStrings.add(retraceString);
- continue;
+ if (retraceString.context.classContext == null) {
+ return strings;
+ }
+ if (retracedStrings == null) {
+ retracedStrings = new ArrayList<>();
}
RetraceSourceFileResult sourceFileResult =
retraceString.getMethodContext() != null
? retraceString.getMethodContext().retraceSourceFile(fileName)
: RetraceUtils.getSourceFile(
retraceString.getClassContext(),
- retraceString.getQualifiedContext(),
+ retraceString.context.qualifiedContext,
fileName);
retracedStrings.add(
retraceString
- .transform()
- .setSource(fileName)
- .replaceInString(
+ .updateContext(context -> context.withSource(sourceFileResult.getFilename()))
+ .appendRetracedString(
+ original,
sourceFileResult.getFilename(),
- matcher.start(captureGroup),
- matcher.end(captureGroup))
- .build());
+ startOfGroup,
+ matcher.end(captureGroup)));
}
return retracedStrings;
};
@@ -666,63 +862,90 @@
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
- return strings;
+ return new RegularExpressionGroupHandler() {
+ @Override
+ public List<RetraceString> handleMatch(
+ String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
+ return strings;
+ }
+ String lineNumberAsString = matcher.group(captureGroup);
+ if (lineNumberAsString.isEmpty()) {
+ return strings;
+ }
+ int lineNumber = Integer.parseInt(lineNumberAsString);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ RetraceMethodResult.Element methodContext = retraceString.context.methodContext;
+ if (methodContext == null) {
+ if (retraceString.context.classContext == null
+ || retraceString.context.methodName == null) {
+ // We have no way of retracing the line number.
+ retracedStrings.add(retraceString);
+ continue;
+ }
+ // This situation arises when we have a matched pattern as %l..%c.%m where the
+ // line number handler is defined before the methodname handler.
+ MethodNameGroup.retraceMethodForString(
+ retraceString,
+ retraceString.context.methodName,
+ (element, newContext) -> {
+ // The same method can be represented multiple times if it has multiple
+ // mappings.
+ if (element.hasNoLineNumberRange()
+ || !element.containsMinifiedLineNumber(lineNumber)) {
+ diagnosticsHandler.info(
+ new StringDiagnostic(
+ "Pruning "
+ + retraceString.builder.retracedString.toString()
+ + " from result because method is not in range on line number "
+ + lineNumber));
+ }
+ final int originalLineNumber = element.getOriginalLineNumber(lineNumber);
+ retracedStrings.add(
+ retraceString
+ .updateContext(
+ context -> context.withLineNumbers(lineNumber, originalLineNumber))
+ .appendRetracedString(
+ original,
+ originalLineNumber + "",
+ startOfGroup,
+ matcher.end(captureGroup)));
+ });
+ continue;
+ }
+ // If the method context is unknown, do nothing.
+ if (methodContext.getMethodReference().isUnknown()
+ || methodContext.hasNoLineNumberRange()) {
+ retracedStrings.add(retraceString);
+ continue;
+ }
+ int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
+ retracedStrings.add(
+ retraceString
+ .updateContext(
+ context -> context.withLineNumbers(lineNumber, originalLineNumber))
+ .appendRetracedString(
+ original,
+ originalLineNumber + "",
+ startOfGroup,
+ matcher.end(captureGroup)));
+ }
+ return retracedStrings;
}
- String lineNumberAsString = matcher.group(captureGroup);
- int lineNumber =
- lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString);
- List<RetraceString> retracedStrings = new ArrayList<>();
- boolean seenRange = false;
- for (RetraceString retraceString : strings) {
- RetraceMethodResult.Element methodContext = retraceString.methodContext;
- if (methodContext == null || methodContext.getMethodReference().isUnknown()) {
- retracedStrings.add(retraceString);
- continue;
+
+ @Override
+ public RetraceStringContext buildInitial(
+ RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+ if (matcher.start(captureGroup) == NO_MATCH || context.minifiedLineNumber > NO_MATCH) {
+ return context;
}
- if (methodContext.hasNoLineNumberRange()) {
- continue;
- }
- seenRange = true;
- Set<MethodReference> narrowedSet =
- methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream()
- .map(RetraceMethodResult.Element::getMethodReference)
- .collect(Collectors.toSet());
- if (!narrowedSet.contains(methodContext.getMethodReference())) {
- // Prune the retraceString since we now have line number information and this is not
- // a part of the result.
- diagnosticsHandler.info(
- new StringDiagnostic(
- "Pruning "
- + retraceString.getRetracedString()
- + " from result because method is not defined on line number "
- + lineNumber));
- continue;
- }
- // The same method can be represented multiple times if it has multiple mappings.
- if (!methodContext.containsMinifiedLineNumber(lineNumber)) {
- diagnosticsHandler.info(
- new StringDiagnostic(
- "Pruning "
- + retraceString.getRetracedString()
- + " from result because method is not in range on line number "
- + lineNumber));
- continue;
- }
- int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
- retracedStrings.add(
- retraceString
- .transform()
- .setAmbiguous(false)
- .setLineNumber(originalLineNumber)
- .replaceInString(
- originalLineNumber + "",
- matcher.start(captureGroup),
- matcher.end(captureGroup))
- .build());
+ String lineNumberAsString = matcher.group(captureGroup);
+ return context.withLineNumbers(
+ lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString),
+ NO_MATCH);
}
- return seenRange ? retracedStrings : strings;
};
}
}
@@ -757,8 +980,9 @@
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
+ return (original, strings, matcher, retracer) -> {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
return strings;
}
String typeName = matcher.group(captureGroup);
@@ -767,29 +991,25 @@
return strings;
}
TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
- List<RetraceString> retracedStrings = new ArrayList<>();
RetraceTypeResult retracedType = retracer.retrace(typeReference);
+ assert !retracedType.isAmbiguous();
for (RetraceString retraceString : strings) {
retracedType.forEach(
element -> {
TypeReference retracedReference = element.getTypeReference();
- retracedStrings.add(
- retraceString
- .transform()
- .setTypeOrReturnTypeContext(retracedReference)
- .replaceInString(
- retracedReference == null ? "void" : retracedReference.getTypeName(),
- matcher.start(captureGroup),
- matcher.end(captureGroup))
- .build());
+ retraceString.appendRetracedString(
+ original,
+ retracedReference == null ? "void" : retracedReference.getTypeName(),
+ startOfGroup,
+ matcher.end(captureGroup));
});
}
- return retracedStrings;
+ return strings;
};
}
}
- private class MethodArgumentsGroup extends RegularExpressionGroup {
+ private static class MethodArgumentsGroup extends RegularExpressionGroup {
@Override
String subExpression() {
@@ -798,74 +1018,36 @@
@Override
RegularExpressionGroupHandler createHandler(String captureGroup) {
- return (strings, matcher, retracer) -> {
- if (matcher.start(captureGroup) == NO_MATCH) {
+ return (original, strings, matcher, retracer) -> {
+ final int startOfGroup = matcher.start(captureGroup);
+ if (startOfGroup == NO_MATCH) {
return strings;
}
- Set<List<TypeReference>> initialValue = new LinkedHashSet<>();
- initialValue.add(new ArrayList<>());
- Set<List<TypeReference>> allRetracedReferences =
+ final String formals =
Arrays.stream(matcher.group(captureGroup).split(","))
- .map(String::trim)
- .reduce(
- initialValue,
- (acc, typeName) -> {
+ .map(
+ typeName -> {
+ typeName = typeName.trim();
+ if (typeName.isEmpty()) {
+ return null;
+ }
String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName);
if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
- return acc;
+ return typeName;
}
- TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
- Set<List<TypeReference>> retracedTypes = new LinkedHashSet<>();
- retracer
- .retrace(typeReference)
- .forEach(
- element -> {
- for (List<TypeReference> currentReferences : acc) {
- ArrayList<TypeReference> newList =
- new ArrayList<>(currentReferences);
- newList.add(element.getTypeReference());
- retracedTypes.add(newList);
- }
- });
- return retracedTypes;
- },
- (l1, l2) -> {
- l1.addAll(l2);
- return l1;
- });
- List<RetraceString> retracedStrings = new ArrayList<>();
- for (RetraceString retraceString : strings) {
- if (retraceString.getMethodContext() != null
- && !allRetracedReferences.contains(
- retraceString.getMethodContext().getMethodReference().getFormalTypes())) {
- // Prune the string since we now know the formals.
- String formals =
- retraceString.getMethodContext().getMethodReference().getFormalTypes().stream()
- .map(TypeReference::getTypeName)
- .collect(Collectors.joining(","));
- diagnosticsHandler.info(
- new StringDiagnostic(
- "Pruning "
- + retraceString.getRetracedString()
- + " from result because formals ("
- + formals
- + ") do not match result set."));
- continue;
- }
- for (List<TypeReference> retracedReferences : allRetracedReferences) {
- retracedStrings.add(
- retraceString
- .transform()
- .replaceInString(
- retracedReferences.stream()
- .map(TypeReference::getTypeName)
- .collect(Collectors.joining(",")),
- matcher.start(captureGroup),
- matcher.end(captureGroup))
- .build());
- }
+ final RetraceTypeResult retraceResult =
+ retracer.retrace(Reference.returnTypeFromDescriptor(descriptor));
+ assert !retraceResult.isAmbiguous();
+ final Box<TypeReference> elementBox = new Box<>();
+ retraceResult.forEach(element -> elementBox.set(element.getTypeReference()));
+ return elementBox.get().getTypeName();
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.joining(","));
+ for (RetraceString string : strings) {
+ string.appendRetracedString(original, formals, startOfGroup, matcher.end(captureGroup));
}
- return retracedStrings;
+ return strings;
};
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 88d048f..fd28127 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -434,7 +434,7 @@
moduleName,
methodReference.getHolderClass().getTypeName(),
methodReference.getMethodName(),
- methodDescriptionFromMethodReference(methodReference, verbose),
+ methodDescriptionFromMethodReference(methodReference, true, verbose),
methodElement.retraceSourceFile(fileName).getFilename(),
hasLinePosition()
? methodElement.getOriginalLineNumber(linePosition)
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
index 8ea169f..b70602d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -35,6 +35,10 @@
.map(clazz -> new Element(clazz.getClassReference()));
}
+ public boolean isAmbiguous() {
+ return false;
+ }
+
@Override
public RetraceTypeResult forEach(Consumer<Element> resultConsumer) {
stream().forEach(resultConsumer);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index c82bf36..8e862d0 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -17,18 +17,20 @@
Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
public static String methodDescriptionFromMethodReference(
- MethodReference methodReference, boolean verbose) {
+ MethodReference methodReference, boolean appendHolder, boolean verbose) {
if (!verbose || methodReference.isUnknown()) {
return methodReference.getHolderClass().getTypeName() + "." + methodReference.getMethodName();
}
StringBuilder sb = new StringBuilder();
+ if (appendHolder) {
+ sb.append(methodReference.getHolderClass().getTypeName());
+ sb.append(".");
+ }
sb.append(
methodReference.getReturnType() == null
? "void"
: methodReference.getReturnType().getTypeName());
sb.append(" ");
- sb.append(methodReference.getHolderClass().getTypeName());
- sb.append(".");
sb.append(methodReference.getMethodName());
sb.append("(");
boolean seenFirstIndex = false;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index c716da8..6768368 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -42,13 +42,13 @@
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.SyntheticItems;
+import com.android.tools.r8.graph.SyntheticItems.CommittedItems;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.utils.AssertionUtils;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
@@ -1000,17 +1000,11 @@
.map(FieldResolutionResult::getResolvedField)
.collect(Collectors.toList()));
- assert lens.assertDefinitionsNotModified(
- neverMerge.stream()
- .map(this::definitionFor)
- .filter(AssertionUtils::assertNotNull)
- .collect(Collectors.toList()));
-
- DexDefinitionSupplier definitionSupplier =
- application.getDefinitionsSupplier(SyntheticItems.createInitialSyntheticItems());
+ CommittedItems committedItems = getSyntheticItems().commit(application, lens);
+ DexDefinitionSupplier definitionSupplier = application.getDefinitionsSupplier(committedItems);
return new AppInfoWithLiveness(
application,
- getSyntheticItems().commit(application, lens),
+ committedItems,
deadProtoTypes,
missingTypes,
lens.rewriteTypes(liveTypes),
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index f0340a3..c23e47d 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -107,6 +107,7 @@
private final ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose;
private final StringResource proguardMapOutputData;
+ private final StringResource proguardMapInputData;
private final List<StringResource> mainDexListResources;
private final List<String> mainDexClasses;
@@ -176,6 +177,7 @@
ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose,
StringResource proguardMapOutputData,
+ StringResource proguardMapInputData,
List<StringResource> mainDexListResources,
List<String> mainDexClasses) {
this.programResourceProviders = programResourceProviders;
@@ -184,6 +186,7 @@
this.libraryResourceProviders = libraryResourceProviders;
this.archiveProvidersToClose = archiveProvidersToClose;
this.proguardMapOutputData = proguardMapOutputData;
+ this.proguardMapInputData = proguardMapInputData;
this.mainDexListResources = mainDexListResources;
this.mainDexClasses = mainDexClasses;
assert verifyInternalProvidersInCloseSet(classpathResourceProviders, archiveProvidersToClose);
@@ -344,6 +347,11 @@
return proguardMapOutputData;
}
+ /** Get the proguard-map associated with an input "app" if it exists. */
+ public StringResource getProguardMapInputData() {
+ return proguardMapInputData;
+ }
+
/**
* True if the main dex list resources exists.
*/
@@ -381,6 +389,7 @@
libraryResourceProviders,
archiveProvidersToClose,
proguardMapOutputData,
+ proguardMapInputData,
ImmutableList.of(),
ImmutableList.of());
}
@@ -718,8 +727,8 @@
private List<String> mainDexListClasses = new ArrayList<>();
private boolean ignoreDexInArchive = false;
- // Proguard map data is output only data. This should never be used as input to a compilation.
private StringResource proguardMapOutputData;
+ private StringResource proguardMapInputData;
private final Reporter reporter;
@@ -737,6 +746,7 @@
archiveProvidersToClose.addAll(app.archiveProvidersToClose);
mainDexListResources = app.mainDexListResources;
mainDexListClasses = app.mainDexClasses;
+ proguardMapInputData = app.proguardMapInputData;
}
public Reporter getReporter() {
@@ -1055,6 +1065,11 @@
return this;
}
+ public Builder setProguardMapInputData(Path mapPath) {
+ this.proguardMapInputData = StringResource.fromFile(mapPath);
+ return this;
+ }
+
/**
* Add a main-dex list file.
*/
@@ -1150,6 +1165,7 @@
ImmutableList.copyOf(libraryResourceProviders),
ImmutableList.copyOf(archiveProvidersToClose),
proguardMapOutputData,
+ proguardMapInputData,
mainDexListResources,
mainDexListClasses);
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 69410dd..bb51cc0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1257,6 +1257,8 @@
// Option for testing outlining with interface array arguments, see b/132420510.
public boolean allowOutlinerInterfaceArrayArguments = false;
+ public int limitNumberOfClassesPerDex = -1;
+
public MinifierTestingOptions minifier = new MinifierTestingOptions();
// Testing hooks to trigger effects in various compiler places.
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 9c703e7..17c2fd2 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -235,7 +235,8 @@
currentPc,
currentPosition,
processedEvents,
- dexItemFactory);
+ dexItemFactory,
+ true);
previousPc = currentPc;
previousPosition = currentPosition;
}
@@ -377,10 +378,9 @@
Map<DexMethod, MethodSignature> signatures = new IdentityHashMap<>();
signatures.put(originalMethod, originalSignature);
Function<DexMethod, MethodSignature> getOriginalMethodSignature =
- m -> {
- return signatures.computeIfAbsent(
- m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
- };
+ m ->
+ signatures.computeIfAbsent(
+ m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
@@ -622,13 +622,17 @@
List<DexDebugEvent> processedEvents = new ArrayList<>();
PositionEventEmitter positionEventEmitter =
- new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
+ new PositionEventEmitter(
+ application.dexItemFactory,
+ appView.graphLens().getOriginalMethodSignature(method.method),
+ processedEvents);
Box<Boolean> inlinedOriginalPosition = new Box<>(false);
// Debug event visitor to map line numbers.
DexDebugEventVisitor visitor =
- new DexDebugPositionState(debugInfo.startLine, method.method) {
+ new DexDebugPositionState(
+ debugInfo.startLine, appView.graphLens().getOriginalMethodSignature(method.method)) {
// Keep track of what PC has been emitted.
private int emittedPc = 0;
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index f395118..5be6b85 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -621,6 +621,17 @@
}
@Test
+ public void pgInputMap() throws CompilationFailedException, IOException, ResourceException {
+ Path mapFile = temp.newFile().toPath();
+ FileUtils.writeTextFile(
+ mapFile,
+ "com.android.tools.r8.ApiLevelException -> com.android.tools.r8.a:",
+ " boolean $assertionsDisabled -> c");
+ D8Command d8Command = parse("--pg-map", mapFile.toString());
+ assertFalse(d8Command.getInputApp().getProguardMapInputData().getString().isEmpty());
+ }
+
+ @Test
public void numThreadsOption() throws Exception {
assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
assertEquals(1, parse("--thread-count", "1").getThreadCount());
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 97e244b..4317ca8 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -162,6 +162,11 @@
return addVmArguments(Arrays.asList(arguments));
}
+ public JvmTestBuilder disassemble() throws Exception {
+ ToolHelper.disassemble(builder.build(), System.out);
+ return self();
+ }
+
public JvmTestBuilder addAndroidBuildVersion() {
addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000");
return addProgramClasses(AndroidBuildVersion.class);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 35e1eec..38637cf 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -73,6 +73,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -451,6 +452,11 @@
.build();
}
+ protected List<String> classNamesFromDexFile(Path dexFile) throws IOException {
+ return new CodeInspector(dexFile)
+ .allClasses().stream().map(FoundClassSubject::toString).collect(Collectors.toList());
+ }
+
/**
* Build an AndroidApp with the specified test classes.
*/
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 8c6b864..7b630c3 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -192,7 +192,7 @@
}
public TestParametersCollection build() {
- assert !enableApiLevels || hasDexRuntimeFilter;
+ assert !enableApiLevels || enableApiLevelsForCf || hasDexRuntimeFilter;
return new TestParametersCollection(
getAvailableRuntimes()
.flatMap(this::createParameters)
diff --git a/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
new file mode 100644
index 0000000..49e78f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2020, 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.d8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ProguardMapSortByTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ProguardMapSortByTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMapSorting() throws Exception {
+ Path mappingFile = temp.newFile().toPath();
+ // Sort the classes, so that they are in 3 packages _not_ sorted by their original name.
+ FileUtils.writeTextFile(
+ mappingFile,
+ "com.A.a -> " + C.class.getTypeName() + ":",
+ "com.A.b -> " + B.class.getTypeName() + ":",
+ "com.B.a -> " + D.class.getTypeName() + ":",
+ "com.B.b -> " + E.class.getTypeName() + ":",
+ "com.C.a -> " + A.class.getTypeName() + ":");
+ D8TestBuilder d8TestBuilder =
+ testForD8()
+ .addOptionsModification(
+ internalOptions -> internalOptions.testing.limitNumberOfClassesPerDex = 2);
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
+ d8TestBuilder.addMainDexListClasses(A.class);
+ }
+ Path outputDir = temp.newFolder().toPath();
+ d8TestBuilder
+ .release()
+ .addProgramClasses(A.class, B.class, C.class, D.class, E.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().setProguardInputMapFile(mappingFile))
+ .run(parameters.getRuntime(), A.class)
+ .assertSuccessWithOutputLines("Hello world!")
+ .app()
+ .writeToDirectory(outputDir, OutputMode.DexIndexed);
+ Map<String, String> mapping = new HashMap<>();
+ for (String dexFile : ImmutableList.of("classes.dex", "classes2.dex", "classes3.dex")) {
+ Path resolvedDexFile = outputDir.resolve(dexFile);
+ assertTrue(Files.exists(resolvedDexFile));
+ classNamesFromDexFile(resolvedDexFile).forEach(name -> mapping.put(name, dexFile));
+ }
+ // Check that the classes are grouped by their original package name.
+ assertEquals(mapping.get(B.class.getTypeName()), mapping.get(C.class.getTypeName()));
+ assertEquals(mapping.get(D.class.getTypeName()), mapping.get(E.class.getTypeName()));
+ assertTrue(
+ mapping.values().stream().filter(s -> s.equals(mapping.get(A.class.getTypeName()))).count()
+ == 1);
+ }
+
+ static class A {
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ static class B {
+ public static void foo() {
+ System.out.println("foo");
+ }
+ }
+
+ static class C {
+ public static void bar() {
+ System.out.println("bar");
+ }
+ }
+
+ static class D {
+ public static void foobar() {
+ System.out.println("foobar");
+ }
+ }
+
+ static class E {
+ public static void barfoo() {
+ System.out.println("barfoo");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
index 63f1aa6..674f288 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
@@ -4,9 +4,13 @@
package com.android.tools.r8.desugar;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -24,31 +28,87 @@
public class DesugarToClassFileB156591935 extends TestBase implements Opcodes {
@Parameterized.Parameters(name = "{0}")
- public static AndroidApiLevel[] data() {
- return AndroidApiLevel.values();
+ public static TestParametersCollection data() {
+ return TestParametersBuilder.builder().withCfRuntimes().withAllApiLevelsAlsoForCf().build();
}
private final AndroidApiLevel apiLevel;
- public DesugarToClassFileB156591935(AndroidApiLevel apiLevel) {
- this.apiLevel = apiLevel;
+ public DesugarToClassFileB156591935(TestParameters parameters) {
+ this.apiLevel = parameters.getApiLevel();
}
- private void expectNops(CodeInspector inspector, int numberOfNops) {
- ClassSubject a = inspector.clazz("A");
- assertEquals(
- numberOfNops, a.clinit().streamInstructions().filter(InstructionSubject::isNop).count());
+ private void expectNoNops(String className, CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(className);
+ assertFalse(classSubject.clinit().streamInstructions().anyMatch(InstructionSubject::isNop));
+ }
+
+ private void expectNoLoad(String className, CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(className);
+ assertFalse(
+ classSubject
+ .clinit()
+ .streamInstructions()
+ .anyMatch(instructionSubject -> instructionSubject.asCfInstruction().isLoad()));
+ }
+
+ private void expectNoStore(String className, CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(className);
+ assertFalse(
+ classSubject
+ .clinit()
+ .streamInstructions()
+ .anyMatch(instructionSubject -> instructionSubject.asCfInstruction().isStore()));
+ }
+
+ private void expectNoSwap(String className, CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(className);
+ assertFalse(
+ classSubject
+ .clinit()
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ instructionSubject.asCfInstruction().isStackInstruction(Opcode.Swap)));
}
@Test
public void test() throws Exception {
// No nops in the input - see dump below.
- // TODO(b/156591935): The three nops should be avoided.
testForD8(Backend.CF)
.addProgramClassFileData(dump())
.setMinApi(apiLevel)
.compile()
- .inspect(inspector -> expectNops(inspector, 3));
+ .inspect(inspector -> expectNoNops("A", inspector))
+ .inspect(inspector -> expectNoLoad("A", inspector))
+ .inspect(inspector -> expectNoStore("A", inspector))
+ .inspect(inspector -> expectNoSwap("A", inspector));
+ }
+
+ @Test
+ public void testWithExtraLine() throws Exception {
+ // No nops in the input - see dump below.
+ testForD8(Backend.CF)
+ .addProgramClassFileData(dumpWithExtraLine())
+ .setMinApi(apiLevel)
+ .compile()
+ .inspect(inspector -> expectNoNops("A", inspector))
+ .inspect(inspector -> expectNoLoad("A", inspector))
+ .inspect(inspector -> expectNoStore("A", inspector))
+ .inspect(inspector -> expectNoSwap("A", inspector));
+ }
+
+ @Test
+ public void testWithMoreConsts() throws Exception {
+ // No nops in the input - see dump below.
+ testForD8(Backend.CF)
+ .addProgramClassFileData(dumpMoreConsts())
+ .setMinApi(apiLevel)
+ .compile()
+ .inspect(inspector -> expectNoNops("B", inspector))
+ .inspect(inspector -> expectNoLoad("B", inspector))
+ .inspect(inspector -> expectNoStore("B", inspector))
+ .inspect(inspector -> expectNoSwap("B", inspector));
}
/*
@@ -86,7 +146,7 @@
}
}
*/
- public static byte[] dump() {
+ private static byte[] dump() {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
@@ -251,4 +311,460 @@
return classWriter.toByteArray();
}
+
+ // Patched version of the code above. Added an additional line change between the two const
+ // instructions for the "createA" calls in <clinit>. Look for label variable names ending in x.
+ private static byte[] dumpWithExtraLine() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+ classWriter.visitSource("A.java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_1", "LA;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_2", "LA;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_3", "LA;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value", "I", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "name", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PRIVATE | ACC_STATIC, "createA", "(ILjava/lang/String;)LA;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(69, label0);
+ methodVisitor.visitTypeInsn(NEW, "A");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "(ILjava/lang/String;)V", false);
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("value", "I", null, label0, label1, 0);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 1);
+ methodVisitor.visitMaxs(4, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "<init>", "(ILjava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(73, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitFieldInsn(PUTFIELD, "A", "value", "I");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(74, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ methodVisitor.visitFieldInsn(PUTFIELD, "A", "name", "Ljava/lang/String;");
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(75, label3);
+ methodVisitor.visitInsn(RETURN);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLocalVariable("this", "LA;", null, label0, label4, 0);
+ methodVisitor.visitLocalVariable("value", "I", null, label0, label4, 1);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label4, 2);
+ methodVisitor.visitMaxs(2, 3);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getValue", "()I", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(78, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "A", "value", "I");
+ methodVisitor.visitInsn(IRETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getName", "()Ljava/lang/String;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(82, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "A", "name", "Ljava/lang/String;");
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(67, label0);
+ methodVisitor.visitInsn(ICONST_1);
+ Label label0x = new Label();
+ methodVisitor.visitLabel(label0x);
+ methodVisitor.visitLineNumber(68, label0x);
+ methodVisitor.visitLdcInsn("FIRST");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(69, label1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_1", "LA;");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(70, label2);
+ methodVisitor.visitInsn(ICONST_1);
+ Label label2x = new Label();
+ methodVisitor.visitLabel(label2x);
+ methodVisitor.visitLineNumber(71, label2x);
+ methodVisitor.visitLdcInsn("SECOND");
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(72, label3);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_2", "LA;");
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(73, label4);
+ methodVisitor.visitInsn(ICONST_1);
+ Label label4x = new Label();
+ methodVisitor.visitLabel(label4x);
+ methodVisitor.visitLineNumber(74, label4x);
+ methodVisitor.visitLdcInsn("THIRD");
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(75, label5);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_3", "LA;");
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(74, label6);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ /*
+ Dump of the compiled code for ths class below. The dump has not been modified, but the
+ code needs to have the specific line breaks for javac to insert the line numbers in the
+ way that this test is about. Used a dump to avoid source formatting invalidating the test.
+
+ This is the same pattern like class A above, just with more constants attributed to the
+ same line.
+
+ class B {
+ // The line break before createA is needed for the expected line info.
+ public static final B B_1 =
+ createB(1, 2, 3, "FIRST");
+ public static final B B_2 =
+ createB(1, 2, 3, "SECOND");
+ public static final B B_3 =
+ createB(1, 2, 3, "THIRD");
+
+ private final int value1;
+ private final int value2;
+ private final int value3;
+ private final String name;
+
+ private static B createB(int value1, int value2, int value3, String name) {
+ return new B(value1, value2, value3, name);
+ }
+
+ private B(int value1, int value2, int value3, String name) {
+ this.value1 = value1;
+ this.value2 = value2;
+ this.value3 = value3;
+ this.name = name;
+ }
+
+ int getValue1() {
+ return value1;
+ }
+
+ int getVlaue2() {
+ return value2;
+ }
+
+ int getValue3() {
+ return value3;
+ }
+
+ String getName() {
+ return name;
+ }
+ }
+ */
+ public static byte[] dumpMoreConsts() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V11, ACC_SUPER, "B", null, "java/lang/Object", null);
+
+ classWriter.visitSource("B.java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "B_1", "LB;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "B_2", "LB;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "B_3", "LB;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value1", "I", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value2", "I", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value3", "I", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "name", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PRIVATE | ACC_STATIC, "createB", "(IIILjava/lang/String;)LB;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(16, label0);
+ methodVisitor.visitTypeInsn(NEW, "B");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitVarInsn(ILOAD, 2);
+ methodVisitor.visitVarInsn(ALOAD, 3);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "B", "<init>", "(IIILjava/lang/String;)V", false);
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("value1", "I", null, label0, label1, 0);
+ methodVisitor.visitLocalVariable("value2", "I", null, label0, label1, 1);
+ methodVisitor.visitLocalVariable("value3", "I", null, label0, label1, 2);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 3);
+ methodVisitor.visitMaxs(6, 4);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "<init>", "(IIILjava/lang/String;)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);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(20, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitFieldInsn(PUTFIELD, "B", "value1", "I");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(21, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 2);
+ methodVisitor.visitFieldInsn(PUTFIELD, "B", "value2", "I");
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(22, label3);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 3);
+ methodVisitor.visitFieldInsn(PUTFIELD, "B", "value3", "I");
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(23, label4);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 4);
+ methodVisitor.visitFieldInsn(PUTFIELD, "B", "name", "Ljava/lang/String;");
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(24, label5);
+ methodVisitor.visitInsn(RETURN);
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLocalVariable("this", "LB;", null, label0, label6, 0);
+ methodVisitor.visitLocalVariable("value1", "I", null, label0, label6, 1);
+ methodVisitor.visitLocalVariable("value2", "I", null, label0, label6, 2);
+ methodVisitor.visitLocalVariable("value3", "I", null, label0, label6, 3);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label6, 4);
+ methodVisitor.visitMaxs(2, 5);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getValue1", "()I", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(27, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "B", "value1", "I");
+ methodVisitor.visitInsn(IRETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getVlaue2", "()I", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(31, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "B", "value2", "I");
+ methodVisitor.visitInsn(IRETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getValue3", "()I", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(35, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "B", "value3", "I");
+ methodVisitor.visitInsn(IRETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getName", "()Ljava/lang/String;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(39, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "B", "name", "Ljava/lang/String;");
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LB;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(ICONST_3);
+ methodVisitor.visitLdcInsn("FIRST");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "B", "createB", "(IIILjava/lang/String;)LB;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "B", "B_1", "LB;");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(5, label2);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(ICONST_3);
+ methodVisitor.visitLdcInsn("SECOND");
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(6, label3);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "B", "createB", "(IIILjava/lang/String;)LB;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "B", "B_2", "LB;");
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(7, label4);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(ICONST_3);
+ methodVisitor.visitLdcInsn("THIRD");
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(8, label5);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "B", "createB", "(IIILjava/lang/String;)LB;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "B", "B_3", "LB;");
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(7, label6);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
new file mode 100644
index 0000000..667116e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.staticinterfacemethod;
+
+import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticInterfaceNestedTest.Library.foo;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 InvokeStaticInterfaceNestedTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean cfToCfDesugar;
+ private final String EXPECTED = "Hello World!";
+
+ @Parameters(name = "{0}, desugar: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public InvokeStaticInterfaceNestedTest(TestParameters parameters, boolean cfToCfDesugar) {
+ this.parameters = parameters;
+ this.cfToCfDesugar = cfToCfDesugar;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ final SingleTestRunResult<?> runResult =
+ testForRuntime(parameters)
+ .addProgramClassFileData(
+ rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
+ rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
+ .run(parameters.getRuntime(), Main.class);
+ if (parameters.isCfRuntime() && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+ runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+ } else {
+ // TODO(b/166247515): This should be ICCE.
+ runResult.assertSuccessWithOutputLines(EXPECTED);
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ final R8FullTestBuilder testBuilder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
+ rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
+ .addKeepAllClassesRule()
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ options.cfToCfDesugar = cfToCfDesugar;
+ });
+ if (parameters.isCfRuntime()) {
+ // TODO(b/166213037): Should not throw an error.
+ assertThrows(CompilationFailedException.class, testBuilder::compile);
+ } else {
+ testBuilder.run(parameters.getRuntime(), Main.class).assertSuccessWithOutputLines(EXPECTED);
+ }
+ }
+
+ private byte[] rewriteToUseNonInterfaceMethodReference(Class<?> clazz, String methodName)
+ throws Exception {
+ return transformer(clazz)
+ .transformMethodInsnInMethod(
+ methodName,
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ assertTrue(isInterface);
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, false);
+ })
+ .transform();
+ }
+
+ public interface Library {
+
+ static void foo() {
+ bar(); // <-- will be rewritten to invoke-static Library::bar();
+ }
+
+ static void bar() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ foo(); // <-- will be rewritten to invoke-static Library::foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumWithNonDefaultForwardingConstructorTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithNonDefaultForwardingConstructorTest.java
new file mode 100644
index 0000000..92b0b54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithNonDefaultForwardingConstructorTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumWithNonDefaultForwardingConstructorTest extends TestBase {
+
+ private final boolean enableEnumUnboxing;
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{1}, enum unboxing: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public EnumWithNonDefaultForwardingConstructorTest(
+ boolean enableEnumUnboxing, TestParameters parameters) {
+ this.enableEnumUnboxing = enableEnumUnboxing;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeFalse(enableEnumUnboxing);
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .apply(this::addProgramClasses)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::addProgramClasses)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.enableEnumUnboxing = enableEnumUnboxing)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ // TODO(b/160939354): Should succeed with 42.
+ .assertSuccessWithOutputLines(enableEnumUnboxing ? "0" : "42");
+ }
+
+ private void addProgramClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(
+ transformer(MyEnum.class)
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ if (getContext().getReference().getMethodName().equals("<init>")) {
+ // Pass the value of the third argument (ignoring the receiver) to the
+ // parent constructor instead of the value of the second argument.
+ super.visitVarInsn(opcode, var == 2 ? 3 : var);
+ return;
+ }
+ super.visitVarInsn(opcode, var);
+ }
+ })
+ .transform());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ MyEnum instance = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+ System.out.println(instance.ordinal());
+ }
+ }
+
+ enum MyEnum {
+ A(42),
+ B(43);
+
+ MyEnum(int ordinal) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
index b6e6c2e..75eb79f 100644
--- a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
@@ -137,7 +137,8 @@
nextPc,
new Position(nextLine, null, method, null),
events,
- factory);
+ factory,
+ false);
consumer.accept(events);
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
index eaf7d56..2b0324c 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
@@ -4,17 +4,16 @@
package com.android.tools.r8.graph.invokespecial;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
import static org.junit.Assert.assertEquals;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -24,6 +23,8 @@
@RunWith(Parameterized.class)
public class InvokeSpecialForInvokeVirtualTest extends TestBase {
+ private static final String EXPECTED = StringUtils.lines("Hello World!");
+
private final TestParameters parameters;
@Parameters(name = "{0}")
@@ -36,13 +37,24 @@
}
@Test
- public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
- TestRunResult<?> runResult =
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
- .addProgramClasses(A.class, Main.class)
- .addProgramClassFileData(getClassBWithTransformedInvoked())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Hello World!");
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/166210854): Fails but should not.
+ .assertFailure();
}
private byte[] getClassBWithTransformedInvoked() throws IOException {
@@ -51,6 +63,7 @@
"bar",
(opcode, owner, name, descriptor, isInterface, continuation) -> {
assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
})
.transform();
@@ -66,7 +79,7 @@
public static class B extends A {
void bar() {
- foo();
+ foo(); // Will be invoke-special B::foo.
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
index 9b9a8eb..b9156fb 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
@@ -4,17 +4,16 @@
package com.android.tools.r8.graph.invokespecial;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
import static org.junit.Assert.assertEquals;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -24,6 +23,8 @@
@RunWith(Parameterized.class)
public class InvokeSpecialForNonDeclaredInvokeVirtualTest extends TestBase {
+ private static final String EXPECTED = StringUtils.lines("Hello World!");
+
private final TestParameters parameters;
@Parameters(name = "{0}")
@@ -36,13 +37,24 @@
}
@Test
- public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
- TestRunResult<?> runResult =
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
- .addProgramClasses(A.class, B.class, Main.class)
- .addProgramClassFileData(getClassCWithTransformedInvoked())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Hello World!");
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addProgramClassFileData(getClassCWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addProgramClassFileData(getClassCWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/166210854): Fails but should not.
+ .assertFailure();
}
private byte[] getClassCWithTransformedInvoked() throws IOException {
@@ -51,6 +63,7 @@
"bar",
(opcode, owner, name, descriptor, isInterface, continuation) -> {
assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals(getBinaryNameFromJavaType(C.class.getTypeName()), owner);
continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
})
.transform();
@@ -68,7 +81,7 @@
public static class C extends B {
void bar() {
- foo();
+ foo(); // Will be invoke-special C::foo
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
index 7a51803..5119689 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
@@ -57,6 +57,8 @@
}
}
+ // TODO(b/166210854): Test behavior on R8 too.
+
private byte[] getClassWithTransformedInvoked() throws IOException {
return transformer(B.class)
.transformMethodInsnInMethod(
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge2Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge2Test.java
new file mode 100644
index 0000000..7d666aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge2Test.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+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 InvokeSpecialInterfaceWithBridge2Test extends TestBase {
+
+ private static final String EXPECTED = StringUtils.lines("Hello World!");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialInterfaceWithBridge2Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals(owner, getBinaryNameFromJavaType(B.class.getTypeName()));
+ continuation.visitMethodInsn(
+ INVOKESPECIAL,
+ getBinaryNameFromJavaType(A.class.getTypeName()),
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transform();
+ }
+
+ public interface I {
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B extends A {
+
+ public void bar() {
+ foo(); // Will be rewritten to invoke-special A.foo()
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
new file mode 100644
index 0000000..e5b0bb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.List;
+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 InvokeSpecialInterfaceWithBridge3Test extends TestBase {
+
+ private static final String EXPECTED = StringUtils.lines("Hello World!");
+
+ private final TestParameters parameters;
+ private final boolean isInterface;
+
+ @Parameters(name = "{0}, itf:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public InvokeSpecialInterfaceWithBridge3Test(TestParameters parameters, boolean isInterface) {
+ this.parameters = parameters;
+ this.isInterface = isInterface;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ TestRunResult<?> result =
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class);
+ if (parameters.isDexRuntime()) {
+ // TODO(b/166210854): Runs but should have failed.
+ result.assertSuccessWithOutput(EXPECTED);
+ } else {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/166210854): Runs but should have failed.
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals(owner, getBinaryNameFromJavaType(B.class.getTypeName()));
+ continuation.visitMethodInsn(
+ INVOKESPECIAL,
+ getBinaryNameFromJavaType(I.class.getTypeName()),
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transform();
+ }
+
+ public interface I {
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B extends A {
+
+ public void bar() {
+ foo(); // Will be rewritten to invoke-special I.foo()
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
index 3536adb..31ec2c6 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
@@ -8,14 +8,11 @@
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -37,13 +34,24 @@
}
@Test
- public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
- TestRunResult<?> runResult =
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
- .addProgramClasses(I.class, A.class, Main.class)
- .addProgramClassFileData(getClassWithTransformedInvoked())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Hello World!");
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/166210854): Fails but should not.
+ .assertFailure();
}
private byte[] getClassWithTransformedInvoked() throws IOException {
@@ -69,7 +77,7 @@
public static class B extends A {
public void bar() {
- foo(); // Will be rewritten to invoke-special A.foo()
+ foo(); // Will be rewritten to invoke-special B.foo()
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
index 93a94e9..fe03361 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
@@ -9,14 +9,11 @@
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -38,13 +35,23 @@
}
@Test
- public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
- TestRunResult<?> runResult =
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
- .addProgramClasses(A.class, Main.class)
- .addProgramClassFileData(getClassWithTransformedInvoked())
- .run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
}
private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
new file mode 100644
index 0000000..183ae6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+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 InvokeSpecialOnOtherInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialOnOtherInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(I.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(
+ parameters.isCfRuntime()
+ ? VerifyError.class
+ // TODO(b/144410139): Consider making this a compilation failure.
+ : NoClassDefFoundError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(
+ parameters.isCfRuntime()
+ ? VerifyError.class
+ // TODO(b/144410139): Consider making this a compilation failure.
+ : NoClassDefFoundError.class);
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(A.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(getBinaryNameFromJavaType(I.class.getTypeName()), owner);
+ continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public interface I {
+
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class A {
+
+ public void bar(I i) {
+ i.foo(); // Will be rewritten to invoke-special I.foo()
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().bar(new I() {});
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
index 5bc6d22..48790e3 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -35,7 +34,7 @@
}
@Test
- public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ public void testRuntime() throws Exception {
try {
testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
.addProgramClasses(Main.class)
@@ -51,6 +50,25 @@
}
}
+ @Test
+ public void testR8() throws Exception {
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ // TODO(b/110175213): Remove when fixed.
+ assertTrue(parameters.isCfRuntime());
+ } catch (CompilationFailedException compilation) {
+ assertThat(
+ compilation.getCause().getMessage(),
+ containsString("Failed to compile unsupported use of invokespecial"));
+ }
+ }
+
private byte[] getClassWithTransformedInvoked() throws IOException {
return transformer(A.class)
.transformMethodInsnInMethod(
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
index 730ae81..400e015 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
@@ -47,6 +47,7 @@
.assertSuccessWithOutput(EXPECTED);
}
+
@Test(expected = CompilationFailedException.class)
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
@@ -62,6 +63,21 @@
diagnosticMessage(containsString("unsupported use of invokespecial"))));
}
+ @Test(expected = CompilationFailedException.class)
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, Bar.class, TestClass.class)
+ .addProgramClassFileData(getFooTransform())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ diagnosticMessage(containsString("unsupported use of invokespecial"))));
+ }
+
@Test
public void testDX() throws Exception {
assumeTrue(parameters.isDexRuntime());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
new file mode 100644
index 0000000..a8abf65
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.RetraceTestBase;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+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 InlineMappingOnSameLineTest extends RetraceTestBase {
+
+ @Parameters(name = "{0}, mode: {1}, compat: {2}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+ CompilationMode.values(),
+ BooleanUtils.values());
+ }
+
+ public InlineMappingOnSameLineTest(
+ TestParameters parameters, CompilationMode mode, boolean isCompat) {
+ super(parameters, mode, isCompat);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
+ (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
+ assertThat(retracedStackTrace, isSame(expectedStackTrace));
+ });
+ }
+
+ @Override
+ public Class<?> getMainClass() {
+ return Main.class;
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ foo(args.length);
+ }
+
+ @NeverInline
+ public static void foo(int arg) {
+ bar(arg);
+ f(arg);
+ }
+
+ public static void bar(int arg) {
+ f(arg);
+ g(arg);
+ }
+
+ public static void f(int arg) {
+ if (arg == 0) {
+ throw new RuntimeException("In f()");
+ }
+ }
+
+ public static void g(int arg) {
+ if (arg == 1) {
+ throw new RuntimeException("In g()");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
similarity index 71%
copy from src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
copy to src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
index f049056..e505e23 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
@@ -19,7 +20,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase {
+public class DesugarStaticInterfaceMethodDirectRetraceTest extends RetraceTestBase {
@Parameters(name = "{0}, mode: {1}, compat: {2}")
public static Collection<Object[]> data() {
@@ -29,20 +30,24 @@
BooleanUtils.values());
}
- public DesugarStaticInterfaceMethodsRetraceTest(
+ public DesugarStaticInterfaceMethodDirectRetraceTest(
TestParameters parameters, CompilationMode mode, boolean compat) {
super(parameters, mode, compat);
}
@Override
public Collection<Class<?>> getClasses() {
- return ImmutableList.of(
- getMainClass(), InterfaceWithStaticMethod1.class, InterfaceWithStaticMethod2.class);
+ return ImmutableList.of(getMainClass(), InterfaceWithStaticMethod.class);
}
@Override
public Class<?> getMainClass() {
- return MainDesugarStaticInterfaceMethodsRetraceTest.class;
+ return MainDesugarStaticInterfaceMethodRetraceTest.class;
+ }
+
+ @Override
+ public void configure(R8TestBuilder<?> builder) {
+ builder.enableInliningAnnotations();
}
@Test
@@ -52,33 +57,26 @@
// For the desugaring to companion classes the retrace stacktrace is still the same
// as the mapping file has a fully qualified class name in the method mapping, e.g.:
//
- // com.android.tools.r8.naming.retrace.InterfaceWithDefaultMethod1$-CC
+ // com.android.tools.r8.naming.retrace.InterfaceWithStaticMethod$-CC
// -> com.android.tools.r8.naming.retrace.a:1:1:void
- // com.android.tools.r8.naming.retrace.InterfaceWithDefaultMethod1.defaultMethod1():80:80
+ // com.android.tools.r8.naming.retrace.InterfaceWithStaticMethod$.staticMethod():80:80
// -> a
(StackTrace actualStackTrace, StackTrace retracedStackTrace) ->
assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace)));
}
}
-interface InterfaceWithStaticMethod2 {
+interface InterfaceWithStaticMethod {
- static void staticMethod1() {
+ @NeverInline
+ static void staticMethod() {
throw null;
}
}
-interface InterfaceWithStaticMethod1 {
-
- @NeverInline
- static void staticMethod2() {
- InterfaceWithStaticMethod2.staticMethod1();
- }
-}
-
-class MainDesugarStaticInterfaceMethodsRetraceTest {
+class MainDesugarStaticInterfaceMethodRetraceTest {
public static void main(String[] args) {
- InterfaceWithStaticMethod1.staticMethod2();
+ InterfaceWithStaticMethod.staticMethod();
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
similarity index 86%
rename from src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
rename to src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
index f049056..7904ede 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
@@ -19,7 +20,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase {
+public class DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest extends RetraceTestBase {
@Parameters(name = "{0}, mode: {1}, compat: {2}")
public static Collection<Object[]> data() {
@@ -29,12 +30,17 @@
BooleanUtils.values());
}
- public DesugarStaticInterfaceMethodsRetraceTest(
+ public DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest(
TestParameters parameters, CompilationMode mode, boolean compat) {
super(parameters, mode, compat);
}
@Override
+ public void configure(R8TestBuilder<?> builder) {
+ builder.enableInliningAnnotations();
+ }
+
+ @Override
public Collection<Class<?>> getClasses() {
return ImmutableList.of(
getMainClass(), InterfaceWithStaticMethod1.class, InterfaceWithStaticMethod2.class);
@@ -63,7 +69,7 @@
interface InterfaceWithStaticMethod2 {
- static void staticMethod1() {
+ static void staticMethod2() {
throw null;
}
}
@@ -71,14 +77,14 @@
interface InterfaceWithStaticMethod1 {
@NeverInline
- static void staticMethod2() {
- InterfaceWithStaticMethod2.staticMethod1();
+ static void staticMethod1() {
+ InterfaceWithStaticMethod2.staticMethod2();
}
}
class MainDesugarStaticInterfaceMethodsRetraceTest {
public static void main(String[] args) {
- InterfaceWithStaticMethod1.staticMethod2();
+ InterfaceWithStaticMethod1.staticMethod1();
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 4dd2fb0..0c4f597 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -233,23 +233,6 @@
// \tat com.android.tools.r8.naming.retrace.Main.a(:150)
// \tat com.android.tools.r8.naming.retrace.Main.a(:156)
// \tat com.android.tools.r8.naming.retrace.Main.main(:162)
- // TODO(122940268): Remove test code when fixed.
- System.out.println("TOTAL STDERR LINES: " + stderrLines.size());
- for (int i = 0; i < stderrLines.size(); i++) {
- System.out.print("LINE " + i + ": " + stderrLines.get(i));
- if (stderrLines.get(i).length() > 3) {
- System.out.print(" (" + ((int) stderrLines.get(i).charAt(0)));
- System.out.print(", " + ((int) stderrLines.get(i).charAt(1)));
- System.out.print(", " + ((int) stderrLines.get(i).charAt(2) + ")"));
- } else {
- System.out.print(" (less than three chars)");
- }
- if (stderrLines.get(i).startsWith(TAB_AT_PREFIX)) {
- System.out.println(" IS STACKTRACE LINE");
- } else {
- System.out.println(" IS NOT STACKTRACE LINE");
- }
- }
for (int i = 0; i < stderrLines.size(); i++) {
String line = stderrLines.get(i);
// Find all lines starting with "\tat" except "dalvik.system.NativeStart.main" frame
diff --git a/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java b/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
new file mode 100644
index 0000000..0d2f1d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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.regress.b163264839;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Handle;
+
+@RunWith(Parameterized.class)
+public class Regress163264839Test extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+ private final boolean isInterface;
+
+ @Parameterized.Parameters(name = "{0}, itf:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withApiLevel(AndroidApiLevel.L).build(),
+ BooleanUtils.values());
+ }
+
+ public Regress163264839Test(TestParameters parameters, boolean isInterface) {
+ this.parameters = parameters;
+ this.isInterface = isInterface;
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestRunResult<?> result =
+ testForRuntime(parameters)
+ .addProgramClassFileData(getFunctionClass())
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class);
+
+ if (isInterface
+ || (parameters.isCfRuntime() && parameters.getRuntime().asCf().getVm().equals(CfVm.JDK8))) {
+ // JDK 8 allows mismatched method references in this case.
+ result.assertSuccessWithOutput(EXPECTED);
+ } else {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+ }
+
+ private byte[] getFunctionClass() throws Exception {
+ // HACK: Use a new name for the lambda implementation method as otherwise ASM will silently
+ // change isInterface to the "right" value and we can't test the error case.
+ String oldLambdaName = "lambda$identity$0";
+ String newLambdaName = "lambda$identity$foo";
+ return transformer(Function.class)
+ .renameMethod(oldLambdaName, newLambdaName)
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitInvokeDynamicInsn(
+ String name,
+ String descriptor,
+ Handle bootstrapMethodHandle,
+ Object... bootstrapMethodArguments) {
+ assertEquals(3, bootstrapMethodArguments.length);
+ Handle handle = (Handle) bootstrapMethodArguments[1];
+ assertTrue(handle.isInterface());
+ assertEquals(oldLambdaName, handle.getName());
+ Handle newHandle =
+ new Handle(
+ handle.getTag(),
+ handle.getOwner(),
+ newLambdaName,
+ handle.getDesc(),
+ isInterface);
+ super.visitInvokeDynamicInsn(
+ name,
+ descriptor,
+ bootstrapMethodHandle,
+ bootstrapMethodArguments[0],
+ newHandle,
+ bootstrapMethodArguments[2]);
+ }
+ })
+ .transform();
+ }
+
+ interface Function<R, T> {
+ R apply(T t);
+
+ static <T> Function<T, T> identity() {
+ return t -> t;
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(Function.identity().apply("Hello, world"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java b/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
new file mode 100644
index 0000000..bf95231
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2020, 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.regress.b165825758;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.RangeSubject;
+import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress165825758Test extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public Regress165825758Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(Regress165825758Test.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableInliningAnnotations()
+ .addInnerClasses(Regress165825758Test.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(A.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkDeadCodeThrowInTryRange);
+ }
+
+ private void checkDeadCodeThrowInTryRange(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject method = classSubject.uniqueMethodWithName("synchronizedMethod");
+ assertThat(method, isPresent());
+
+ // Ensure that the "throwNpe" method remains and that it was not inlined by checking that no
+ // allocations of NullPointerException are in the method.
+ assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isNewInstance));
+ assertThat(inspector.clazz(TestClass.class).uniqueMethodWithName("throwNpe"), isPresent());
+
+ // Source has 2 catch ranges:
+ // 1st try catch is the source range, 2nd is the compiler inserted catch over monitor-exit.
+ // When compiled with R8 the catch ranges are collapsed.
+ List<TryCatchSubject> tryCatchSubjects = method.streamTryCatches().collect(Collectors.toList());
+ assertEquals(1, tryCatchSubjects.size());
+ TryCatchSubject sourceTry = tryCatchSubjects.get(0);
+
+ // 1st throw is the "dead code" throw, the 2nd is the exceptional rethrow after method exit.
+ List<InstructionSubject> throwInstructions =
+ method
+ .streamInstructions()
+ .filter(InstructionSubject::isThrow)
+ .collect(Collectors.toList());
+ assertEquals(2, throwInstructions.size());
+ InstructionSubject deadCodeThrow = throwInstructions.get(0);
+ InstructionOffsetSubject throwOffset = deadCodeThrow.getOffset(method);
+ RangeSubject range = sourceTry.getRange();
+ assertTrue(
+ "Expected throw@" + throwOffset + " to be in try-range " + range,
+ range.includes(throwOffset));
+ }
+
+ static class A {
+
+ @NeverInline
+ void synchronizedMethod() {
+ synchronized (this) {
+ TestClass.throwNpe();
+ System.out.println("Never hit");
+ }
+ }
+ }
+
+ static class TestClass {
+
+ @NeverInline
+ static void throwNpe() {
+ throw new NullPointerException();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new A().synchronizedMethod();
+ } catch (NullPointerException e) {
+ System.out.println("Hello, world");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index f2dc303..acf4aff 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -66,8 +66,7 @@
@Test
public void testVerbose() throws IOException {
FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
- // TODO(b/159562137): Add proper support for -verbose when using regexp.
- runTestNotEquals(
+ runTest(
stackTrace.mapping(),
StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
false,
@@ -78,8 +77,7 @@
@Test
public void testVerboseSingleHyphen() throws IOException {
FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
- // TODO(b/159562137): Add proper support for -verbose when using regexp.
- runTestNotEquals(
+ runTest(
stackTrace.mapping(),
StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
false,
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
index d8768a2..4fb8389 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -394,7 +395,7 @@
}
@Test
- public void testNotFoundLineNumberInMethodContext() {
+ public void testNoLineNumberInMethodContext() {
runRetraceTest(
"%c\\.%m\\(%l\\)",
new StackTraceForTest() {
@@ -411,7 +412,35 @@
@Override
public List<String> retracedStackTrace() {
- return ImmutableList.of("a.b.c.a()");
+ return ImmutableList.of("com.android.tools.r8.R8.foo()");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testNotFoundLineNumberInMethodContext() {
+ runRetraceTest(
+ "%c\\.%m\\(%l\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a(42)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.R8 -> a.b.c:", " 3:3:boolean foo():7 -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.a(42)");
}
@Override
@@ -539,6 +568,7 @@
}
@Test
+ @Ignore("b/165782924")
public void useReturnTypeToNarrowMethodMatches() {
runRetraceTest(
"%t %c.%m",
@@ -662,6 +692,7 @@
}
@Test
+ @Ignore("b/165782924")
public void testPruningOfMethodsByFormals() {
runRetraceTest(
"%c.%m\\(%a\\)",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
index 53aa7c6..d57b024 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
@@ -45,59 +45,6 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
- "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
- + " because method is not defined on line number 21",
- "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
- + " because method is not defined on line number 21",
- "Pruning \tat com.android.tools.r8.utils.Reporter.fatalError(Reporter.java:21) from result"
- + " because method is not defined on line number 21",
- "Pruning \tat"
- + " com.android.tools.r8.utils.Reporter.addSuppressedExceptions(Reporter.java:21) from"
- + " result because method is not defined on line number 21",
- "Pruning \tat"
- + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
- + " from result because method is not defined on line number 19",
- "Pruning \tat"
- + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
- + " from result because method is not defined on line number 19",
- "Pruning \tat"
- + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
- + " from result because method is not in range on line number 19",
- "Pruning \tat"
- + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
- + " from result because method is not in range on line number 19",
- "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
- + " result because method is not defined on line number 11",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:11)"
- + " from result because method is not defined on line number 11",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:11)"
- + " from result because method is not defined on line number 11",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
- + " from result because method is not defined on line number 11",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
- + " from result because method is not defined on line number 11",
- "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
- + " result because method is not in range on line number 11",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:1)"
- + " from result because method is not defined on line number 1",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:1)"
- + " from result because method is not defined on line number 1",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
- + " from result because method is not defined on line number 1",
- "Pruning \tat"
- + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
- + " from result because method is not defined on line number 1",
- "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
- + " result because method is not defined on line number 1",
- "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
- + " result because method is not defined on line number 1",
"com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
"\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:143)",
"\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
index 4d9418c..ce7a8f5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
@@ -27,7 +27,7 @@
public List<String> retracedStackTrace() {
return Arrays.asList(
"Exception in thread \"main\" java.lang.NullPointerException",
- "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main(java.lang.String[],"
+ "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main(java.lang.String[],"
+ "com.android.Bar)(Main.java:102)");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
index 3eb9710..5a47079 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
@@ -25,7 +25,7 @@
"com.android.tools.r8.naming.retrace.Main -> a.a:",
" com.android.Foo main(java.lang.String[],com.android.Bar) -> a",
" com.android.Foo main(java.lang.String[]) -> b",
- " com.android.Bar main(com.android.Bar) -> b");
+ " void main(com.android.Bar) -> b");
}
@Override
@@ -33,11 +33,11 @@
return Arrays.asList(
"Exception in thread \"main\" java.lang.NullPointerException",
"\tat com.android.tools.r8.naming.retrace.Main.c(Main.java)",
- "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main("
+ "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main("
+ "java.lang.String[])(Main.java)",
- "\t<OR> at com.android.Bar com.android.tools.r8.naming.retrace.Main.main("
+ "\t<OR> at com.android.tools.r8.naming.retrace.Main.void main("
+ "com.android.Bar)(Main.java)",
- "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main("
+ "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main("
+ "java.lang.String[],com.android.Bar)(Main.java)");
}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index c34dc98..c84415e 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -448,6 +448,18 @@
});
}
+ public ClassFileTransformer renameMethod(String oldName, String newName) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ return super.visitMethod(
+ access, name.equals(oldName) ? newName : name, descriptor, signature, exceptions);
+ }
+ });
+ }
+
/** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
@FunctionalInterface
public interface MethodInsnTransform {
diff --git a/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java b/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
index a5ffa82..cb05520 100644
--- a/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
@@ -17,7 +17,7 @@
*/
public class MethodTransformer extends MethodVisitor {
- static class MethodContext {
+ public static class MethodContext {
public final MethodReference method;
public final int accessFlags;
@@ -25,6 +25,10 @@
this.method = method;
this.accessFlags = accessFlags;
}
+
+ public MethodReference getReference() {
+ return method;
+ }
}
private MethodContext context;
@@ -43,7 +47,7 @@
// Package internals.
- MethodContext getContext() {
+ protected MethodContext getContext() {
return context;
}
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 2387655..6cf80fb 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
@@ -31,12 +31,15 @@
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.cf.code.CfSwitch;
import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.Monitor.Type;
import com.android.tools.r8.ir.code.ValueType;
+import java.util.Iterator;
import org.objectweb.asm.Opcodes;
public class CfInstructionSubject implements InstructionSubject {
@@ -55,6 +58,11 @@
}
@Override
+ public CfInstructionSubject asCfInstruction() {
+ return this;
+ }
+
+ @Override
public boolean isFieldAccess() {
return instruction instanceof CfFieldInstruction;
}
@@ -297,6 +305,10 @@
return instruction instanceof CfLoad;
}
+ public boolean isStore() {
+ return instruction instanceof CfStore;
+ }
+
@Override
public boolean isMultiplication() {
if (!(instruction instanceof CfArithmeticBinop)) {
@@ -348,8 +360,17 @@
@Override
public InstructionOffsetSubject getOffset(MethodSubject methodSubject) {
- // TODO(b/122302789): CfInstruction#getOffset()
- throw new UnsupportedOperationException("CfInstruction doesn't have offset yet.");
+ // TODO(b/122302789): Update this if 'offset' is introduced.
+ Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
+ int bci = 0;
+ while (it.hasNext()) {
+ ++bci;
+ InstructionSubject next = it.next();
+ if (next.asCfInstruction().instruction == instruction) {
+ return new InstructionOffsetSubject(bci);
+ }
+ }
+ throw new Unreachable();
}
@Override
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 0335a5d..26944f9 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
@@ -121,6 +121,11 @@
}
@Override
+ public CfInstructionSubject asCfInstruction() {
+ return null;
+ }
+
+ @Override
public boolean isFieldAccess() {
return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticPut();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
index 232884b..58e41ff 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
@@ -12,4 +12,8 @@
this.offset = offset;
}
+ @Override
+ public String toString() {
+ return "" + offset;
+ }
}
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 ad9b5c2..513de02 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
@@ -18,6 +18,8 @@
DexInstructionSubject asDexInstruction();
+ CfInstructionSubject asCfInstruction();
+
boolean isFieldAccess();
boolean isInstancePut();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
index 12e3a4e..18c024a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
@@ -18,4 +18,9 @@
public boolean includes(InstructionOffsetSubject offsetSubject) {
return this.start <= offsetSubject.offset && offsetSubject.offset <= this.end;
}
+
+ @Override
+ public String toString() {
+ return "[" + start + ":" + end + "]";
+ }
}
diff --git a/tools/archive.py b/tools/archive.py
index a3e15ba..f1410a5 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -233,13 +233,15 @@
# Upload desugar_jdk_libs configuration to a maven compatible location.
if file == utils.DESUGAR_CONFIGURATION:
- jar_name = 'desugar_jdk_libs_configuration-%s.jar' % version
+ jar_basename = 'desugar_jdk_libs_configuration.jar'
+ jar_version_name = 'desugar_jdk_libs_configuration-%s.jar' % version
maven_dst = GetUploadDestination(
utils.get_maven_path('desugar_jdk_libs_configuration', version),
- jar_name, is_master)
+ jar_version_name, is_master)
with utils.TempDir() as tmp_dir:
- desugar_jdk_libs_configuration_jar = os.path.join(tmp_dir, jar_name)
+ desugar_jdk_libs_configuration_jar = os.path.join(tmp_dir,
+ jar_version_name)
create_maven_release.generate_jar_with_desugar_configuration(
utils.DESUGAR_CONFIGURATION,
utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP,
@@ -251,11 +253,17 @@
if options.dry_run_output:
shutil.copyfile(
desugar_jdk_libs_configuration_jar,
- os.path.join(options.dry_run_output, jar_name))
+ os.path.join(options.dry_run_output, jar_version_name))
else:
utils.upload_file_to_cloud_storage(
desugar_jdk_libs_configuration_jar, maven_dst)
print('Maven repo root available at: %s' % GetMavenUrl(is_master))
+ # Also archive the jar as non maven destination for Google3
+ jar_destination = GetUploadDestination(
+ version, jar_basename, is_master)
+ utils.upload_file_to_cloud_storage(
+ desugar_jdk_libs_configuration_jar, jar_destination)
+
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 42dcc63..f92afde 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -312,21 +312,22 @@
gradle.RunGradle([utils.R8LIB, '-Pno_internal'])
version = determine_version()
- # Generate the pom file.
- pom_file = 'r8.pom'
- write_pom_file(
- R8_POMTEMPLATE,
- pom_file,
- version,
- "" if is_r8lib else generate_dependencies(),
- generate_library_licenses() if is_r8lib else "")
- # Write the maven zip file.
- generate_maven_zip(
- 'r8',
- version,
- pom_file,
- utils.R8LIB_JAR if is_r8lib else utils.R8_JAR,
- out)
+ with utils.TempDir() as tmp_dir:
+ # Generate the pom file.
+ pom_file = join(tmp_dir, 'r8.pom')
+ write_pom_file(
+ R8_POMTEMPLATE,
+ pom_file,
+ version,
+ "" if is_r8lib else generate_dependencies(),
+ generate_library_licenses() if is_r8lib else "")
+ # Write the maven zip file.
+ generate_maven_zip(
+ 'r8',
+ version,
+ pom_file,
+ utils.R8LIB_JAR if is_r8lib else utils.R8_JAR,
+ out)
# Write the desugaring configuration of a jar file with the following content:
# java/
@@ -369,20 +370,20 @@
# Generate the maven zip for the configuration to desugar desugar_jdk_libs.
def generate_desugar_configuration_maven_zip(out):
- version = utils.desugar_configuration_version()
- # Generate the pom file.
- pom_file = 'desugar_configuration.pom'
- write_pom_file(DESUGAR_CONFIGUATION_POMTEMPLATE, pom_file, version)
- # Generate the jar with the configuration file.
- jar_file = 'desugar_configuration.jar'
- generate_jar_with_desugar_configuration(
- utils.DESUGAR_CONFIGURATION,
- utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP,
- jar_file)
- # Write the maven zip file.
- generate_maven_zip(
- 'desugar_jdk_libs_configuration', version, pom_file, jar_file, out)
-
+ with utils.TempDir() as tmp_dir:
+ version = utils.desugar_configuration_version()
+ # Generate the pom file.
+ pom_file = join(tmp_dir, 'desugar_configuration.pom')
+ write_pom_file(DESUGAR_CONFIGUATION_POMTEMPLATE, pom_file, version)
+ # Generate the jar with the configuration file.
+ jar_file = join(tmp_dir, 'desugar_configuration.jar')
+ generate_jar_with_desugar_configuration(
+ utils.DESUGAR_CONFIGURATION,
+ utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP,
+ jar_file)
+ # Write the maven zip file.
+ generate_maven_zip(
+ 'desugar_jdk_libs_configuration', version, pom_file, jar_file, out)
def main(argv):
options = parse_options(argv)
diff --git a/tools/r8_release.py b/tools/r8_release.py
index e813756..dce7dad 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -357,11 +357,17 @@
g4_open('src.jar')
g4_open('lib.jar')
g4_open('lib.jar.map')
+ g4_open('desugar_jdk_libs.json')
+ g4_open('desugar_jdk_libs_configuration.jar')
download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
download_file(options.version, 'r8-src.jar', 'src.jar')
download_file(options.version, 'r8lib-exclude-deps.jar', 'lib.jar')
download_file(
options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
+ download_file(options.version, 'desugar_jdk_libs.json',
+ 'desugar_jdk_libs.json')
+ download_file(options.version, 'desugar_jdk_libs_configuration.jar',
+ 'desugar_jdk_libs_configuration.jar')
g4_open('METADATA')
sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
options.version,