Implement nest based access desugaring as cf-to-cf
This CL also changes the interface method rewriter to throw a NoSuchMethodError for static invokes to interface methods that do not resolve (see also MissingMethodTest).
Change-Id: Ia8862049d4e48dbc7645d4ef2c32f22a901911a9
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 5774e10..bf85148 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -97,7 +97,7 @@
Enqueuer enqueuer =
EnqueuerFactory.createForFinalMainDexTracing(
- appView, subtypingInfo, graphConsumer, MainDexTracingResult.NONE);
+ appView, executor, subtypingInfo, graphConsumer, MainDexTracingResult.NONE);
Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
// LiveTypes is the result.
MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 212eb32..7f11da5 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -92,7 +92,8 @@
RootSet rootSet =
new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
.run(executor);
- Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
+ Enqueuer enqueuer =
+ EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo);
AppInfoWithLiveness appInfo =
enqueuer.traceApplication(
rootSet, options.getProguardConfiguration().getDontWarnPatterns(), executor, timing);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a75f48f..0a5ef6e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -31,7 +31,6 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.InitClassLens;
@@ -49,8 +48,6 @@
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.NestedPrivateMethodLens;
-import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.ir.optimize.NestReducer;
@@ -420,6 +417,8 @@
}
assert appView.appInfo().hasLiveness();
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
assert verifyNoJarApplicationReaders(appView.appInfo().classes());
// Build conservative main dex content after first round of tree shaking. This is used
// by certain optimizations to avoid introducing additional class references into main dex
@@ -435,7 +434,7 @@
.run(executorService);
// Live types is the tracing result.
Set<DexProgramClass> mainDexBaseClasses =
- EnqueuerFactory.createForInitialMainDexTracing(appView, subtypingInfo)
+ EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
.traceMainDex(mainDexRootSet, executorService, timing);
// Calculate the automatic main dex list according to legacy multidex constraints.
mainDexTracingResult = new MainDexListBuilder(mainDexBaseClasses, appView).run();
@@ -448,7 +447,6 @@
appView.dexItemFactory().clearTypeElementsCache();
if (options.getProguardConfiguration().isAccessModificationAllowed()) {
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
GraphLens publicizedLens =
ClassAndMemberPublicizer.run(
@@ -466,29 +464,13 @@
}
}
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ // This pass attempts to reduce the number of nests and nest size to allow further passes, and
+ // should therefore be run after the publicizer.
+ new NestReducer(appViewWithLiveness).run(executorService, timing);
+
appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run(executorService));
appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
- if (options.shouldDesugarNests()) {
- timing.begin("NestBasedAccessDesugaring");
- R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
- Builder appBuilder = getDirectApp(appView).builder();
- NestedPrivateMethodLens lens = analyzer.run(executorService, appBuilder);
- if (lens != null) {
- appView.rewriteWithLensAndApplication(lens, appBuilder.build());
- }
- timing.end();
- } else {
- timing.begin("NestReduction");
- // This pass attempts to reduce the number of nests and nest size
- // to allow further passes, specifically the class mergers, to do
- // a better job. This pass is better run before the class merger
- // but after the publicizer (cannot be run as part of the Enqueuer).
- new NestReducer(appViewWithLiveness).run(executorService);
- timing.end();
- }
-
boolean isKotlinLibraryCompilationWithInlinePassThrough =
options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
@@ -646,6 +628,7 @@
Enqueuer enqueuer =
EnqueuerFactory.createForFinalMainDexTracing(
appView,
+ executorService,
new SubtypingInfo(appView),
mainDexKeptGraphConsumer,
mainDexTracingResult);
@@ -697,6 +680,7 @@
Enqueuer enqueuer =
EnqueuerFactory.createForFinalTreeShaking(
appView,
+ executorService,
new SubtypingInfo(appView),
keptGraphConsumer,
prunedTypes);
@@ -1014,7 +998,8 @@
SubtypingInfo subtypingInfo,
RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder)
throws ExecutionException {
- Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
+ Enqueuer enqueuer =
+ EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
@@ -1084,12 +1069,16 @@
if (forMainDex) {
enqueuer =
EnqueuerFactory.createForFinalMainDexTracing(
- appView, subtypingInfo, whyAreYouKeepingConsumer, MainDexTracingResult.NONE);
+ appView,
+ executorService,
+ subtypingInfo,
+ whyAreYouKeepingConsumer,
+ MainDexTracingResult.NONE);
enqueuer.traceMainDex(rootSet, executorService, timing);
} else {
enqueuer =
EnqueuerFactory.createForWhyAreYouKeeping(
- appView, subtypingInfo, whyAreYouKeepingConsumer);
+ appView, executorService, subtypingInfo, whyAreYouKeepingConsumer);
enqueuer.traceApplication(
rootSet,
options.getProguardConfiguration().getDontWarnPatterns(),
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index b4b0d5a..d56b873 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -452,9 +452,9 @@
@Override
public void print(CfFrame frame) {
- String keys = join(frame.getLocals().keySet(), ",", BraceType.NONE);
- String values = join(frame.getLocals().values(), ",", BraceType.NONE, this::frameTypeType);
- String stack = join(frame.getStack(), ",", BraceType.NONE, this::frameTypeType);
+ String keys = join(frame.getLocals().keySet(), ",");
+ String values = join(",", frame.getLocals().values(), this::frameTypeType);
+ String stack = join(",", frame.getStack(), this::frameTypeType);
printNewInstruction(
"CfFrame",
"new "
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 6176b49..be6176b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -65,6 +65,10 @@
return visitor.visit(this, other.asFieldInstruction(), CfFieldInstruction::specify);
}
+ public boolean isFieldGet() {
+ return opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC;
+ }
+
@Override
public CfFieldInstruction asFieldInstruction() {
return this;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 6c7f76f..00d4c93 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -170,6 +170,10 @@
!method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
}
+ public boolean isInvokeStatic() {
+ return opcode == Opcodes.INVOKESTATIC;
+ }
+
public boolean isInvokeVirtual() {
return opcode == Opcodes.INVOKEVIRTUAL;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathField.java b/src/main/java/com/android/tools/r8/graph/ClasspathField.java
new file mode 100644
index 0000000..b40e51c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathField.java
@@ -0,0 +1,27 @@
+// 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;
+
+public class ClasspathField extends DexClassAndField {
+
+ public ClasspathField(DexClasspathClass holder, DexEncodedField field) {
+ super(holder, field);
+ }
+
+ @Override
+ public boolean isClasspathField() {
+ return true;
+ }
+
+ @Override
+ public ClasspathField asClasspathField() {
+ return this;
+ }
+
+ @Override
+ public boolean isClasspathMember() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
index 9d2a941..0c58053 100644
--- a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
@@ -23,6 +23,11 @@
}
@Override
+ public boolean isClasspathMember() {
+ return true;
+ }
+
+ @Override
public boolean isClasspathMethod() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 6e38ed4..219b723 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -18,6 +18,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
public abstract class DexApplication {
@@ -109,7 +110,7 @@
// that.
if (options.testing.deterministicSortingBasedOnDexType) {
// To keep the order deterministic, we sort the classes by their type, which is a unique key.
- classes.sort((a, b) -> a.type.compareTo(b.type));
+ classes.sort(Comparator.comparing(DexClass::getType));
}
return classes;
}
@@ -133,7 +134,7 @@
// new or removing existing classes), classpath and library
// collections will be considered monolithic collections.
- final List<DexProgramClass> programClasses = new ArrayList<>();
+ List<DexProgramClass> programClasses = new ArrayList<>();
final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 22a830e..f1c0a2f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -122,6 +122,11 @@
}
}
+ public abstract void accept(
+ Consumer<DexProgramClass> programClassConsumer,
+ Consumer<DexClasspathClass> classpathClassConsumer,
+ Consumer<DexLibraryClass> libraryClassConsumer);
+
public void forEachClassMethod(Consumer<? super DexClassAndMethod> consumer) {
forEachClassMethodMatching(alwaysTrue(), consumer);
}
@@ -163,6 +168,10 @@
return Iterables.concat(fields(), methods());
}
+ public Iterable<DexEncodedMember<?, ?>> members(Predicate<DexEncodedMember<?, ?>> predicate) {
+ return Iterables.concat(fields(predicate), methods(predicate));
+ }
+
public MethodCollection getMethodCollection() {
return methodCollection;
}
@@ -171,7 +180,7 @@
return methodCollection.methods();
}
- public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) {
+ public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
return methodCollection.methods(predicate);
}
@@ -445,6 +454,15 @@
return field;
}
+ /** Find method in this class matching {@param method}. */
+ public DexClassAndField lookupClassField(DexField field) {
+ return toClassFieldOrNull(lookupField(field));
+ }
+
+ private DexClassAndField toClassFieldOrNull(DexEncodedField field) {
+ return field != null ? DexClassAndField.create(this, field) : null;
+ }
+
/** Find field in this class matching {@param field}. */
public DexEncodedField lookupField(DexField field) {
DexEncodedField result = lookupInstanceField(field);
@@ -848,6 +866,10 @@
nestHost = null;
}
+ public void clearNestMembers() {
+ nestMembers.clear();
+ }
+
public void setNestHost(DexType type) {
assert type != null;
this.nestHost = new NestHostClassAttribute(type);
@@ -887,6 +909,10 @@
/** Returns kotlin class info if the class is synthesized by kotlin compiler. */
public abstract KotlinClassLevelInfo getKotlinInfo();
+ public final String getTypeName() {
+ return getType().getTypeName();
+ }
+
public boolean hasStaticFields() {
return staticFields.length > 0;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index bba0f82..cef86d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -8,15 +8,20 @@
DexClassAndField(DexClass holder, DexEncodedField field) {
super(holder, field);
+ assert holder.isClasspathClass() == (this instanceof ClasspathField);
+ assert holder.isLibraryClass() == (this instanceof LibraryField);
assert holder.isProgramClass() == (this instanceof ProgramField);
}
public static DexClassAndField create(DexClass holder, DexEncodedField field) {
if (holder.isProgramClass()) {
return new ProgramField(holder.asProgramClass(), field);
- } else {
- return new DexClassAndField(holder, field);
}
+ if (holder.isLibraryClass()) {
+ return new LibraryField(holder.asLibraryClass(), field);
+ }
+ assert holder.isClasspathClass();
+ return new ClasspathField(holder.asClasspathClass(), field);
}
@Override
@@ -28,6 +33,22 @@
return getReference().getType();
}
+ public boolean isClasspathField() {
+ return false;
+ }
+
+ public ClasspathField asClasspathField() {
+ return null;
+ }
+
+ public boolean isLibraryField() {
+ return false;
+ }
+
+ public LibraryField asLibraryField() {
+ return null;
+ }
+
public boolean isProgramField() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index 719e2d6..83c4334 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -51,6 +51,18 @@
return holder.origin;
}
+ public boolean isClasspathMember() {
+ return false;
+ }
+
+ public boolean isLibraryMember() {
+ return false;
+ }
+
+ public boolean isProgramMember() {
+ return false;
+ }
+
public String toSourceString() {
return getReference().toSourceString();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 0446e09..075c1ba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -9,6 +9,8 @@
DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
super(holder, method);
+ assert holder.isClasspathClass() == (this instanceof ClasspathMethod);
+ assert holder.isLibraryClass() == (this instanceof LibraryMethod);
assert holder.isProgramClass() == (this instanceof ProgramMethod);
}
@@ -19,12 +21,12 @@
public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) {
if (holder.isProgramClass()) {
return new ProgramMethod(holder.asProgramClass(), method);
- } else if (holder.isLibraryClass()) {
- return new LibraryMethod(holder.asLibraryClass(), method);
- } else {
- assert holder.isClasspathClass();
- return new ClasspathMethod(holder.asClasspathClass(), method);
}
+ if (holder.isLibraryClass()) {
+ return new LibraryMethod(holder.asLibraryClass(), method);
+ }
+ assert holder.isClasspathClass();
+ return new ClasspathMethod(holder.asClasspathClass(), method);
}
public boolean isDefaultMethod() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index c97a1a4..dc3ff8d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.google.common.base.Predicates.alwaysTrue;
+
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.dex.MixedSectionCollection;
@@ -12,6 +14,7 @@
import com.android.tools.r8.origin.Origin;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -58,6 +61,24 @@
}
@Override
+ public void accept(
+ Consumer<DexProgramClass> programClassConsumer,
+ Consumer<DexClasspathClass> classpathClassConsumer,
+ Consumer<DexLibraryClass> libraryClassConsumer) {
+ classpathClassConsumer.accept(this);
+ }
+
+ public void forEachClasspathMethod(Consumer<? super ClasspathMethod> consumer) {
+ forEachClasspathMethodMatching(alwaysTrue(), consumer);
+ }
+
+ public void forEachClasspathMethodMatching(
+ Predicate<DexEncodedMethod> predicate, Consumer<? super ClasspathMethod> consumer) {
+ methodCollection.forEachMethodMatching(
+ predicate, method -> consumer.accept(new ClasspathMethod(this, method)));
+ }
+
+ @Override
public String toString() {
return type.toString() + "(classpath class)";
}
@@ -78,6 +99,10 @@
return this;
}
+ public static DexClasspathClass asClasspathClassOrNull(DexClass clazz) {
+ return clazz != null ? clazz.asClasspathClass() : null;
+ }
+
@Override
public boolean isNotProgramClass() {
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index fc0eeb0..30d5cfd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -203,10 +203,6 @@
return accessFlags.isPackagePrivate();
}
- public boolean isPrivate() {
- return accessFlags.isPrivate();
- }
-
public boolean isProtected() {
return accessFlags.isProtected();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index bca8a6b..d5c5135 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -35,6 +35,10 @@
return this;
}
+ public final boolean isPrivate() {
+ return getAccessFlags().isPrivate();
+ }
+
public abstract ProgramMember<D, R> asProgramMember(DexDefinitionSupplier definitions);
@Override
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 2236b6c..1df9806 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -49,7 +49,6 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.NestUtils;
@@ -60,7 +59,8 @@
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
+import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
@@ -497,10 +497,6 @@
return accessFlags.isNative();
}
- public boolean isPrivate() {
- return accessFlags.isPrivate();
- }
-
public boolean isPublic() {
return accessFlags.isPublic();
}
@@ -1192,59 +1188,47 @@
return builder.build();
}
- public ProgramMethod toInitializerForwardingBridge(DexProgramClass holder, DexMethod newMethod) {
- assert accessFlags.isPrivate()
+ public ProgramMethod toInitializerForwardingBridge(
+ DexProgramClass holder, DexMethod newMethod, DexItemFactory dexItemFactory) {
+ assert isPrivate()
: "Expected to create bridge for private constructor as part of nest-based access"
+ " desugaring";
- Builder builder = syntheticBuilder(this);
- builder.setMethod(newMethod);
- ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
- ForwardMethodSourceCode.builder(newMethod);
- forwardSourceCodeBuilder
- .setReceiver(holder.type)
- .setTargetReceiver(holder.type)
- .setTarget(method)
- .setInvokeType(Invoke.Type.DIRECT)
- .setExtraNullParameter();
- builder.setCode(
- new SynthesizedCode(
- forwardSourceCodeBuilder::build, registry -> registry.registerInvokeDirect(method)));
- assert !builder.accessFlags.isStatic();
assert !holder.isInterface();
- builder.accessFlags.unsetPrivate();
- builder.accessFlags.setSynthetic();
- builder.accessFlags.setConstructor();
- return new ProgramMethod(holder, builder.build());
+ return new ProgramMethod(
+ holder,
+ syntheticBuilder(this)
+ .setMethod(newMethod)
+ .setCode(
+ ForwardMethodBuilder.builder(dexItemFactory)
+ .setNonStaticSourceWithExtraUnusedParameter(newMethod)
+ .setConstructorTarget(getReference())
+ .build())
+ .modifyAccessFlags(
+ accessFlags -> {
+ assert !accessFlags.isStatic();
+ accessFlags.unsetPrivate();
+ accessFlags.setSynthetic();
+ accessFlags.setConstructor();
+ })
+ .build());
}
public static ProgramMethod createFieldAccessorBridge(
- DexFieldWithAccess fieldWithAccess, DexProgramClass holder, DexMethod newMethod) {
- assert holder.type == fieldWithAccess.getHolder();
+ ProgramField field, boolean isGet, DexMethod newMethod) {
MethodAccessFlags accessFlags =
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_SYNTHETIC
| Constants.ACC_STATIC
- | (holder.isInterface() ? Constants.ACC_PUBLIC : 0),
+ | (field.getHolder().isInterface() ? Constants.ACC_PUBLIC : 0),
false);
- Code code =
- new SynthesizedCode(
- callerPosition ->
- new FieldAccessorSourceCode(
- null, newMethod, callerPosition, newMethod, fieldWithAccess),
- registry -> {
- if (fieldWithAccess.isInstanceGet()) {
- registry.registerInstanceFieldRead(fieldWithAccess.getField());
- } else if (fieldWithAccess.isStaticGet()) {
- registry.registerStaticFieldRead(fieldWithAccess.getField());
- } else if (fieldWithAccess.isInstancePut()) {
- registry.registerInstanceFieldWrite(fieldWithAccess.getField());
- } else {
- assert fieldWithAccess.isStaticPut();
- registry.registerStaticFieldWrite(fieldWithAccess.getField());
- }
- });
+ CfCode code =
+ FieldAccessorBuilder.builder()
+ .apply(isGet ? FieldAccessorBuilder::setGetter : FieldAccessorBuilder::setSetter)
+ .setField(field)
+ .setSourceMethod(newMethod)
+ .build();
return new ProgramMethod(
- holder,
+ field.getHolder(),
new DexEncodedMethod(
newMethod,
accessFlags,
@@ -1285,35 +1269,36 @@
true);
}
- public ProgramMethod toStaticForwardingBridge(DexProgramClass holder, DexMethod newMethod) {
- assert accessFlags.isPrivate()
+ public ProgramMethod toStaticForwardingBridge(
+ DexProgramClass holder, DexMethod newMethod, DexItemFactory dexItemFactory) {
+ assert isPrivate()
: "Expected to create bridge for private method as part of nest-based access desugaring";
- Builder builder = syntheticBuilder(this);
- builder.setMethod(newMethod);
- ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
- ForwardMethodSourceCode.builder(newMethod);
- forwardSourceCodeBuilder
- .setTargetReceiver(accessFlags.isStatic() ? null : method.holder)
- .setTarget(method)
- .setInvokeType(accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.DIRECT)
- .setIsInterface(holder.isInterface());
- builder.setCode(
- new SynthesizedCode(
- forwardSourceCodeBuilder::build,
- registry -> {
- if (accessFlags.isStatic()) {
- registry.registerInvokeStatic(method);
- } else {
- registry.registerInvokeDirect(method);
- }
- }));
- builder.accessFlags.setSynthetic();
- builder.accessFlags.setStatic();
- builder.accessFlags.unsetPrivate();
- if (holder.isInterface()) {
- builder.accessFlags.setPublic();
- }
- return new ProgramMethod(holder, builder.build());
+ return new ProgramMethod(
+ holder,
+ syntheticBuilder(this)
+ .setMethod(newMethod)
+ .setCode(
+ ForwardMethodBuilder.builder(dexItemFactory)
+ .setStaticSource(newMethod)
+ .apply(
+ builder -> {
+ if (isStatic()) {
+ builder.setStaticTarget(getReference(), holder.isInterface());
+ } else {
+ builder.setDirectTarget(getReference(), holder.isInterface());
+ }
+ })
+ .build())
+ .modifyAccessFlags(
+ accessFlags -> {
+ accessFlags.setSynthetic();
+ accessFlags.setStatic();
+ accessFlags.unsetPrivate();
+ if (holder.isInterface()) {
+ accessFlags.setPublic();
+ }
+ })
+ .build());
}
public DexEncodedMethod toPrivateSyntheticMethod(DexMethod method) {
@@ -1587,12 +1572,18 @@
return this;
}
+ public Builder modifyAccessFlags(Consumer<MethodAccessFlags> consumer) {
+ consumer.accept(accessFlags);
+ return this;
+ }
+
public void setAccessFlags(MethodAccessFlags accessFlags) {
this.accessFlags = accessFlags.copy();
}
- public void setMethod(DexMethod method) {
+ public Builder setMethod(DexMethod method) {
this.method = method;
+ return this;
}
public Builder setCompilationState(CompilationState compilationState) {
@@ -1677,8 +1668,9 @@
return this;
}
- public void setCode(Code code) {
+ public Builder setCode(Code code) {
this.code = code;
+ return this;
}
public DexEncodedMethod build() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index e9c2fc5..a925fcc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -52,6 +52,11 @@
}
@Override
+ public DexClassAndField lookupMemberOnClass(DexClass clazz) {
+ return clazz != null ? clazz.lookupClassField(this) : null;
+ }
+
+ @Override
public ProgramField lookupOnProgramClass(DexProgramClass clazz) {
return clazz != null ? clazz.lookupProgramField(this) : null;
}
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 bc6db0a..fd33483 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -25,7 +25,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -417,6 +417,8 @@
public final DexType noClassDefFoundErrorType =
createStaticallyKnownType(noClassDefFoundErrorDescriptor);
public final DexType noSuchFieldErrorType = createStaticallyKnownType(noSuchFieldErrorDescriptor);
+ public final DexType noSuchMethodErrorType =
+ createStaticallyKnownType("Ljava/lang/NoSuchMethodError;");
public final DexType npeType = createStaticallyKnownType(npeDescriptor);
public final DexType reflectiveOperationExceptionType =
createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index e5a8097..df36fb7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -13,6 +13,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -85,6 +86,14 @@
}
@Override
+ public void accept(
+ Consumer<DexProgramClass> programClassConsumer,
+ Consumer<DexClasspathClass> classpathClassConsumer,
+ Consumer<DexLibraryClass> libraryClassConsumer) {
+ libraryClassConsumer.accept(this);
+ }
+
+ @Override
public String toString() {
return type.toString() + "(library class)";
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 1c02f3b..9b62855 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -24,6 +24,8 @@
public abstract DexEncodedMember<?, ?> lookupOnClass(DexClass clazz);
+ public abstract DexClassAndMember<?, ?> lookupMemberOnClass(DexClass clazz);
+
public abstract ProgramMember<?, ?> lookupOnProgramClass(DexProgramClass clazz);
public abstract boolean match(R entry);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index d35b1a0..29e9792 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -102,6 +102,11 @@
}
@Override
+ public DexClassAndMethod lookupMemberOnClass(DexClass clazz) {
+ return clazz != null ? clazz.lookupClassMethod(this) : null;
+ }
+
+ @Override
public ProgramMethod lookupOnProgramClass(DexProgramClass clazz) {
return clazz != null ? clazz.lookupProgramMethod(this) : null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 7d69899..1d66acd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -148,6 +148,14 @@
}
@Override
+ public void accept(
+ Consumer<DexProgramClass> programClassConsumer,
+ Consumer<DexClasspathClass> classpathClassConsumer,
+ Consumer<DexLibraryClass> libraryClassConsumer) {
+ programClassConsumer.accept(this);
+ }
+
+ @Override
public DexProgramClass self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index d2fb517..56f9a92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -175,6 +175,10 @@
classConsumer.accept(this, arg);
}
+ public String getTypeName() {
+ return toSourceString();
+ }
+
@Override
public String toSourceString() {
if (toStringCache == null) {
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 5a7d18f..c6cf5f8 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -213,6 +213,12 @@
return self();
}
+ public Builder addProgramClasses(Collection<DexProgramClass> classes) {
+ programClasses =
+ ImmutableList.<DexProgramClass>builder().addAll(programClasses).addAll(classes).build();
+ return self();
+ }
+
public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
classpathClasses =
ImmutableList.<DexClasspathClass>builder()
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 88596f9..40b6445 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -817,8 +817,7 @@
private boolean classRequiresCode() {
return parent.classKind == ClassKind.PROGRAM
- || (parent.application.options.enableNestBasedAccessDesugaring
- && !parent.application.options.canUseNestBasedAccess()
+ || (!parent.application.options.canUseNestBasedAccess()
&& parent.classKind == ClassKind.CLASSPATH
&& parent.isInANest());
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 6a70cdd..ba22b16 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -105,7 +105,7 @@
private final Origin origin;
private JarApplicationReader application;
private CfCode code;
- protected ReparseContext context;
+ private ReparseContext context;
private boolean reachabilitySensitive = false;
public void markReachabilitySensitive() {
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryField.java b/src/main/java/com/android/tools/r8/graph/LibraryField.java
new file mode 100644
index 0000000..49ed565
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LibraryField.java
@@ -0,0 +1,35 @@
+// 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;
+
+public class LibraryField extends DexClassAndField
+ implements LibraryMember<DexEncodedField, DexField> {
+
+ public LibraryField(DexLibraryClass holder, DexEncodedField field) {
+ super(holder, field);
+ }
+
+ @Override
+ public DexLibraryClass getHolder() {
+ DexClass holder = super.getHolder();
+ assert holder.isLibraryClass();
+ return holder.asLibraryClass();
+ }
+
+ @Override
+ public boolean isLibraryField() {
+ return true;
+ }
+
+ @Override
+ public LibraryField asLibraryField() {
+ return this;
+ }
+
+ @Override
+ public boolean isLibraryMember() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMember.java b/src/main/java/com/android/tools/r8/graph/LibraryMember.java
new file mode 100644
index 0000000..880da78
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LibraryMember.java
@@ -0,0 +1,14 @@
+// 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;
+
+public interface LibraryMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+
+ D getDefinition();
+
+ DexLibraryClass getHolder();
+
+ DexType getHolderType();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
index c06ebab..29cdc41 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
@@ -4,13 +4,26 @@
package com.android.tools.r8.graph;
/** Type representing a method definition from the library and its holder. */
-public final class LibraryMethod extends DexClassAndMethod {
+public final class LibraryMethod extends DexClassAndMethod
+ implements LibraryMember<DexEncodedMethod, DexMethod> {
public LibraryMethod(DexLibraryClass holder, DexEncodedMethod method) {
super(holder, method);
}
@Override
+ public DexLibraryClass getHolder() {
+ DexClass holder = super.getHolder();
+ assert holder.isLibraryClass();
+ return holder.asLibraryClass();
+ }
+
+ @Override
+ public boolean isLibraryMember() {
+ return true;
+ }
+
+ @Override
public boolean isLibraryMethod() {
return true;
}
@@ -19,11 +32,4 @@
public LibraryMethod asLibraryMethod() {
return this;
}
-
- @Override
- public DexLibraryClass getHolder() {
- DexClass holder = super.getHolder();
- assert holder.isLibraryClass();
- return holder.asLibraryClass();
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 9339630..ccf33aa 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -130,7 +130,7 @@
return backing.methods();
}
- public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) {
+ public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
return IterableUtils.filter(methods(), predicate);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index 015b182..2b5e70f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -62,7 +62,7 @@
public void recordFieldAccesses(
IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
- if (!methodProcessor.isPrimary()) {
+ if (!methodProcessor.isPrimaryMethodProcessor()) {
return;
}
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 a1c4e7b..d0e0027 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
@@ -227,9 +227,9 @@
for (Value value : current.inValues()) {
value.removeUser(current);
}
- if (current.outValue() != null && current.outValue().isUsed()) {
+ if (current.hasUsedOutValue()) {
assert newInstruction.outValue() != null;
- if (affectedValues != null) {
+ if (affectedValues != null && newInstruction.getOutType() != current.getOutType()) {
current.outValue().addAffectedValuesTo(affectedValues);
}
current.outValue().replaceUsers(newInstruction.outValue());
@@ -243,6 +243,7 @@
listIterator.add(newInstruction);
current.clearBlock();
metadata.record(newInstruction);
+ current = newInstruction;
}
@Override
@@ -402,6 +403,69 @@
}
@Override
+ public void replaceCurrentInstructionWithThrow(
+ AppView<?> appView,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ Value exceptionValue,
+ Set<BasicBlock> blocksToRemove,
+ Set<Value> affectedValues) {
+ if (current == null) {
+ throw new IllegalStateException();
+ }
+
+ Instruction toBeReplaced = current;
+ InternalOptions options = appView.options();
+
+ BasicBlock block = toBeReplaced.getBlock();
+ assert !blocksToRemove.contains(block);
+ assert affectedValues != null;
+
+ // Split the block before the instruction that should be replaced by `throw exceptionValue`.
+ previous();
+
+ BasicBlock throwBlock;
+ if (block.hasCatchHandlers() && !toBeReplaced.instructionTypeCanThrow()) {
+ // We need to insert the throw instruction in a block of its own, so split the current block
+ // into three blocks, where the intermediate block only contains a goto instruction.
+ throwBlock = splitCopyCatchHandlers(code, blockIterator, options);
+ throwBlock.listIterator(code).split(code, blockIterator, true);
+ } else {
+ splitCopyCatchHandlers(code, blockIterator, options);
+ throwBlock = block;
+ }
+
+ // Unlink all blocks that are dominated by the unique normal successor of the throw block.
+ blocksToRemove.addAll(
+ throwBlock.unlink(
+ throwBlock.getUniqueNormalSuccessor(),
+ new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS),
+ affectedValues));
+
+ InstructionListIterator throwBlockInstructionIterator;
+ if (throwBlock == block) {
+ throwBlockInstructionIterator = this;
+ previous();
+ next();
+ } else {
+ throwBlockInstructionIterator = throwBlock.listIterator(code, 1);
+ }
+ assert !throwBlockInstructionIterator.hasNext();
+
+ // Replace the instruction by throw.
+ Throw throwInstruction = new Throw(exceptionValue);
+ if (hasInsertionPosition()) {
+ throwInstruction.setPosition(position);
+ } else if (toBeReplaced.getPosition().isSome()) {
+ throwInstruction.setPosition(toBeReplaced.getPosition());
+ } else {
+ assert !toBeReplaced.instructionTypeCanThrow();
+ throwInstruction.setPosition(Position.syntheticNone());
+ }
+ throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction);
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCode code,
@@ -563,8 +627,9 @@
IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) {
BasicBlock splitBlock = split(code, blockIterator, false);
assert !block.hasCatchHandlers();
- assert splitBlock.hasCatchHandlers();
- block.copyCatchHandlers(code, blockIterator, splitBlock, options);
+ if (splitBlock.hasCatchHandlers()) {
+ block.copyCatchHandlers(code, blockIterator, splitBlock, options);
+ }
return splitBlock;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 757a581..4e223924 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -85,6 +85,17 @@
}
@Override
+ public void replaceCurrentInstructionWithThrow(
+ AppView<?> appView,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ Value exceptionValue,
+ Set<BasicBlock> blocksToRemove,
+ Set<Value> affectedValues) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 6b17a04..32b1f29 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -120,6 +120,14 @@
void replaceCurrentInstructionWithStaticGet(
AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues);
+ void replaceCurrentInstructionWithThrow(
+ AppView<?> appView,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ Value exceptionValue,
+ Set<BasicBlock> blocksToRemove,
+ Set<Value> affectedValues);
+
/**
* Replace the current instruction with null throwing instructions.
*
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 9d7d69b..0d9d21a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
@@ -21,6 +22,7 @@
import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
@@ -253,6 +255,11 @@
return self();
}
+ public B setFreshOutValue(AppView<?> appView, ValueFactory factory) {
+ return super.setFreshOutValue(
+ factory, TypeElement.fromDexType(method.getReturnType(), maybeNull(), appView));
+ }
+
public B setSingleArgument(Value argument) {
return setArguments(ImmutableList.of(argument));
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index bbb6d27..3e61866 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -100,6 +100,18 @@
}
@Override
+ public void replaceCurrentInstructionWithThrow(
+ AppView<?> appView,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ Value exceptionValue,
+ Set<BasicBlock> blocksToRemove,
+ Set<Value> affectedValues) {
+ currentBlockIterator.replaceCurrentInstructionWithThrow(
+ appView, code, blockIterator, exceptionValue, blocksToRemove, affectedValues);
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
new file mode 100644
index 0000000..e747891
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, 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.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public abstract class ClassConverter {
+
+ private final IRConverter converter;
+ private final D8MethodProcessor methodProcessor;
+
+ ClassConverter(IRConverter converter, D8MethodProcessor methodProcessor) {
+ this.converter = converter;
+ this.methodProcessor = methodProcessor;
+ }
+
+ public static ClassConverter create(
+ AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
+ return appView.options().desugarSpecificOptions().allowAllDesugaredInput
+ ? new LibraryDesugaredClassConverter(appView, converter, methodProcessor)
+ : new DefaultClassConverter(converter, methodProcessor);
+ }
+
+ public void convertClasses(DexApplication application, ExecutorService executorService)
+ throws ExecutionException {
+ internalConvertClasses(application, executorService);
+ notifyAllClassesConverted();
+ }
+
+ private void internalConvertClasses(DexApplication application, ExecutorService executorService)
+ throws ExecutionException {
+ List<DexProgramClass> classes = application.classes();
+ while (!classes.isEmpty()) {
+ Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
+ List<DexProgramClass> deferred = new ArrayList<>(classes.size() / 2);
+ List<DexProgramClass> wave = new ArrayList<>(classes.size());
+ for (DexProgramClass clazz : classes) {
+ if (clazz.isInANest() && !seenNestHosts.add(clazz.getNestHost())) {
+ deferred.add(clazz);
+ } else {
+ wave.add(clazz);
+ }
+ }
+ ThreadUtils.processItems(wave, this::convertClass, executorService);
+ classes = deferred;
+ }
+ methodProcessor.awaitMethodProcessing();
+ }
+
+ abstract void convertClass(DexProgramClass clazz);
+
+ void convertMethods(DexProgramClass clazz) {
+ converter.convertMethods(clazz, methodProcessor);
+ }
+
+ abstract void notifyAllClassesConverted();
+
+ static class DefaultClassConverter extends ClassConverter {
+
+ DefaultClassConverter(IRConverter converter, D8MethodProcessor methodProcessor) {
+ super(converter, methodProcessor);
+ }
+
+ @Override
+ void convertClass(DexProgramClass clazz) {
+ convertMethods(clazz);
+ }
+
+ @Override
+ void notifyAllClassesConverted() {
+ // Intentionally empty.
+ }
+ }
+
+ static class LibraryDesugaredClassConverter extends ClassConverter {
+
+ private final AppView<?> appView;
+ private final Set<DexType> alreadyLibraryDesugared = Sets.newConcurrentHashSet();
+
+ LibraryDesugaredClassConverter(
+ AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
+ super(converter, methodProcessor);
+ this.appView = appView;
+ }
+
+ @Override
+ void convertClass(DexProgramClass clazz) {
+ // Classes which has already been through library desugaring will not go through IR
+ // processing again.
+ LibraryDesugaredChecker libraryDesugaredChecker = new LibraryDesugaredChecker(appView);
+ if (libraryDesugaredChecker.isClassLibraryDesugared(clazz)) {
+ alreadyLibraryDesugared.add(clazz.getType());
+ } else {
+ convertMethods(clazz);
+ }
+ }
+
+ @Override
+ void notifyAllClassesConverted() {
+ appView.setAlreadyLibraryDesugared(alreadyLibraryDesugared);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
new file mode 100644
index 0000000..61ea47c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -0,0 +1,50 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+public class D8MethodProcessor extends MethodProcessor {
+
+ private final IRConverter converter;
+ private final ExecutorService executorService;
+ private final List<Future<?>> futures = Collections.synchronizedList(new ArrayList<>());
+
+ public D8MethodProcessor(IRConverter converter, ExecutorService executorService) {
+ this.converter = converter;
+ this.executorService = executorService;
+ }
+
+ public void awaitMethodProcessing() throws ExecutionException {
+ ThreadUtils.awaitFutures(futures);
+ futures.clear();
+ }
+
+ @Override
+ public boolean shouldApplyCodeRewritings(ProgramMethod method) {
+ return true;
+ }
+
+ @Override
+ public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+ futures.add(
+ ThreadUtils.processAsynchronously(
+ () ->
+ converter.rewriteCode(method, OptimizationFeedbackIgnore.getInstance(), this, null),
+ executorService));
+ }
+
+ public boolean verifyAllMethodsProcessed() {
+ assert futures.isEmpty();
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index da599dc..60e2477 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -45,7 +45,6 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
-import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
@@ -54,6 +53,8 @@
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.StringConcatRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBridgeConsumer;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.AssumeInserter;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
@@ -108,12 +109,10 @@
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -393,10 +392,21 @@
}
}
- private void desugarNestBasedAccess(Builder<?> builder, ExecutorService executorService)
- throws ExecutionException {
+ private void synthesizeBridgesForNestBasedAccessesOnClasspath(
+ MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
if (d8NestBasedAccessDesugaring != null) {
- d8NestBasedAccessDesugaring.desugarNestBasedAccess(builder, executorService, this);
+ d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
+ methodProcessor, executorService);
+ }
+ }
+
+ private void finalizeNestBasedAccessDesugaring(Builder<?> builder) {
+ if (d8NestBasedAccessDesugaring != null) {
+ DexProgramClass nestConstructor = d8NestBasedAccessDesugaring.synthesizeNestConstructor();
+ if (nestConstructor != null) {
+ builder.addProgramClass(nestConstructor);
+ }
+ d8NestBasedAccessDesugaring.reportDesugarDependencies();
}
}
@@ -479,38 +489,12 @@
DexApplication application = appView.appInfo().app();
timing.begin("IR conversion");
- if (appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
- // Classes which has already been through library desugaring will not go through IR
- // processing again.
- LibraryDesugaredChecker libraryDesugaredChecker = new LibraryDesugaredChecker(appView);
- Set<DexType> alreadyLibraryDesugared = Sets.newConcurrentHashSet();
- ThreadUtils.processItems(
- application.classes(),
- clazz -> {
- if (libraryDesugaredChecker.isClassLibraryDesugared(clazz)) {
- if (appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
- alreadyLibraryDesugared.add(clazz.getType());
- } else {
- throw new CompilationError(
- "Code for "
- + clazz.getType().getDescriptor()
- + "has already been library desugared.");
- }
- } else {
- convertMethods(clazz);
- }
- },
- executor);
- appView.setAlreadyLibraryDesugared(alreadyLibraryDesugared);
- } else {
- ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
- }
+ convertClasses(application, executor);
// Build a new application with jumbo string info,
- Builder<?> builder = application.builder();
- builder.setHighestSortingString(highestSortingString);
+ Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
- desugarNestBasedAccess(builder, executor);
+ finalizeNestBasedAccessDesugaring(builder);
// Synthesize lambda classes and commit to the app in full.
synthesizeLambdaClasses(executor);
@@ -541,7 +525,17 @@
appView.appInfo().getMainDexClasses()));
}
- private void convertMethods(DexProgramClass clazz) {
+ private void convertClasses(DexApplication application, ExecutorService executorService)
+ throws ExecutionException {
+ D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executorService);
+ ClassConverter classConverter = ClassConverter.create(appView, this, methodProcessor);
+ classConverter.convertClasses(application, executorService);
+
+ synthesizeBridgesForNestBasedAccessesOnClasspath(methodProcessor, executorService);
+ methodProcessor.awaitMethodProcessing();
+ }
+
+ void convertMethods(DexProgramClass clazz, MethodProcessor methodProcessor) {
boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
// When converting all methods on a class always convert <clinit> first.
DexEncodedMethod classInitializer = clazz.getClassInitializer();
@@ -549,14 +543,14 @@
classInitializer
.getMutableOptimizationInfo()
.setReachabilitySensitive(isReachabilitySensitive);
- convertMethod(new ProgramMethod(clazz, classInitializer));
+ convertMethod(new ProgramMethod(clazz, classInitializer), methodProcessor);
}
clazz.forEachProgramMethodMatching(
definition -> !definition.isClassInitializer(),
method -> {
DexEncodedMethod definition = method.getDefinition();
definition.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
- convertMethod(method);
+ convertMethod(method, methodProcessor);
});
// The class file version is downgraded after compilation. Some of the desugaring might need
// the initial class file version to determine how far a method can be downgraded.
@@ -566,7 +560,7 @@
}
}
- private void convertMethod(ProgramMethod method) {
+ private void convertMethod(ProgramMethod method, MethodProcessor methodProcessor) {
DexEncodedMethod definition = method.getDefinition();
if (definition.hasClassFileVersion()) {
definition.downgradeClassFileVersion(
@@ -585,8 +579,7 @@
if (options.isGeneratingClassFiles()
|| !(options.passthroughDexCode && definition.getCode().isDexCode())) {
// We do not process in call graph order, so anything could be a leaf.
- rewriteCode(
- method, simpleOptimizationFeedback, OneTimeMethodProcessor.create(method, appView), null);
+ rewriteCode(method, simpleOptimizationFeedback, methodProcessor, null);
} else {
assert definition.getCode().isDexCode();
}
@@ -1142,7 +1135,7 @@
}
}
- private Timing rewriteCode(
+ Timing rewriteCode(
ProgramMethod method,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
@@ -1169,7 +1162,7 @@
method.toSourceString(),
logCode(options, method.getDefinition()));
}
- boolean didDesugar = desugar(method);
+ boolean didDesugar = desugar(method, methodProcessor);
if (Log.ENABLED && didDesugar) {
Log.debug(
getClass(),
@@ -1192,7 +1185,7 @@
return optimize(code, feedback, methodProcessor, methodProcessingId);
}
- private boolean desugar(ProgramMethod method) {
+ private boolean desugar(ProgramMethod method, MethodProcessor methodProcessor) {
if (options.desugarState != DesugarState.ON) {
return false;
}
@@ -1201,13 +1194,17 @@
}
boolean didDesugar = false;
Supplier<AppInfoWithClassHierarchy> lazyAppInfo =
- Suppliers.memoize(() -> appView.appInfoForDesugaring());
+ Suppliers.memoize(appView::appInfoForDesugaring);
if (lambdaRewriter != null) {
didDesugar |= lambdaRewriter.desugarLambdas(method, lazyAppInfo.get()) > 0;
}
if (backportedMethodRewriter != null) {
didDesugar |= backportedMethodRewriter.desugar(method, lazyAppInfo.get());
}
+ if (d8NestBasedAccessDesugaring != null) {
+ NestBridgeConsumer bridgeConsumer = NestBridgeConsumer.createForD8(methodProcessor);
+ didDesugar |= d8NestBasedAccessDesugaring.desugar(method, bridgeConsumer);
+ }
return didDesugar;
}
@@ -1537,18 +1534,9 @@
previous = printMethod(code, "IR after class inlining (SSA)", previous);
- if (d8NestBasedAccessDesugaring != null) {
- timing.begin("Desugar nest access");
- d8NestBasedAccessDesugaring.rewriteNestBasedAccesses(context, code, appView);
- timing.end();
- assert code.isConsistentSSA();
- }
-
- previous = printMethod(code, "IR after nest based access desugaring (SSA)", previous);
-
if (interfaceMethodRewriter != null) {
timing.begin("Rewrite interface methods");
- interfaceMethodRewriter.rewriteMethodReferences(code);
+ interfaceMethodRewriter.rewriteMethodReferences(code, methodProcessor, methodProcessingId);
timing.end();
assert code.isConsistentSSA();
}
@@ -1557,7 +1545,8 @@
// This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
if (desugaredLibraryAPIConverter != null
- && (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
+ && (!appView.enableWholeProgramOptimizations()
+ || methodProcessor.isPrimaryMethodProcessor())) {
timing.begin("Desugar library API");
desugaredLibraryAPIConverter.desugar(code);
timing.end();
@@ -1587,7 +1576,7 @@
// TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
// the list for primary processing only.
- if (options.outline.enabled && outliner != null && methodProcessor.isPrimary()) {
+ if (options.outline.enabled && outliner != null && methodProcessor.isPrimaryMethodProcessor()) {
timing.begin("Identify outlines");
outliner.getOutlineMethodIdentifierGenerator().accept(code);
timing.end();
@@ -1640,7 +1629,7 @@
timing.end();
}
- if (enumUnboxer != null && methodProcessor.isPrimary()) {
+ if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
enumUnboxer.analyzeEnums(code);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 6706aa5..ef9a31c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -8,25 +8,13 @@
public abstract class MethodProcessor {
- public enum Phase {
- ONE_TIME,
- PRIMARY,
- POST
- }
-
protected SortedProgramMethodSet wave;
protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
- public abstract Phase getPhase();
-
public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
- public boolean isPrimary() {
- return getPhase() == Phase.PRIMARY;
- }
-
- public boolean isPost() {
- return getPhase() == Phase.POST;
+ public boolean isPrimaryMethodProcessor() {
+ return false;
}
public CallSiteInformation getCallSiteInformation() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index a89e3f9..c44ce41 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -18,7 +18,7 @@
class NeedsIRDesugarUseRegistry extends UseRegistry {
- private boolean needsDesugarging = false;
+ private boolean needsDesugaring = false;
private final AppView appView;
private final BackportedMethodRewriter backportedMethodRewriter;
private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
@@ -40,15 +40,15 @@
}
public boolean needsDesugaring() {
- return needsDesugarging;
+ return needsDesugaring;
}
@Override
public void registerInitClass(DexType type) {
- if (!needsDesugarging
+ if (!needsDesugaring
&& desugaredLibraryAPIConverter != null
&& desugaredLibraryAPIConverter.canConvert(type)) {
- needsDesugarging = true;
+ needsDesugaring = true;
}
}
@@ -68,37 +68,37 @@
}
private void registerTwrCloseResourceRewriting(DexMethod method) {
- if (!needsDesugarging) {
- needsDesugarging =
+ if (!needsDesugaring) {
+ needsDesugaring =
TwrCloseResourceRewriter.isTwrCloseResourceMethod(method, appView.dexItemFactory());
}
}
private void registerBackportedMethodRewriting(DexMethod method) {
- if (!needsDesugarging) {
- needsDesugarging = backportedMethodRewriter.needsDesugaring(method);
+ if (!needsDesugaring) {
+ needsDesugaring = backportedMethodRewriter.needsDesugaring(method);
}
}
private void registerInterfaceMethodRewriting(DexMethod method, boolean isInvokeSuper) {
- if (!needsDesugarging) {
- needsDesugarging =
+ if (!needsDesugaring) {
+ needsDesugaring =
interfaceMethodRewriter != null
&& interfaceMethodRewriter.needsRewriting(method, isInvokeSuper, appView);
}
}
private void registerDesugaredLibraryAPIConverter(DexMethod method) {
- if (!needsDesugarging) {
- needsDesugarging =
+ if (!needsDesugaring) {
+ needsDesugaring =
desugaredLibraryAPIConverter != null
&& desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
}
}
private void registerLibraryRetargeting(DexMethod method, boolean b) {
- if (!needsDesugarging) {
- needsDesugarging =
+ if (!needsDesugaring) {
+ needsDesugaring =
desugaredLibraryRetargeter != null
&& desugaredLibraryRetargeter.getRetargetedMethod(method, b) != null;
}
@@ -123,7 +123,7 @@
@Override
public void registerInvokeStatic(DexMethod method, boolean itf) {
if (itf) {
- needsDesugarging = true;
+ needsDesugaring = true;
}
registerInvokeStatic(method);
}
@@ -131,7 +131,7 @@
@Override
public void registerCallSite(DexCallSite callSite) {
super.registerCallSite(callSite);
- needsDesugarging = true;
+ needsDesugaring = true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index bb3f07c..a8c2e57 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -52,11 +52,6 @@
return true;
}
- @Override
- public Phase getPhase() {
- return Phase.ONE_TIME;
- }
-
public <E extends Exception> void forEachWaveWithExtension(
ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer) throws E {
while (!wave.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index a3cccb4..649edfd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -53,11 +53,6 @@
}
@Override
- public Phase getPhase() {
- return Phase.POST;
- }
-
- @Override
public boolean shouldApplyCodeRewritings(ProgramMethod method) {
assert !wave.contains(method);
return !processed.contains(method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index dd4c0a0..cb288b0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -62,8 +62,8 @@
}
@Override
- public Phase getPhase() {
- return Phase.PRIMARY;
+ public boolean isPrimaryMethodProcessor() {
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
deleted file mode 100644
index 787915f..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.FieldInstruction;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-// Summary:
-// - Process all methods compiled rewriting nest based access (Methods processed concurrently).
-// - Process classes on class path in reachable nests to find bridges to add
-// in Program classes (Nests processed concurrently).
-// - Add bridges and nest constructor class (Sequential).
-// - Optimize bridges (Bridges processed concurrently).
-public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
-
- // Maps a nest host to a class met which has that nest host.
- // The value is used because the nest host might be missing.
- private final Map<DexType, DexProgramClass> metNestHosts = new ConcurrentHashMap<>();
-
- public D8NestBasedAccessDesugaring(AppView<?> appView) {
- super(appView);
- }
-
- public void rewriteNestBasedAccesses(ProgramMethod method, IRCode code, AppView<?> appView) {
- if (!method.getHolder().isInANest()) {
- return;
- }
- metNestHosts.put(method.getHolder().getNestHost(), method.getHolder());
-
- ListIterator<BasicBlock> blocks = code.listIterator();
- while (blocks.hasNext()) {
- BasicBlock block = blocks.next();
- InstructionListIterator instructions = block.listIterator(code);
- while (instructions.hasNext()) {
- Instruction instruction = instructions.next();
- if (instruction.isInvokeMethod()) {
- InvokeMethod invokeMethod = instruction.asInvokeMethod();
- DexMethod invokedMethod = invokeMethod.getInvokedMethod();
- if (!invokedMethod.holder.isClassType()) {
- continue;
- }
- // Since we only need to desugar accesses to private methods, and all accesses to private
- // methods must be accessing the private method directly on its holder, we can lookup the
- // method on the holder instead of resolving the method.
- DexClass holder = appView.definitionForHolder(invokedMethod);
- DexEncodedMethod definition = invokedMethod.lookupOnClass(holder);
- if (definition != null && invokeRequiresRewriting(definition, method)) {
- DexMethod bridge = ensureInvokeBridge(definition);
- if (definition.isInstanceInitializer()) {
- instructions.previous();
- Value extraNullValue =
- instructions.insertConstNullInstruction(code, appView.options());
- instructions.next();
- List<Value> parameters = new ArrayList<>(invokeMethod.arguments());
- parameters.add(extraNullValue);
- instructions.replaceCurrentInstruction(
- new InvokeDirect(bridge, invokeMethod.outValue(), parameters));
- } else {
- instructions.replaceCurrentInstruction(
- new InvokeStatic(bridge, invokeMethod.outValue(), invokeMethod.arguments()));
- }
- }
- } else if (instruction.isFieldInstruction()) {
- // Since we only need to desugar accesses to private fields, and all accesses to private
- // fields must be accessing the private field directly on its holder, we can lookup the
- // field on the holder instead of resolving the field.
- FieldInstruction fieldInstruction = instruction.asFieldInstruction();
- DexClass holder = appView.definitionForHolder(fieldInstruction.getField());
- DexEncodedField field = fieldInstruction.getField().lookupOnClass(holder);
- if (field != null && fieldAccessRequiresRewriting(field, method)) {
- if (instruction.isInstanceGet() || instruction.isStaticGet()) {
- DexMethod bridge = ensureFieldAccessBridge(field, true);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
- } else {
- assert instruction.isInstancePut() || instruction.isStaticPut();
- DexMethod bridge = ensureFieldAccessBridge(field, false);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
- }
- }
- }
- }
- }
- }
-
- private void processNestsConcurrently(ExecutorService executorService) throws ExecutionException {
- List<Future<?>> futures = new ArrayList<>();
- for (DexProgramClass clazz : metNestHosts.values()) {
- futures.add(asyncProcessNest(clazz, executorService));
- }
- ThreadUtils.awaitFutures(futures);
- }
-
- private void addDeferredBridges() {
- addDeferredBridges(bridges.values());
- addDeferredBridges(getFieldBridges.values());
- addDeferredBridges(putFieldBridges.values());
- }
-
- private void addDeferredBridges(Collection<ProgramMethod> bridges) {
- for (ProgramMethod bridge : bridges) {
- bridge.getHolder().addMethod(bridge.getDefinition());
- }
- }
-
- private void optimizeDeferredBridgesConcurrently(
- ExecutorService executorService, IRConverter converter) throws ExecutionException {
- SortedProgramMethodSet methods = SortedProgramMethodSet.create();
- methods.addAll(bridges.values());
- methods.addAll(getFieldBridges.values());
- methods.addAll(putFieldBridges.values());
- converter.processMethodsConcurrently(methods, executorService);
- }
-
- public void desugarNestBasedAccess(
- DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
- throws ExecutionException {
- if (metNestHosts.isEmpty()) {
- return;
- }
- processNestsConcurrently(executorService);
- addDeferredBridges();
- synthesizeNestConstructor(builder);
- optimizeDeferredBridgesConcurrently(executorService, converter);
- }
-
- // In D8, programClass are processed on the fly so they do not need to be processed again here.
- @Override
- protected boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest) {
- return clazz.isClasspathClass();
- }
-
- @Override
- void reportMissingNestHost(DexClass clazz) {
- appView.options().nestDesugaringWarningMissingNestHost(clazz);
- }
-
- @Override
- void reportIncompleteNest(List<DexType> nest) {
- appView.options().nestDesugaringWarningIncompleteNest(nest, appView);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 9ea3aa3..8682669 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -48,9 +49,14 @@
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeSuper;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -251,15 +257,21 @@
// Rewrites the references to static and default interface methods.
// NOTE: can be called for different methods concurrently.
- public void rewriteMethodReferences(IRCode code) {
+ public void rewriteMethodReferences(
+ IRCode code, MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
ProgramMethod context = code.context();
if (synthesizedMethods.contains(context)) {
return;
}
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
+ if (blocksToRemove.contains(block)) {
+ continue;
+ }
InstructionListIterator instructions = block.listIterator(code);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
@@ -271,7 +283,15 @@
rewriteInvokeDirect(instruction.asInvokeDirect(), instructions, context);
break;
case INVOKE_STATIC:
- rewriteInvokeStatic(instruction.asInvokeStatic(), instructions, context);
+ rewriteInvokeStatic(
+ instruction.asInvokeStatic(),
+ code,
+ blocks,
+ instructions,
+ affectedValues,
+ blocksToRemove,
+ methodProcessor,
+ methodProcessingId);
break;
case INVOKE_SUPER:
rewriteInvokeSuper(instruction.asInvokeSuper(), instructions, context);
@@ -287,6 +307,14 @@
}
}
}
+
+ code.removeBlocks(blocksToRemove);
+
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
+
+ assert code.isConsistentSSA();
}
private void rewriteInvokeCustom(InvokeCustom invoke, ProgramMethod context) {
@@ -359,13 +387,21 @@
}
private void rewriteInvokeStatic(
- InvokeStatic invoke, InstructionListIterator instructions, ProgramMethod context) {
+ InvokeStatic invoke,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ InstructionListIterator instructions,
+ Set<Value> affectedValues,
+ Set<BasicBlock> blocksToRemove,
+ MethodProcessor methodProcessor,
+ MethodProcessingId methodProcessingId) {
DexMethod invokedMethod = invoke.getInvokedMethod();
if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
// We did not create this code yet, but it will not require rewriting.
return;
}
+ ProgramMethod context = code.context();
DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
if (clazz == null) {
// NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
@@ -432,13 +468,44 @@
// When leaving static interface method invokes upgrade the class file version.
context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
}
- } else {
+ return;
+ }
+
+ SingleResolutionResult resolutionResult =
+ appView
+ .appInfoForDesugaring()
+ .resolveMethodOnInterface(clazz, invokedMethod)
+ .asSingleResolution();
+ if (resolutionResult != null) {
instructions.replaceCurrentInstruction(
new InvokeStatic(
- staticAsMethodOfCompanionClass(invokedMethod),
+ staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
invoke.outValue(),
invoke.arguments()));
+ return;
}
+
+ // Replace by throw new NoSuchMethodError.
+ UtilityMethodForCodeOptimizations throwNoSuchMethodErrorMethod =
+ UtilityMethodsForCodeOptimizations.synthesizeThrowNoSuchMethodErrorMethod(
+ appView, context, methodProcessingId);
+ throwNoSuchMethodErrorMethod.optimize(methodProcessor);
+ InvokeStatic throwNoSuchMethodErrorInvoke =
+ InvokeStatic.builder()
+ .setMethod(throwNoSuchMethodErrorMethod.getMethod())
+ .setFreshOutValue(appView, code)
+ .setPosition(invoke)
+ .build();
+ instructions.previous();
+ instructions.add(throwNoSuchMethodErrorInvoke);
+ instructions.next();
+ instructions.replaceCurrentInstructionWithThrow(
+ appView,
+ code,
+ blockIterator,
+ throwNoSuchMethodErrorInvoke.outValue(),
+ blocksToRemove,
+ affectedValues);
}
private void rewriteInvokeSuper(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
deleted file mode 100644
index eb42231..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ /dev/null
@@ -1,529 +0,0 @@
-// 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.ir.desugar;
-
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.ClasspathMethod;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.Pair;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-// NestBasedAccessDesugaring contains common code between the two subclasses
-// which are specialized for d8 and r8
-public abstract class NestBasedAccessDesugaring {
-
- // Short names to avoid creating long strings
- public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
- private static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
- private static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX =
- NEST_ACCESS_NAME_PREFIX + "sm";
- private static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
- private static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
- NEST_ACCESS_NAME_PREFIX + "sfget";
- private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
- private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
- NEST_ACCESS_NAME_PREFIX + "sfput";
- public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
-
- protected final AppView<?> appView;
- // Following maps are there to avoid creating the bridges multiple times
- // and remember the bridges to add once the nests are processed.
- final Map<DexMethod, ProgramMethod> bridges = new ConcurrentHashMap<>();
- final Map<DexField, ProgramMethod> getFieldBridges = new ConcurrentHashMap<>();
- final Map<DexField, ProgramMethod> putFieldBridges = new ConcurrentHashMap<>();
- // Common single empty class for nest based private constructors
- private final DexProgramClass nestConstructor;
- private boolean nestConstructorUsed = false;
-
- NestBasedAccessDesugaring(AppView<?> appView) {
- this.appView = appView;
- this.nestConstructor = createNestAccessConstructor();
- }
-
- DexType getNestConstructorType() {
- assert nestConstructor != null;
- return nestConstructor.type;
- }
-
- abstract void reportMissingNestHost(DexClass clazz);
-
- abstract void reportIncompleteNest(List<DexType> nest);
-
- DexClass definitionFor(DexType type) {
- return appView.definitionFor(appView.graphLens().lookupType(type));
- }
-
- private DexEncodedMethod lookupOnHolder(
- DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
- DexMethod rewritten =
- appView.graphLens().lookupMethod(method, context.getReference(), invokeType).getReference();
- return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
- }
-
- private DexEncodedField lookupOnHolder(DexField field) {
- DexField rewritten = appView.graphLens().lookupField(field);
- return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
- }
-
- // Extract the list of types in the programClass' nest, of host hostClass
- private Pair<DexClass, List<DexType>> extractNest(DexClass clazz) {
- assert clazz != null;
- DexClass hostClass = clazz.isNestHost() ? clazz : definitionFor(clazz.getNestHost());
- if (hostClass == null) {
- reportMissingNestHost(clazz);
- // Missing nest host means the class is considered as not being part of a nest.
- clazz.clearNestHost();
- return null;
- }
- List<DexType> classesInNest =
- new ArrayList<>(hostClass.getNestMembersClassAttributes().size() + 1);
- for (NestMemberClassAttribute nestmate : hostClass.getNestMembersClassAttributes()) {
- classesInNest.add(nestmate.getNestMember());
- }
- classesInNest.add(hostClass.type);
- return new Pair<>(hostClass, classesInNest);
- }
-
- Future<?> asyncProcessNest(DexProgramClass clazz, ExecutorService executorService) {
- return executorService.submit(
- () -> {
- Pair<DexClass, List<DexType>> nest = extractNest(clazz);
- // Nest is null when nest host is missing, we do nothing in this case.
- if (nest != null) {
- processNest(nest.getFirst(), nest.getSecond());
- }
- return null; // we want a Callable not a Runnable to be able to throw
- });
- }
-
- private void processNest(DexClass host, List<DexType> nest) {
- boolean reported = false;
- for (DexType type : nest) {
- DexClass clazz = definitionFor(type);
- if (clazz == null) {
- if (!reported) {
- reportIncompleteNest(nest);
- reported = true;
- }
- } else {
- reportDesugarDependencies(host, clazz);
- if (shouldProcessClassInNest(clazz, nest)) {
- for (DexEncodedMethod definition : clazz.methods()) {
- if (clazz.isProgramClass()) {
- ProgramMethod method = new ProgramMethod(clazz.asProgramClass(), definition);
- method.registerCodeReferences(new NestBasedAccessDesugaringUseRegistry(method));
- } else if (clazz.isClasspathClass()) {
- ClasspathMethod method = new ClasspathMethod(clazz.asClasspathClass(), definition);
- method.registerCodeReferencesForDesugaring(
- new NestBasedAccessDesugaringUseRegistry(method));
- } else {
- assert false;
- }
- }
- }
- }
- }
- }
-
- private void reportDesugarDependencies(DexClass host, DexClass clazz) {
- if (host == clazz) {
- return;
- }
- if (host.isProgramClass()) {
- InterfaceMethodRewriter.reportDependencyEdge(host.asProgramClass(), clazz, appView.options());
- }
- if (clazz.isProgramClass()) {
- InterfaceMethodRewriter.reportDependencyEdge(clazz.asProgramClass(), host, appView.options());
- }
- }
-
- protected abstract boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest);
-
- private DexProgramClass createNestAccessConstructor() {
- // TODO(b/176900254): The class generated should be in a package that has lower change of
- // collisions (and perhaps be unique by some hash from the nest).
- return new DexProgramClass(
- appView.dexItemFactory().nestConstructorType,
- null,
- new SynthesizedOrigin("Nest based access desugaring", getClass()),
- // Make the synthesized class public since shared in the whole program.
- ClassAccessFlags.fromDexAccessFlags(
- Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- appView.dexItemFactory().createString("nest"),
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
- }
-
- void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
- if (nestConstructorUsed) {
- appView.appInfo().addSynthesizedClass(nestConstructor, true);
- builder.addSynthesizedClass(nestConstructor);
- }
- }
-
- private DexString computeMethodBridgeName(DexEncodedMethod method) {
- String methodName = method.method.name.toString();
- String fullName;
- if (method.isStatic()) {
- fullName = NEST_ACCESS_STATIC_METHOD_NAME_PREFIX + methodName;
- } else {
- fullName = NEST_ACCESS_METHOD_NAME_PREFIX + methodName;
- }
- return appView.dexItemFactory().createString(fullName);
- }
-
- private DexString computeFieldBridgeName(DexEncodedField field, boolean isGet) {
- String fieldName = field.field.name.toString();
- String fullName;
- if (isGet && !field.isStatic()) {
- fullName = NEST_ACCESS_FIELD_GET_NAME_PREFIX + fieldName;
- } else if (isGet) {
- fullName = NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX + fieldName;
- } else if (!field.isStatic()) {
- fullName = NEST_ACCESS_FIELD_PUT_NAME_PREFIX + fieldName;
- } else {
- fullName = NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX + fieldName;
- }
- return appView.dexItemFactory().createString(fullName);
- }
-
- private DexMethod computeMethodBridge(DexEncodedMethod encodedMethod) {
- DexMethod method = encodedMethod.method;
- DexProto proto =
- encodedMethod.accessFlags.isStatic()
- ? method.proto
- : appView.dexItemFactory().prependHolderToProto(method);
- return appView
- .dexItemFactory()
- .createMethod(method.holder, proto, computeMethodBridgeName(encodedMethod));
- }
-
- private DexMethod computeInitializerBridge(DexMethod method) {
- DexProto newProto =
- appView.dexItemFactory().appendTypeToProto(method.proto, nestConstructor.type);
- return appView.dexItemFactory().createMethod(method.holder, newProto, method.name);
- }
-
- private DexMethod computeFieldBridge(DexEncodedField field, boolean isGet) {
- DexType holderType = field.getHolderType();
- DexType fieldType = field.field.type;
- int bridgeParameterCount =
- BooleanUtils.intValue(!field.isStatic()) + BooleanUtils.intValue(!isGet);
- DexType[] parameters = new DexType[bridgeParameterCount];
- if (!isGet) {
- parameters[parameters.length - 1] = fieldType;
- }
- if (!field.isStatic()) {
- parameters[0] = holderType;
- }
- DexType returnType = isGet ? fieldType : appView.dexItemFactory().voidType;
- DexProto proto = appView.dexItemFactory().createProto(returnType, parameters);
- return appView
- .dexItemFactory()
- .createMethod(holderType, proto, computeFieldBridgeName(field, isGet));
- }
-
- boolean invokeRequiresRewriting(DexEncodedMethod method, DexClassAndMethod context) {
- assert method != null;
- // Rewrite only when targeting other nest members private fields.
- if (!method.accessFlags.isPrivate() || method.getHolderType() == context.getHolderType()) {
- return false;
- }
- DexClass methodHolder = definitionFor(method.getHolderType());
- assert methodHolder != null; // from encodedMethod
- return methodHolder.getNestHost() == context.getHolder().getNestHost();
- }
-
- boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClassAndMethod context) {
- assert field != null;
- // Rewrite only when targeting other nest members private fields.
- if (!field.accessFlags.isPrivate() || field.getHolderType() == context.getHolderType()) {
- return false;
- }
- DexClass fieldHolder = definitionFor(field.getHolderType());
- assert fieldHolder != null; // from encodedField
- return fieldHolder.getNestHost() == context.getHolder().getNestHost();
- }
-
- private boolean holderRequiresBridge(DexClass holder) {
- // Bridges are added on program classes only.
- // Bridges on class paths are added in different compilation units.
- if (holder.isProgramClass()) {
- return false;
- } else if (holder.isClasspathClass()) {
- return true;
- }
- assert holder.isLibraryClass();
- Pair<DexClass, List<DexType>> nest = extractNest(holder);
- assert nest != null : "Should be a compilation error if missing nest host on library class.";
- reportIncompleteNest(nest.getSecond());
- throw new Unreachable(
- "Incomplete nest due to missing library class should raise a compilation error.");
- }
-
- DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
- DexClass holder = definitionFor(field.getHolderType());
- assert holder != null;
- DexMethod bridgeMethod = computeFieldBridge(field, isGet);
- if (holderRequiresBridge(holder)) {
- return bridgeMethod;
- }
- assert holder.isProgramClass();
- // The map is used to avoid creating multiple times the bridge
- // and remembers the bridges to add.
- Map<DexField, ProgramMethod> fieldMap = isGet ? getFieldBridges : putFieldBridges;
- assert holder.isProgramClass();
- fieldMap.computeIfAbsent(
- field.field,
- k ->
- DexEncodedMethod.createFieldAccessorBridge(
- new DexFieldWithAccess(field, isGet), holder.asProgramClass(), bridgeMethod));
- return bridgeMethod;
- }
-
- DexMethod ensureInvokeBridge(DexEncodedMethod method) {
- // We add bridges only when targeting other nest members.
- DexClass holder = definitionFor(method.getHolderType());
- assert holder != null;
- DexMethod bridgeMethod;
- if (method.isInstanceInitializer()) {
- nestConstructorUsed = true;
- bridgeMethod = computeInitializerBridge(method.method);
- } else {
- bridgeMethod = computeMethodBridge(method);
- }
- if (holderRequiresBridge(holder)) {
- return bridgeMethod;
- }
- // The map is used to avoid creating multiple times the bridge
- // and remembers the bridges to add.
- assert holder.isProgramClass();
- bridges.computeIfAbsent(
- method.method,
- k ->
- method.isInstanceInitializer()
- ? method.toInitializerForwardingBridge(holder.asProgramClass(), bridgeMethod)
- : method.toStaticForwardingBridge(
- holder.asProgramClass(), computeMethodBridge(method)));
- return bridgeMethod;
- }
-
- protected class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
-
- private final DexClassAndMethod context;
-
- NestBasedAccessDesugaringUseRegistry(DexClassAndMethod context) {
- super(appView.options().itemFactory);
- this.context = context;
- }
-
- private void registerInvoke(DexMethod method, Invoke.Type invokeType) {
- // Calls to non class type are not done through nest based access control.
- // Work-around for calls to enum.clone().
- if (!method.holder.isClassType()) {
- return;
- }
- DexEncodedMethod encodedMethod = lookupOnHolder(method, context, invokeType);
- if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) {
- ensureInvokeBridge(encodedMethod);
- }
- }
-
- private void registerFieldAccess(DexField field, boolean isGet) {
- // Since we only need to desugar accesses to private fields, and all accesses to private
- // fields must be accessing the private field directly on its holder, we can lookup the field
- // on the holder instead of resolving the field.
- DexEncodedField encodedField = lookupOnHolder(field);
- if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) {
- ensureFieldAccessBridge(encodedField, isGet);
- }
- }
-
- @Override
- public void registerInitClass(DexType clazz) {
- // Nothing to do since we always use a public field for initializing the class.
- }
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {
- // Calls to class nest mate private methods are targeted by invokeVirtual in jdk11.
- // The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
- registerInvoke(method, Invoke.Type.VIRTUAL);
- }
-
- @Override
- public void registerInvokeDirect(DexMethod method) {
- registerInvoke(method, Invoke.Type.DIRECT);
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method) {
- registerInvoke(method, Invoke.Type.STATIC);
- }
-
- @Override
- public void registerInvokeInterface(DexMethod method) {
- // Calls to interface nest mate private methods are targeted by invokeInterface in jdk11.
- // The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
- registerInvoke(method, Invoke.Type.INTERFACE);
- }
-
- @Override
- public void registerInvokeSuper(DexMethod method) {
- registerInvoke(method, Invoke.Type.SUPER);
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- registerFieldAccess(field, false);
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- registerFieldAccess(field, true);
- }
-
- @Override
- public void registerNewInstance(DexType type) {
- // Unrelated to access based control.
- // The <init> method has to be rewritten instead
- // and <init> is called through registerInvoke.
- }
-
- @Override
- public void registerStaticFieldRead(DexField field) {
- registerFieldAccess(field, true);
- }
-
- @Override
- public void registerStaticFieldWrite(DexField field) {
- registerFieldAccess(field, false);
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- // Unrelated to access based control.
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- // Unrelated to access based control.
- }
- }
-
- public static final class DexFieldWithAccess {
-
- private final DexEncodedField field;
- private final boolean isGet;
-
- DexFieldWithAccess(DexEncodedField field, boolean isGet) {
- this.field = field;
- this.isGet = isGet;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(field, isGet);
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null) {
- return false;
- }
- if (getClass() != o.getClass()) {
- return false;
- }
- DexFieldWithAccess other = (DexFieldWithAccess) o;
- return isGet == other.isGet && field == other.field;
- }
-
- public boolean isGet() {
- return isGet;
- }
-
- public boolean isStatic() {
- return field.accessFlags.isStatic();
- }
-
- public boolean isPut() {
- return !isGet();
- }
-
- public boolean isInstance() {
- return !isStatic();
- }
-
- public boolean isStaticGet() {
- return isStatic() && isGet();
- }
-
- public boolean isStaticPut() {
- return isStatic() && isPut();
- }
-
- public boolean isInstanceGet() {
- return isInstance() && isGet();
- }
-
- public boolean isInstancePut() {
- return isInstance() && isPut();
- }
-
- public DexType getType() {
- return field.field.type;
- }
-
- public DexType getHolder() {
- return field.getHolderType();
- }
-
- public DexField getField() {
- return field.field;
- }
- }
-}
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
deleted file mode 100644
index 12418b3..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
-import com.google.common.collect.ImmutableMap;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-public class NestedPrivateMethodLens extends NestedGraphLens {
-
- // Map from nestHost to nest members including nest hosts
- private final DexType nestConstructorType;
- private final Map<DexField, DexMethod> getFieldMap;
- private final Map<DexField, DexMethod> putFieldMap;
-
- NestedPrivateMethodLens(
- AppView<?> appView,
- DexType nestConstructorType,
- Map<DexMethod, DexMethod> methodMap,
- Map<DexField, DexMethod> getFieldMap,
- Map<DexField, DexMethod> putFieldMap,
- GraphLens previousLens) {
- super(
- ImmutableMap.of(),
- methodMap,
- new EmptyBidirectionalOneToOneMap<>(),
- new EmptyBidirectionalOneToOneMap<>(),
- previousLens,
- appView.dexItemFactory());
- // No concurrent maps here, we do not want synchronization overhead.
- assert methodMap instanceof IdentityHashMap;
- assert getFieldMap instanceof IdentityHashMap;
- assert putFieldMap instanceof IdentityHashMap;
- this.nestConstructorType = nestConstructorType;
- this.getFieldMap = getFieldMap;
- this.putFieldMap = putFieldMap;
- }
-
- private DexMethod lookupFieldForMethod(
- DexField field, DexMethod context, Map<DexField, DexMethod> map) {
- assert context != null;
- DexMethod bridge = map.get(field);
- if (bridge != null && bridge.holder != context.holder) {
- return bridge;
- }
- return null;
- }
-
- @Override
- public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
- assert getPrevious().lookupGetFieldForMethod(field, context) == null;
- return lookupFieldForMethod(field, context, getFieldMap);
- }
-
- @Override
- public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
- assert getPrevious().lookupPutFieldForMethod(field, context) == null;
- return lookupFieldForMethod(field, context, putFieldMap);
- }
-
- @Override
- public boolean isContextFreeForMethods() {
- return methodMap.isEmpty();
- }
-
- @Override
- public boolean verifyIsContextFreeForMethod(DexMethod method) {
- assert !methodMap.containsKey(getPrevious().lookupMethod(method));
- return true;
- }
-
- // This is true because mappings specific to this class can be filled.
- @Override
- protected boolean isLegitimateToHaveEmptyMappings() {
- return true;
- }
-
- private boolean isConstructorBridge(DexMethod method) {
- DexType[] parameters = method.proto.parameters.values;
- if (parameters.length == 0) {
- return false;
- }
- DexType lastParameterType = parameters[parameters.length - 1];
- return lastParameterType == nestConstructorType;
- }
-
- @Override
- protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
- RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
- if (isConstructorBridge(method)) {
- // TODO (b/132767654): Try to write a test which breaks that assertion.
- assert prototypeChanges.isEmpty();
- // TODO(b/164901008): Fix when the number of arguments overflows.
- return RewrittenPrototypeDescription.none().withExtraUnusedNullParameter();
- }
- return prototypeChanges;
- }
-
- @Override
- public MethodLookupResult internalDescribeLookupMethod(
- MethodLookupResult previous, DexMethod context) {
- DexMethod bridge = methodMap.get(previous.getReference());
- if (bridge == null) {
- return previous;
- }
- assert context != null : "Guaranteed by isContextFreeForMethod";
- if (bridge.holder == context.holder) {
- return previous;
- }
- MethodLookupResult.Builder resultBuilder =
- MethodLookupResult.builder(this).setReference(bridge);
- if (isConstructorBridge(bridge)) {
- resultBuilder
- .setPrototypeChanges(
- internalDescribePrototypeChanges(previous.getPrototypeChanges(), bridge))
- .setType(Type.DIRECT);
- } else {
- resultBuilder.setPrototypeChanges(previous.getPrototypeChanges()).setType(Type.STATIC);
- }
- return resultBuilder.build();
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- public static class Builder extends NestedGraphLens.Builder {
-
- private Map<DexField, DexMethod> getFieldMap = new IdentityHashMap<>();
- private Map<DexField, DexMethod> putFieldMap = new IdentityHashMap<>();
-
- public void mapGetField(DexField from, DexMethod to) {
- getFieldMap.put(from, to);
- }
-
- public void mapPutField(DexField from, DexMethod to) {
- putFieldMap.put(from, to);
- }
-
- public NestedPrivateMethodLens build(AppView<?> appView, DexType nestConstructorType) {
- assert typeMap.isEmpty();
- assert fieldMap.isEmpty();
- if (getFieldMap.isEmpty() && methodMap.isEmpty() && putFieldMap.isEmpty()) {
- return null;
- }
- return new NestedPrivateMethodLens(
- appView, nestConstructorType, methodMap, getFieldMap, putFieldMap, appView.graphLens());
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
deleted file mode 100644
index e9ac116..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ /dev/null
@@ -1,119 +0,0 @@
-// 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.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
-
-// Summary:
-// - Computes all the live nests reachable from Program Classes (Sequential), each time a
-// nest is computed, its processing starts concurrently.
-// - Add bridges to be processed by further passes and create the maps
-// for the lens (Sequential)
-public class R8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
-
- public R8NestBasedAccessDesugaring(AppView<?> appView) {
- super(appView);
- }
-
- public NestedPrivateMethodLens run(
- ExecutorService executorService, DexApplication.Builder<?> appBuilder)
- throws ExecutionException {
- assert !appView.options().canUseNestBasedAccess()
- || appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
- computeAndProcessNestsConcurrently(executorService);
- NestedPrivateMethodLens.Builder lensBuilder = NestedPrivateMethodLens.builder();
- addDeferredBridgesAndMapMethods(lensBuilder);
- clearNestAttributes();
- synthesizeNestConstructor(appBuilder);
- return lensBuilder.build(appView, getNestConstructorType());
- }
-
- private void addDeferredBridgesAndMapMethods(NestedPrivateMethodLens.Builder lensBuilder) {
- // Here we add the bridges and we fill the lens map.
- addDeferredBridgesAndMapMethods(bridges, lensBuilder::map);
- addDeferredBridgesAndMapMethods(getFieldBridges, lensBuilder::mapGetField);
- addDeferredBridgesAndMapMethods(putFieldBridges, lensBuilder::mapPutField);
- }
-
- private <E> void addDeferredBridgesAndMapMethods(
- Map<E, ProgramMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
- for (Map.Entry<E, ProgramMethod> entry : bridges.entrySet()) {
- ProgramMethod method = entry.getValue();
- method.getHolder().addMethod(method.getDefinition());
- lensInserter.accept(entry.getKey(), method.getReference());
- }
- bridges.clear();
- }
-
- private void clearNestAttributes() {
- // Clearing nest attributes is required to allow class merging to be performed across nests
- // and to forbid other optimizations to introduce nest based accesses.
- for (DexClass clazz : appView.appInfo().classes()) {
- clazz.clearNestHost();
- clazz.getNestMembersClassAttributes().clear();
- }
- }
-
- private void computeAndProcessNestsConcurrently(ExecutorService executorService)
- throws ExecutionException {
- Set<DexType> nestHosts = Sets.newIdentityHashSet();
- ;
- List<Future<?>> futures = new ArrayList<>();
- // It is possible that a nest member is on the program path but its nest host
- // is only in the class path (or missing, raising an error).
- // Nests are therefore computed the first time a nest member is met, host or not.
- // The computedNestHosts list is there to avoid processing multiple times the same nest.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (clazz.isInANest()) {
- DexType hostType = clazz.getNestHost();
- if (!nestHosts.contains(hostType)) {
- nestHosts.add(hostType);
- futures.add(asyncProcessNest(clazz, executorService));
- }
- }
- }
- ThreadUtils.awaitFutures(futures);
- }
-
- // In R8, all classes are processed ahead of time.
- @Override
- protected boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest) {
- return true;
- }
-
- @Override
- void reportMissingNestHost(DexClass clazz) {
- if (appView.options().ignoreMissingClasses) {
- appView.options().nestDesugaringWarningMissingNestHost(clazz);
- } else {
- appView.options().errorMissingClassMissingNestHost(clazz);
- }
- }
-
- @Override
- void reportIncompleteNest(List<DexType> nest) {
- if (appView.options().ignoreMissingClasses) {
- appView.options().nestDesugaringWarningIncompleteNest(nest, appView);
- } else {
- appView.options().errorMissingClassIncompleteNest(nest, appView);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
new file mode 100644
index 0000000..de211e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2021, 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.desugar.nest;
+
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.reportDependencyEdge;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Responsible for reporting desugar dependencies and for synthesizing bridges in the program for
+ * accesses from the classpath into the program.
+ */
+public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
+
+ public D8NestBasedAccessDesugaring(AppView<?> appView) {
+ super(appView);
+ }
+
+ public void reportDesugarDependencies() {
+ forEachNest(
+ nest -> {
+ DexClass hostClass = nest.getHostClass();
+ for (DexClass memberClass : nest.getMembers()) {
+ if (hostClass.isProgramClass() || memberClass.isProgramClass()) {
+ reportDependencyEdge(hostClass, memberClass, appView.options());
+ reportDependencyEdge(memberClass, hostClass, appView.options());
+ }
+ }
+ });
+ }
+
+ public void synthesizeBridgesForNestBasedAccessesOnClasspath(
+ MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
+ List<DexClasspathClass> classpathClassesInNests = new ArrayList<>();
+ forEachNest(
+ nest -> {
+ if (nest.getHostClass().isClasspathClass()) {
+ classpathClassesInNests.add(nest.getHostClass().asClasspathClass());
+ }
+ Iterables.addAll(classpathClassesInNests, nest.getClasspathMembers());
+ });
+
+ NestBridgeConsumer bridgeConsumer = NestBridgeConsumer.createForD8(methodProcessor);
+ ThreadUtils.processItems(
+ classpathClassesInNests,
+ clazz -> synthesizeBridgesForNestBasedAccessesOnClasspath(clazz, bridgeConsumer),
+ executorService);
+ }
+
+ public void synthesizeBridgesForNestBasedAccessesOnClasspath(
+ DexClasspathClass clazz, NestBridgeConsumer bridgeConsumer) {
+ clazz.forEachClasspathMethod(
+ method ->
+ method.registerCodeReferencesForDesugaring(
+ new NestBasedAccessDesugaringUseRegistry(method, bridgeConsumer)));
+ }
+
+ private class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
+
+ private final NestBridgeConsumer bridgeConsumer;
+ private final ClasspathMethod context;
+
+ NestBasedAccessDesugaringUseRegistry(
+ ClasspathMethod context, NestBridgeConsumer bridgeConsumer) {
+ super(appView.dexItemFactory());
+ this.bridgeConsumer = bridgeConsumer;
+ this.context = context;
+ }
+
+ private void registerFieldAccess(DexField reference, boolean isGet) {
+ DexClassAndField field =
+ reference.lookupMemberOnClass(appView.definitionForHolder(reference));
+ if (field != null && needsDesugaring(field, context)) {
+ ensureFieldAccessBridge(field, isGet, bridgeConsumer);
+ }
+ }
+
+ private void registerInvoke(DexMethod reference) {
+ if (!reference.getHolderType().isClassType()) {
+ return;
+ }
+ DexClassAndMethod method =
+ reference.lookupMemberOnClass(appView.definitionForHolder(reference));
+ if (method != null && needsDesugaring(method, context)) {
+ ensureMethodBridge(method, bridgeConsumer);
+ }
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ registerInvoke(method);
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ registerInvoke(method);
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ registerInvoke(method);
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ registerInvoke(method);
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ registerInvoke(method);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldAccess(field, false);
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldAccess(field, true);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldAccess(field, true);
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldAccess(field, false);
+ }
+
+ @Override
+ public void registerInitClass(DexType clazz) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ // Intentionally empty.
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
new file mode 100644
index 0000000..7468cba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, 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.desugar.nest;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+
+public class D8NestBridgeConsumer extends NestBridgeConsumer {
+
+ private final MethodProcessor methodProcessor;
+
+ public D8NestBridgeConsumer(MethodProcessor methodProcessor) {
+ this.methodProcessor = methodProcessor;
+ }
+
+ @Override
+ public void acceptFieldGetBridge(ProgramField target, ProgramMethod bridge) {
+ methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+ }
+
+ @Override
+ public void acceptFieldPutBridge(ProgramField target, ProgramMethod bridge) {
+ methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+ }
+
+ @Override
+ public void acceptMethodBridge(ProgramMethod target, ProgramMethod bridge) {
+ methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/Nest.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/Nest.java
new file mode 100644
index 0000000..1e66edc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/Nest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2021, 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.desugar.nest;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Nest {
+
+ private final DexClass hostClass;
+ private final List<DexClass> members;
+ private final List<DexType> missingMembers;
+
+ private Nest(DexClass hostClass, List<DexClass> members, List<DexType> missingMembers) {
+ this.hostClass = hostClass;
+ this.members = members;
+ this.missingMembers = missingMembers;
+ }
+
+ public static Nest create(AppView<?> appView, DexClass clazz) {
+ assert clazz.isInANest();
+
+ DexClass hostClass = clazz.isNestHost() ? clazz : appView.definitionFor(clazz.getNestHost());
+ if (hostClass == null) {
+ // Missing nest host means the class is considered as not being part of a nest.
+ clazz.clearNestHost();
+ appView.options().nestDesugaringWarningMissingNestHost(clazz);
+ return null;
+ }
+
+ List<DexClass> members = new ArrayList<>(hostClass.getNestMembersClassAttributes().size());
+ List<DexType> missingMembers = new ArrayList<>();
+ hostClass.forEachNestMember(
+ memberType -> {
+ DexClass memberClass = appView.definitionFor(memberType);
+ if (memberClass != null) {
+ members.add(memberClass);
+ } else {
+ missingMembers.add(memberType);
+ }
+ });
+ return new Nest(hostClass, members, missingMembers);
+ }
+
+ public Iterable<DexClasspathClass> getClasspathMembers() {
+ return Iterables.transform(
+ Iterables.filter(members, DexClass::isClasspathClass), DexClass::asClasspathClass);
+ }
+
+ public DexClass getHostClass() {
+ return hostClass;
+ }
+
+ public List<DexClass> getMembers() {
+ return members;
+ }
+
+ public List<DexType> getMissingMembers() {
+ return missingMembers;
+ }
+
+ public boolean hasLibraryMember() {
+ return Iterables.any(members, DexClass::isLibraryClass);
+ }
+
+ public boolean hasMissingMembers() {
+ return !missingMembers.isEmpty();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
new file mode 100644
index 0000000..bab308e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -0,0 +1,376 @@
+// Copyright (c) 2021, 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.desugar.nest;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.LibraryMember;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
+
+// NestBasedAccessDesugaring contains common code between the two subclasses
+// which are specialized for d8 and r8
+public class NestBasedAccessDesugaring {
+
+ // Short names to avoid creating long strings
+ public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
+ private static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
+ private static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX =
+ NEST_ACCESS_NAME_PREFIX + "sm";
+ private static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
+ private static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
+ NEST_ACCESS_NAME_PREFIX + "sfget";
+ private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
+ private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
+ NEST_ACCESS_NAME_PREFIX + "sfput";
+ public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
+
+ protected final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+
+ // Common single empty class for nest based private constructors
+ private DexProgramClass nestConstructor;
+ private boolean nestConstructorUsed;
+
+ public NestBasedAccessDesugaring(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ void forEachNest(Consumer<Nest> consumer) {
+ Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (!clazz.isInANest() || !seenNestHosts.add(clazz.getNestHost())) {
+ continue;
+ }
+
+ Nest nest = Nest.create(appView, clazz);
+ if (nest != null) {
+ consumer.accept(nest);
+ }
+ }
+ }
+
+ public boolean needsDesugaring(ProgramMethod method) {
+ if (!method.getHolder().isInANest() || !method.getDefinition().hasCode()) {
+ return false;
+ }
+
+ Code code = method.getDefinition().getCode();
+ if (code.isDexCode()) {
+ assert appView.testing().allowDexInputForTesting;
+ return false;
+ }
+
+ if (!code.isCfCode()) {
+ throw new Unreachable("Unexpected attempt to determine if non-CF code needs desugaring");
+ }
+
+ return Iterables.any(
+ code.asCfCode().getInstructions(), instruction -> needsDesugaring(instruction, method));
+ }
+
+ private boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ if (instruction.isFieldInstruction()) {
+ return needsDesugaring(instruction.asFieldInstruction().getField(), context);
+ }
+ if (instruction.isInvoke()) {
+ return needsDesugaring(instruction.asInvoke().getMethod(), context);
+ }
+ return false;
+ }
+
+ public boolean needsDesugaring(DexMember<?, ?> memberReference, ProgramMethod context) {
+ if (!context.getHolder().isInANest() || !memberReference.getHolderType().isClassType()) {
+ return false;
+ }
+ DexClass holder = appView.definitionForHolder(memberReference, context);
+ DexClassAndMember<?, ?> member = memberReference.lookupMemberOnClass(holder);
+ return member != null && needsDesugaring(member, context);
+ }
+
+ public boolean needsDesugaring(DexClassAndMember<?, ?> member, DexClassAndMethod context) {
+ return member.getAccessFlags().isPrivate()
+ && member.getHolderType() != context.getHolderType()
+ && member.getHolder().isInANest()
+ && member.getHolder().getNestHost() == context.getHolder().getNestHost();
+ }
+
+ public boolean desugar(ProgramMethod method) {
+ return desugar(method, null);
+ }
+
+ public boolean desugar(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
+ if (!method.getHolder().isInANest()) {
+ return false;
+ }
+
+ Code code = method.getDefinition().getCode();
+ if (!code.isCfCode()) {
+ appView
+ .options()
+ .reporter
+ .error(
+ new StringDiagnostic(
+ "Unsupported attempt to desugar non-CF code",
+ method.getOrigin(),
+ method.getPosition()));
+ return false;
+ }
+
+ CfCode cfCode = code.asCfCode();
+ List<CfInstruction> desugaredInstructions =
+ ListUtils.flatMap(
+ cfCode.getInstructions(),
+ instruction -> desugarInstruction(instruction, method, bridgeConsumer),
+ null);
+ if (desugaredInstructions != null) {
+ cfCode.setInstructions(desugaredInstructions);
+ return true;
+ }
+ return false;
+ }
+
+ public List<CfInstruction> desugarInstruction(
+ CfInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+ if (instruction.isFieldInstruction()) {
+ return desugarFieldInstruction(instruction.asFieldInstruction(), context, bridgeConsumer);
+ }
+ if (instruction.isInvoke()) {
+ return desugarInvokeInstruction(instruction.asInvoke(), context, bridgeConsumer);
+ }
+ return null;
+ }
+
+ private List<CfInstruction> desugarFieldInstruction(
+ CfFieldInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+ // Since we only need to desugar accesses to private fields, and all accesses to private
+ // fields must be accessing the private field directly on its holder, we can lookup the
+ // field on the holder instead of resolving the field.
+ DexClass holder = appView.definitionForHolder(instruction.getField(), context);
+ DexClassAndField field = instruction.getField().lookupMemberOnClass(holder);
+ if (field == null || !needsDesugaring(field, context)) {
+ return null;
+ }
+
+ DexMethod bridge = ensureFieldAccessBridge(field, instruction.isFieldGet(), bridgeConsumer);
+ return ImmutableList.of(
+ new CfInvoke(Opcodes.INVOKESTATIC, bridge, field.getHolder().isInterface()));
+ }
+
+ private List<CfInstruction> desugarInvokeInstruction(
+ CfInvoke invoke, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+ DexMethod invokedMethod = invoke.getMethod();
+ if (!invokedMethod.getHolderType().isClassType()) {
+ return null;
+ }
+
+ // Since we only need to desugar accesses to private methods, and all accesses to private
+ // methods must be accessing the private method directly on its holder, we can lookup the
+ // method on the holder instead of resolving the method.
+ DexClass holder = appView.definitionForHolder(invokedMethod, context);
+ DexClassAndMethod target = invokedMethod.lookupMemberOnClass(holder);
+ if (target == null || !needsDesugaring(target, context)) {
+ return null;
+ }
+
+ DexMethod bridge = ensureMethodBridge(target, bridgeConsumer);
+ if (target.getDefinition().isInstanceInitializer()) {
+ assert !invoke.isInterface();
+ return ImmutableList.of(
+ new CfConstNull(), new CfInvoke(Opcodes.INVOKESPECIAL, bridge, false));
+ }
+
+ return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, bridge, invoke.isInterface()));
+ }
+
+ private RuntimeException reportIncompleteNest(LibraryMember<?, ?> member) {
+ Nest nest = Nest.create(appView, member.getHolder());
+ assert nest != null : "Should be a compilation error if missing nest host on library class.";
+ throw appView.options().errorMissingClassIncompleteNest(nest);
+ }
+
+ private DexProgramClass createNestAccessConstructor() {
+ // TODO(b/176900254): ensure hygienic synthetic class.
+ return new DexProgramClass(
+ dexItemFactory.nestConstructorType,
+ null,
+ new SynthesizedOrigin("Nest based access desugaring", getClass()),
+ // Make the synthesized class public since shared in the whole program.
+ ClassAccessFlags.fromDexAccessFlags(
+ Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
+ dexItemFactory.objectType,
+ DexTypeList.empty(),
+ dexItemFactory.createString("nest"),
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ ClassSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ dexItemFactory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ }
+
+ public DexProgramClass synthesizeNestConstructor() {
+ if (nestConstructorUsed && nestConstructor == null) {
+ nestConstructor = createNestAccessConstructor();
+ return nestConstructor;
+ }
+ return null;
+ }
+
+ DexMethod ensureFieldAccessBridge(
+ DexClassAndField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
+ if (field.isProgramField()) {
+ return ensureFieldAccessBridge(field.asProgramField(), isGet, bridgeConsumer);
+ }
+ if (field.isClasspathField()) {
+ return getFieldAccessBridgeReference(field, isGet);
+ }
+ assert field.isLibraryField();
+ throw reportIncompleteNest(field.asLibraryField());
+ }
+
+ private DexMethod ensureFieldAccessBridge(
+ ProgramField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
+ DexMethod bridgeReference = getFieldAccessBridgeReference(field, isGet);
+ synchronized (field.getHolder().getMethodCollection()) {
+ ProgramMethod bridge = field.getHolder().lookupProgramMethod(bridgeReference);
+ if (bridge == null) {
+ bridge = DexEncodedMethod.createFieldAccessorBridge(field, isGet, bridgeReference);
+ bridge.getHolder().addDirectMethod(bridge.getDefinition());
+ if (bridgeConsumer != null) {
+ bridgeConsumer.acceptFieldBridge(field, bridge, isGet);
+ }
+ }
+ return bridge.getReference();
+ }
+ }
+
+ private DexMethod getFieldAccessBridgeReference(DexClassAndField field, boolean isGet) {
+ int bridgeParameterCount =
+ BooleanUtils.intValue(!field.getAccessFlags().isStatic()) + BooleanUtils.intValue(!isGet);
+ DexType[] parameters = new DexType[bridgeParameterCount];
+ if (!isGet) {
+ parameters[parameters.length - 1] = field.getType();
+ }
+ if (!field.getAccessFlags().isStatic()) {
+ parameters[0] = field.getHolderType();
+ }
+ DexType returnType = isGet ? field.getType() : dexItemFactory.voidType;
+ DexProto proto = dexItemFactory.createProto(returnType, parameters);
+ return dexItemFactory.createMethod(
+ field.getHolderType(), proto, getFieldAccessBridgeName(field, isGet));
+ }
+
+ private DexString getFieldAccessBridgeName(DexClassAndField field, boolean isGet) {
+ String prefix;
+ if (isGet && !field.getAccessFlags().isStatic()) {
+ prefix = NEST_ACCESS_FIELD_GET_NAME_PREFIX;
+ } else if (isGet) {
+ prefix = NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX;
+ } else if (!field.getAccessFlags().isStatic()) {
+ prefix = NEST_ACCESS_FIELD_PUT_NAME_PREFIX;
+ } else {
+ prefix = NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX;
+ }
+ return dexItemFactory.createString(prefix + field.getName().toString());
+ }
+
+ DexMethod ensureMethodBridge(DexClassAndMethod method, NestBridgeConsumer bridgeConsumer) {
+ if (method.isProgramMethod()) {
+ return ensureMethodBridge(method.asProgramMethod(), bridgeConsumer);
+ }
+ if (method.isClasspathMethod()) {
+ return getMethodBridgeReference(method);
+ }
+ assert method.isLibraryMethod();
+ throw reportIncompleteNest(method.asLibraryMethod());
+ }
+
+ private DexMethod ensureMethodBridge(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
+ DexMethod bridgeReference = getMethodBridgeReference(method);
+ synchronized (method.getHolder().getMethodCollection()) {
+ ProgramMethod bridge = method.getHolder().lookupProgramMethod(bridgeReference);
+ if (bridge == null) {
+ DexEncodedMethod definition = method.getDefinition();
+ bridge =
+ definition.isInstanceInitializer()
+ ? definition.toInitializerForwardingBridge(
+ method.getHolder(), bridgeReference, dexItemFactory)
+ : definition.toStaticForwardingBridge(
+ method.getHolder(), bridgeReference, dexItemFactory);
+ bridge.getHolder().addDirectMethod(bridge.getDefinition());
+ if (bridgeConsumer != null) {
+ bridgeConsumer.acceptMethodBridge(method, bridge);
+ }
+ }
+ }
+ return bridgeReference;
+ }
+
+ private DexMethod getMethodBridgeReference(DexClassAndMethod method) {
+ if (method.getDefinition().isInstanceInitializer()) {
+ DexProto newProto =
+ dexItemFactory.appendTypeToProto(method.getProto(), dexItemFactory.nestConstructorType);
+ nestConstructorUsed = true;
+ return method.getReference().withProto(newProto, dexItemFactory);
+ }
+ DexProto proto =
+ method.getAccessFlags().isStatic()
+ ? method.getProto()
+ : dexItemFactory.prependHolderToProto(method.getReference());
+ return dexItemFactory.createMethod(method.getHolderType(), proto, getMethodBridgeName(method));
+ }
+
+ private DexString getMethodBridgeName(DexClassAndMethod method) {
+ String prefix =
+ method.getAccessFlags().isStatic()
+ ? NEST_ACCESS_STATIC_METHOD_NAME_PREFIX
+ : NEST_ACCESS_METHOD_NAME_PREFIX;
+ return dexItemFactory.createString(prefix + method.getName().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.java
new file mode 100644
index 0000000..e6a8a03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, 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.desugar.nest;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+
+public abstract class NestBridgeConsumer {
+
+ public static D8NestBridgeConsumer createForD8(MethodProcessor methodProcessor) {
+ return new D8NestBridgeConsumer(methodProcessor);
+ }
+
+ public abstract void acceptFieldGetBridge(ProgramField target, ProgramMethod bridge);
+
+ public abstract void acceptFieldPutBridge(ProgramField target, ProgramMethod bridge);
+
+ public abstract void acceptMethodBridge(ProgramMethod target, ProgramMethod bridge);
+
+ public final void acceptFieldBridge(ProgramField target, ProgramMethod bridge, boolean isGet) {
+ if (isGet) {
+ acceptFieldGetBridge(target, bridge);
+ } else {
+ acceptFieldPutBridge(target, bridge);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index ef0e5ad..5112116 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1476,19 +1476,16 @@
UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
appView, context, methodProcessingId);
- // TODO(b/172194277): Allow synthetics when generating CF.
- if (throwClassCastExceptionIfNotNullMethod != null) {
- throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
- InvokeStatic replacement =
- InvokeStatic.builder()
- .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
- .setSingleArgument(checkCast.object())
- .setPosition(checkCast)
- .build();
- it.replaceCurrentInstruction(replacement);
- assert replacement.lookupSingleTarget(appView, context) != null;
- return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
- }
+ throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
+ InvokeStatic replacement =
+ InvokeStatic.builder()
+ .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
+ .setSingleArgument(checkCast.object())
+ .setPosition(checkCast)
+ .build();
+ it.replaceCurrentInstruction(replacement);
+ assert replacement.lookupSingleTarget(appView, context) != null;
+ return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
}
// Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java b/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java
index 6fa08c8..e20e28c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java
@@ -4,131 +4,102 @@
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-// This pass
-// - cleans the nests: it removes missing nest host/members from the input and
-// report them (warning or error).
-// - clears nests which do not use nest based access control to allow other
-// optimizations such as class merging to perform better.
+/**
+ * This pass:
+ *
+ * <ul>
+ * <li>cleans the nests: it removes missing nest host/members from the input,
+ * <li>clears nests which do not use nest based access control to allow other optimizations such
+ * as class merging to perform better.
+ * </ul>
+ */
public class NestReducer {
- private AppView<?> appView;
+ private AppView<AppInfoWithLiveness> appView;
- public NestReducer(AppView<?> appView) {
+ public NestReducer(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
- private DexClass definitionFor(DexType type) {
- assert appView.graphLens().lookupType(type) == type;
- return appView.definitionFor(appView.graphLens().lookupType(type));
+ public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("NestReduction");
+ if (appView.options().shouldDesugarNests()) {
+ removeNests();
+ } else {
+ reduceNests(executorService);
+ }
+ timing.end();
}
- public void run(ExecutorService executorService) throws ExecutionException {
- Set<DexType> nestHosts = Sets.newIdentityHashSet();
- List<Future<?>> futures = new ArrayList<>();
- // It is possible that a nest member is on the program path but its nest host
- // is only in the class path.
- // Nests are therefore computed the first time a nest member is met, host or not.
- // The computedNestHosts list is there to avoid processing multiple times the same nest.
+ private void removeNests() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
- DexType hostType = clazz.getNestHost();
- if (hostType != null && !nestHosts.contains(hostType)) {
- nestHosts.add(hostType);
- futures.add(
- executorService.submit(
- () -> {
- processNestFrom(clazz);
- return null; // we want a Callable not a Runnable to be able to throw
- }));
- }
- }
- ThreadUtils.awaitFutures(futures);
- }
-
- private void processNestFrom(DexClass clazz) {
- DexClass nestHost = definitionFor(clazz.getNestHost());
- if (nestHost == null) {
- reportMissingNestHost(clazz);
- clazz.clearNestHost();
- return;
- }
- boolean hasPrivateMembers = hasPrivateMembers(nestHost);
- Iterator<NestMemberClassAttribute> iterator =
- nestHost.getNestMembersClassAttributes().iterator();
- boolean reported = false;
- while (iterator.hasNext()) {
- DexClass member = definitionFor(iterator.next().getNestMember());
- if (member == null) {
- if (!reported) {
- reported = true;
- reportIncompleteNest(nestHost);
+ if (clazz.isInANest()) {
+ if (clazz.isNestHost()) {
+ clazz.clearNestMembers();
+ } else {
+ clazz.clearNestHost();
}
- iterator.remove();
- } else {
- hasPrivateMembers = hasPrivateMembers || hasPrivateMembers(member);
}
}
- if (!hasPrivateMembers && appView.options().enableNestReduction) {
- clearNestAttributes(nestHost);
- }
}
- private void reportMissingNestHost(DexClass clazz) {
- if (appView.options().ignoreMissingClasses) {
- appView.options().warningMissingClassMissingNestHost(clazz);
- } else {
- appView.options().errorMissingClassMissingNestHost(clazz);
- }
- }
-
- private void reportIncompleteNest(DexClass nestHost) {
- List<DexType> nest = new ArrayList<>(nestHost.getNestMembersClassAttributes().size() + 1);
- for (NestMemberClassAttribute attr : nestHost.getNestMembersClassAttributes()) {
- nest.add(attr.getNestMember());
- }
- nest.add(nestHost.type);
- if (appView.options().ignoreMissingClasses) {
- appView.options().warningMissingClassIncompleteNest(nest, appView);
- } else {
- appView.options().errorMissingClassIncompleteNest(nest, appView);
- }
- }
-
- private void clearNestAttributes(DexClass nestHost) {
- nestHost.getNestMembersClassAttributes().clear();
- for (NestMemberClassAttribute attr : nestHost.getNestMembersClassAttributes()) {
- DexClass member = appView.definitionFor(appView.graphLens().lookupType(attr.getNestMember()));
- member.clearNestHost();
- }
- }
-
- private boolean hasPrivateMembers(DexClass clazz) {
- for (DexEncodedMethod method : clazz.methods()) {
- if (method.accessFlags.isPrivate()) {
- return true;
+ private void reduceNests(ExecutorService executorService) throws ExecutionException {
+ Set<DexProgramClass> nestHosts = Sets.newIdentityHashSet();
+ Set<DexProgramClass> nestMembers = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (clazz.isInANest()) {
+ if (clazz.isNestHost()) {
+ nestHosts.add(clazz);
+ } else {
+ nestMembers.add(clazz);
+ }
}
}
- for (DexEncodedField field : clazz.fields()) {
- if (field.accessFlags.isPrivate()) {
- return true;
- }
+ ThreadUtils.processItems(nestHosts, this::processNestHost, executorService);
+ ThreadUtils.processItems(nestMembers, this::processNestMember, executorService);
+ }
+
+ private void processNestHost(DexProgramClass clazz) {
+ BooleanBox nestHasPrivateMembers =
+ new BooleanBox(IterableUtils.hasNext(clazz.members(DexEncodedMember::isPrivate)));
+ clazz
+ .getNestMembersClassAttributes()
+ .removeIf(
+ attribute -> {
+ DexProgramClass member =
+ asProgramClassOrNull(appView.definitionFor(attribute.getNestMember(), clazz));
+ if (member == null) {
+ return true;
+ }
+ nestHasPrivateMembers.computeIfNotSet(
+ () -> IterableUtils.hasNext(member.members(DexEncodedMember::isPrivate)));
+ return false;
+ });
+ if (nestHasPrivateMembers.isFalse() && appView.options().enableNestReduction) {
+ clazz.getNestMembersClassAttributes().clear();
}
- return false;
+ }
+
+ private void processNestMember(DexProgramClass clazz) {
+ DexProgramClass hostClass =
+ asProgramClassOrNull(appView.definitionFor(clazz.getNestHost(), clazz));
+ if (hostClass == null || !hostClass.isNestHost()) {
+ clazz.clearNestHost();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index a972b39..35467e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexItemFactory;
@@ -23,10 +24,6 @@
public static UtilityMethodForCodeOptimizations synthesizeToStringIfNotNullMethod(
AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
InternalOptions options = appView.options();
- if (options.isGeneratingClassFiles()) {
- // TODO(b/172194277): Allow synthetics when generating CF.
- return null;
- }
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -37,9 +34,10 @@
dexItemFactory,
builder ->
builder
- .setProto(proto)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
- .setCode(method -> getToStringIfNotNullCodeTemplate(method, options)),
+ .setClassFileVersion(CfVersion.V1_8)
+ .setCode(method -> getToStringIfNotNullCodeTemplate(method, options))
+ .setProto(proto),
methodProcessingId);
return new UtilityMethodForCodeOptimizations(syntheticMethod);
}
@@ -53,10 +51,6 @@
public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
InternalOptions options = appView.options();
- if (options.isGeneratingClassFiles()) {
- // TODO(b/172194277): Allow synthetics when generating CF.
- return null;
- }
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -67,10 +61,11 @@
dexItemFactory,
builder ->
builder
- .setProto(proto)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setClassFileVersion(CfVersion.V1_8)
.setCode(
- method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options)),
+ method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options))
+ .setProto(proto),
methodProcessingId);
return new UtilityMethodForCodeOptimizations(syntheticMethod);
}
@@ -82,6 +77,33 @@
options, method);
}
+ public static UtilityMethodForCodeOptimizations synthesizeThrowNoSuchMethodErrorMethod(
+ AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+ InternalOptions options = appView.options();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexProto proto = dexItemFactory.createProto(dexItemFactory.noSuchMethodErrorType);
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ ProgramMethod syntheticMethod =
+ syntheticItems.createMethod(
+ SyntheticNaming.SyntheticKind.THROW_NSME,
+ context,
+ dexItemFactory,
+ builder ->
+ builder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setClassFileVersion(CfVersion.V1_8)
+ .setCode(method -> getThrowNoSuchMethodErrorCodeTemplate(method, options))
+ .setProto(proto),
+ methodProcessingId);
+ return new UtilityMethodForCodeOptimizations(syntheticMethod);
+ }
+
+ private static CfCode getThrowNoSuchMethodErrorCodeTemplate(
+ DexMethod method, InternalOptions options) {
+ return CfUtilityMethodsForCodeOptimizations
+ .CfUtilityMethodsForCodeOptimizationsTemplates_throwNoSuchMethodError(options, method);
+ }
+
public static class UtilityMethodForCodeOptimizations {
private final ProgramMethod method;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index 99b1bda..d75e060 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -122,16 +122,13 @@
UtilityMethodForCodeOptimizations toStringIfNotNullMethod =
UtilityMethodsForCodeOptimizations.synthesizeToStringIfNotNullMethod(
appView, code.context(), state.methodProcessingId);
- // TODO(b/172194277): Allow synthetics when generating CF.
- if (toStringIfNotNullMethod != null) {
- toStringIfNotNullMethod.optimize(state.methodProcessor);
- InvokeStatic replacement =
- InvokeStatic.builder()
- .setMethod(toStringIfNotNullMethod.getMethod())
- .setSingleArgument(object)
- .build();
- instructionIterator.replaceCurrentInstruction(replacement);
- }
+ toStringIfNotNullMethod.optimize(state.methodProcessor);
+ InvokeStatic replacement =
+ InvokeStatic.builder()
+ .setMethod(toStringIfNotNullMethod.getMethod())
+ .setSingleArgument(object)
+ .build();
+ instructionIterator.replaceCurrentInstruction(replacement);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 9625b93..a1731e7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -33,6 +33,7 @@
public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
factory.createSynthesizedType("Ljava/lang/ClassCastException;");
+ factory.createSynthesizedType("Ljava/lang/NoSuchMethodError;");
}
public static CfCode
@@ -73,6 +74,29 @@
ImmutableList.of());
}
+ public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_throwNoSuchMethodError(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 0,
+ ImmutableList.of(
+ label0,
+ new CfNew(options.itemFactory.createType("Ljava/lang/NoSuchMethodError;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/NoSuchMethodError;"),
+ options.itemFactory.createProto(options.itemFactory.voidType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow()),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_toStringIfNotNull(
InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
new file mode 100644
index 0000000..b02544d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2021, 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
+
+public class FieldAccessorBuilder {
+
+ private DexField field;
+ private OptionalBool isInstanceField = OptionalBool.unknown();
+ private OptionalBool isSetter = OptionalBool.unknown();
+ private DexMethod sourceMethod;
+
+ private FieldAccessorBuilder() {}
+
+ public static FieldAccessorBuilder builder() {
+ return new FieldAccessorBuilder();
+ }
+
+ public FieldAccessorBuilder apply(Consumer<FieldAccessorBuilder> consumer) {
+ consumer.accept(this);
+ return this;
+ }
+
+ public FieldAccessorBuilder setField(DexClassAndField field) {
+ return field.getAccessFlags().isStatic()
+ ? setStaticField(field.getReference())
+ : setInstanceField(field.getReference());
+ }
+
+ public FieldAccessorBuilder setGetter() {
+ isSetter = OptionalBool.FALSE;
+ return this;
+ }
+
+ public FieldAccessorBuilder setInstanceField(DexField field) {
+ this.field = field;
+ this.isInstanceField = OptionalBool.TRUE;
+ return this;
+ }
+
+ public FieldAccessorBuilder setSetter() {
+ isSetter = OptionalBool.TRUE;
+ return this;
+ }
+
+ public FieldAccessorBuilder setSourceMethod(DexMethod sourceMethod) {
+ this.sourceMethod = sourceMethod;
+ return this;
+ }
+
+ public FieldAccessorBuilder setStaticField(DexField field) {
+ this.field = field;
+ this.isInstanceField = OptionalBool.FALSE;
+ return this;
+ }
+
+ public CfCode build() {
+ assert validate();
+ int maxStack = 0;
+ int maxLocals = 0;
+ Builder<CfInstruction> instructions = ImmutableList.builder();
+ if (isInstanceField()) {
+ // Load the receiver.
+ instructions.add(new CfLoad(ValueType.OBJECT, maxLocals));
+ maxStack += 1;
+ maxLocals += 1;
+ }
+
+ if (isSetter()) {
+ // Load the argument.
+ ValueType fieldType = ValueType.fromDexType(field.getType());
+ instructions.add(new CfLoad(fieldType, maxLocals));
+ maxLocals += fieldType.requiredRegisters();
+ }
+
+ // Get or set the field.
+ int opcode =
+ Opcodes.GETSTATIC + BooleanUtils.intValue(isSetter()) + (isInstanceField.ordinal() << 1);
+ instructions.add(new CfFieldInstruction(opcode, field, field));
+
+ // Return.
+ if (isSetter()) {
+ instructions.add(new CfReturnVoid());
+ } else {
+ ValueType fieldType = ValueType.fromDexType(field.getType());
+ instructions.add(new CfReturn(fieldType));
+ }
+
+ ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+ ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+ return new CfCode(
+ sourceMethod.getHolderType(),
+ maxStack,
+ maxLocals,
+ instructions.build(),
+ tryCatchRanges,
+ localVariables);
+ }
+
+ private boolean isSetter() {
+ return isSetter.isTrue();
+ }
+
+ private boolean isInstanceField() {
+ return isInstanceField.isTrue();
+ }
+
+ private boolean validate() {
+ assert field != null;
+ assert !isInstanceField.isUnknown();
+ assert !isSetter.isUnknown();
+ assert sourceMethod != null;
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorSourceCode.java
deleted file mode 100644
index 8936c73..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorSourceCode.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2017, 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.synthetic;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-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.NestBasedAccessDesugaring.DexFieldWithAccess;
-
-// Source code representing simple forwarding method.
-public final class FieldAccessorSourceCode extends SyntheticSourceCode {
-
- private final DexFieldWithAccess fieldWithAccess;
-
- public FieldAccessorSourceCode(
- DexType receiver,
- DexMethod method,
- Position callerPosition,
- DexMethod originalMethod,
- DexFieldWithAccess field) {
- super(receiver, method, callerPosition, originalMethod);
- this.fieldWithAccess = field;
- assert method.proto.returnType == fieldWithAccess.getType() || fieldWithAccess.isPut();
- }
-
- @Override
- protected void prepareInstructions() {
- if (fieldWithAccess.isInstanceGet()) {
- ValueType valueType = ValueType.fromDexType(proto.returnType);
- int objReg = getParamRegister(0);
- int returnReg = nextRegister(valueType);
- add(builder -> builder.addInstanceGet(returnReg, objReg, fieldWithAccess.getField()));
- add(builder -> builder.addReturn(returnReg));
- } else if (fieldWithAccess.isStaticGet()) {
- ValueType valueType = ValueType.fromDexType(proto.returnType);
- int returnReg = nextRegister(valueType);
- add(builder -> builder.addStaticGet(returnReg, fieldWithAccess.getField()));
- add(builder -> builder.addReturn(returnReg));
- } else if (fieldWithAccess.isInstancePut()) {
- int objReg = getParamRegister(0);
- int putValueReg = getParamRegister(1);
- add(builder -> builder.addInstancePut(putValueReg, objReg, fieldWithAccess.getField()));
- add(IRBuilder::addReturn);
- } else {
- assert fieldWithAccess.isStaticPut();
- int putValueReg = getParamRegister(0);
- add(builder -> builder.addStaticPut(putValueReg, fieldWithAccess.getField()));
- add(IRBuilder::addReturn);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 8d8ca9f..794fd3e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -20,8 +20,10 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
+import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
public class ForwardMethodBuilder {
@@ -41,6 +43,7 @@
private DexMethod sourceMethod = null;
private DexMethod targetMethod = null;
+ private boolean sourceMethodHasExtraUnusedParameter = false;
private boolean staticSource = false;
private InvokeType invokeType = null;
@@ -53,12 +56,24 @@
this.factory = factory;
}
+ public ForwardMethodBuilder apply(Consumer<ForwardMethodBuilder> fn) {
+ fn.accept(this);
+ return this;
+ }
+
public ForwardMethodBuilder setNonStaticSource(DexMethod method) {
sourceMethod = method;
staticSource = false;
return this;
}
+ public ForwardMethodBuilder setNonStaticSourceWithExtraUnusedParameter(DexMethod method) {
+ sourceMethod = method;
+ staticSource = false;
+ sourceMethodHasExtraUnusedParameter = true;
+ return this;
+ }
+
public ForwardMethodBuilder setStaticSource(DexMethod method) {
sourceMethod = method;
staticSource = true;
@@ -79,6 +94,10 @@
return this;
}
+ public ForwardMethodBuilder setConstructorTarget(DexMethod method) {
+ return setDirectTarget(method, false);
+ }
+
public ForwardMethodBuilder setDirectTarget(DexMethod method, boolean isInterface) {
targetMethod = method;
invokeType = InvokeType.SPECIAL;
@@ -126,7 +145,9 @@
maxLocals += 1;
}
DexType[] sourceParameters = getSourceParameters();
- for (int i = 0; i < sourceParameters.length; i++) {
+ for (int i = 0;
+ i < sourceParameters.length - BooleanUtils.intValue(sourceMethodHasExtraUnusedParameter);
+ i++) {
DexType parameter = sourceParameters[i];
ValueType parameterType = ValueType.fromDexType(parameter);
instructions.add(new CfLoad(parameterType, maxLocals));
@@ -212,7 +233,9 @@
}
private int sourceArguments() {
- return sourceMethod.getParameters().size() + (isStaticSource() ? 0 : 1);
+ return sourceMethod.getParameters().size()
+ + (isStaticSource() ? 0 : 1)
+ - BooleanUtils.intValue(sourceMethodHasExtraUnusedParameter);
}
private int targetArguments() {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index f0ba88d..6decd5e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.retrace.RetracedType;
import com.android.tools.r8.retrace.StackTraceElementProxy;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.StringUtils.BraceType;
import com.android.tools.r8.utils.TriFunction;
import java.util.ArrayList;
import java.util.List;
@@ -287,7 +286,7 @@
return original.getMethodArguments();
}
return StringUtils.join(
- retraced.getMethodArguments(), ",", BraceType.NONE, RetracedType::getTypeName);
+ ",", retraced.getMethodArguments(), RetracedType::getTypeName);
});
orderedIndices.add(methodArguments);
return this;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 95cdf4a..1905d90 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
@@ -56,6 +57,7 @@
import com.android.tools.r8.graph.LookupLambdaTarget;
import com.android.tools.r8.graph.LookupTarget;
import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
@@ -88,6 +90,7 @@
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -107,11 +110,13 @@
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.WorkList;
@@ -121,6 +126,7 @@
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
@@ -137,6 +143,7 @@
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -210,6 +217,7 @@
// Don't hold a direct pointer to app info (use appView).
private AppInfoWithClassHierarchy appInfo;
private final AppView<AppInfoWithClassHierarchy> appView;
+ private final ExecutorService executorService;
private SubtypingInfo subtypingInfo;
private final InternalOptions options;
private RootSet rootSet;
@@ -371,7 +379,9 @@
private final LambdaRewriter lambdaRewriter;
private final BackportedMethodRewriter backportRewriter;
+ private final NestBasedAccessDesugaring nestBasedAccessRewriter;
private final TwrCloseResourceRewriter twrCloseResourceRewriter;
+
private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
new IdentityHashMap<>();
@@ -380,10 +390,12 @@
private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
private final Map<DexMethod, ProgramMethod> methodsWithTwrCloseResource = new IdentityHashMap<>();
private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
+ private final ProgramMethodSet pendingDesugaring = ProgramMethodSet.create();
private final MainDexTracingResult previousMainDexTracingResult;
Enqueuer(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
SubtypingInfo subtypingInfo,
GraphConsumer keptGraphConsumer,
Mode mode,
@@ -392,6 +404,7 @@
InternalOptions options = appView.options();
this.appInfo = appView.appInfo();
this.appView = appView.withClassHierarchy();
+ this.executorService = executorService;
this.subtypingInfo = subtypingInfo;
this.forceProguardCompatibility = options.forceProguardCompatibility;
this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
@@ -418,6 +431,8 @@
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new LiveFieldsSet(graphReporter::registerField);
lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+ nestBasedAccessRewriter =
+ options.shouldDesugarNests() ? new NestBasedAccessDesugaring(appView) : null;
backportRewriter =
options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
twrCloseResourceRewriter =
@@ -1705,6 +1720,17 @@
recordTypeReference(innerClassAttribute.getInner(), clazz, this::ignoreMissingClass);
recordTypeReference(innerClassAttribute.getOuter(), clazz, this::ignoreMissingClass);
}
+
+ // Mark types in nest attributes referenced.
+ if (clazz.isNestHost()) {
+ for (NestMemberClassAttribute nestMemberClassAttribute :
+ clazz.getNestMembersClassAttributes()) {
+ recordTypeReference(nestMemberClassAttribute.getNestMember(), clazz);
+ }
+ } else {
+ recordTypeReference(clazz.getNestHost(), clazz);
+ }
+
EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
if (enclosingMethodAttribute != null) {
DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
@@ -3053,6 +3079,8 @@
private static class SyntheticAdditions {
+ List<ProgramMethod> desugaredMethods = new LinkedList<>();
+
Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
new IdentityHashMap<>();
@@ -3061,6 +3089,8 @@
Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
+ Map<DexType, DexProgramClass> syntheticProgramClasses = new IdentityHashMap<>();
+
Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
// Subset of live methods that need have keep requirements.
@@ -3072,8 +3102,10 @@
boolean isEmpty() {
boolean empty =
- syntheticInstantiations.isEmpty()
+ desugaredMethods.isEmpty()
+ && syntheticInstantiations.isEmpty()
&& liveMethods.isEmpty()
+ && syntheticProgramClasses.isEmpty()
&& syntheticClasspathClasses.isEmpty();
assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
return empty;
@@ -3089,6 +3121,11 @@
assert old == null;
}
+ void addProgramClass(DexProgramClass clazz) {
+ DexProgramClass old = syntheticProgramClasses.put(clazz.type, clazz);
+ assert old == null;
+ }
+
void addLiveMethod(ProgramMethod method) {
DexMethod signature = method.getDefinition().method;
assert !liveMethods.containsKey(signature);
@@ -3103,6 +3140,7 @@
void amendApplication(Builder appBuilder) {
assert !isEmpty();
+ appBuilder.addProgramClasses(syntheticProgramClasses.values());
appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
}
@@ -3114,9 +3152,14 @@
void enqueueWorkItems(Enqueuer enqueuer) {
assert !isEmpty();
assert enqueuer.mode.isInitialTreeShaking();
+
// All synthetic additions are initial tree shaking only. No need to track keep reasons.
KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
+ for (ProgramMethod desugaredMethod : desugaredMethods) {
+ enqueuer.workList.enqueueTraceCodeAction(desugaredMethod);
+ }
+
liveMethodsWithKeepActions.forEach(
item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond()));
for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
@@ -3160,7 +3203,7 @@
}
}
- private void synthesize() {
+ private void synthesize() throws ExecutionException {
if (!mode.isInitialTreeShaking()) {
return;
}
@@ -3168,11 +3211,13 @@
// In particular these additions are order independent, i.e., it does not matter which are
// registered first and no dependencies may exist among them.
SyntheticAdditions additions = new SyntheticAdditions();
+ desugar(additions);
synthesizeInvokeSpecialBridges(additions);
synthesizeInterfaceMethodBridges(additions);
synthesizeLambdas(additions);
synthesizeLibraryConversionWrappers(additions);
synthesizeBackports(additions);
+ synthesizeNestConstructor(additions);
synthesizeTwrCloseResource(additions);
if (additions.isEmpty()) {
return;
@@ -3195,6 +3240,50 @@
additions.enqueueWorkItems(this);
}
+ private void desugar(SyntheticAdditions additions) throws ExecutionException {
+ ThreadUtils.processItems(pendingDesugaring, this::desugar, executorService);
+ Iterables.addAll(additions.desugaredMethods, pendingDesugaring);
+ pendingDesugaring.clear();
+ }
+
+ private void desugar(ProgramMethod method) {
+ Code code = method.getDefinition().getCode();
+ if (!code.isCfCode()) {
+ appView
+ .options()
+ .reporter
+ .error(
+ new StringDiagnostic(
+ "Unsupported attempt to desugar non-CF code",
+ method.getOrigin(),
+ method.getPosition()));
+ return;
+ }
+
+ CfCode cfCode = code.asCfCode();
+ List<CfInstruction> desugaredInstructions =
+ ListUtils.flatMap(
+ cfCode.getInstructions(),
+ instruction -> {
+ // TODO(b/177810578): Migrate other cf-to-cf based desugaring here, and assert that
+ // that at most one instruction desugarer applies to each instruction.
+ if (nestBasedAccessRewriter != null) {
+ List<CfInstruction> replacement =
+ nestBasedAccessRewriter.desugarInstruction(instruction, method, null);
+ if (replacement != null) {
+ return replacement;
+ }
+ }
+ return null;
+ },
+ null);
+ if (desugaredInstructions != null) {
+ cfCode.setInstructions(desugaredInstructions);
+ } else {
+ assert false : "Expected code to be desugared";
+ }
+ }
+
private void synthesizeInvokeSpecialBridges(SyntheticAdditions additions) {
SortedProgramMethodSet bridges =
appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForR8();
@@ -3217,6 +3306,16 @@
}
}
+ private void synthesizeNestConstructor(SyntheticAdditions additions) {
+ if (nestBasedAccessRewriter != null) {
+ DexProgramClass nestConstructor = nestBasedAccessRewriter.synthesizeNestConstructor();
+ if (nestConstructor != null) {
+ // TODO(b/177638147): use getSyntheticItems().createClass().
+ additions.addProgramClass(nestConstructor);
+ }
+ }
+ }
+
private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
@@ -3874,7 +3973,8 @@
processAnnotations(holder, method);
definition.parameterAnnotationsList.forEachAnnotation(
annotation -> processAnnotation(holder, method, annotation));
- method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
+
+ traceNonDesugaredCode(method);
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(definition));
@@ -3885,6 +3985,21 @@
analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
}
+ private void traceNonDesugaredCode(ProgramMethod method) {
+ if (getMode().isInitialTreeShaking()) {
+ if (nestBasedAccessRewriter != null && nestBasedAccessRewriter.needsDesugaring(method)) {
+ pendingDesugaring.add(method);
+ return;
+ }
+ }
+
+ traceCode(method);
+ }
+
+ void traceCode(ProgramMethod method) {
+ method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
+ }
+
private void checkMemberForSoftPinning(ProgramMember<?, ?> member) {
DexMember<?, ?> reference = member.getDefinition().getReference();
Set<ProguardKeepRuleBase> softPinRules = rootSet.softPinned.getRulesForReference(reference);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 3c9101a..04fb840 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -11,24 +11,33 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.Enqueuer.Mode;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
public class EnqueuerFactory {
public static Enqueuer createForInitialTreeShaking(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
SubtypingInfo subtypingInfo) {
return new Enqueuer(
- appView, subtypingInfo, null, Mode.INITIAL_TREE_SHAKING, MainDexTracingResult.NONE);
+ appView,
+ executorService,
+ subtypingInfo,
+ null,
+ Mode.INITIAL_TREE_SHAKING,
+ MainDexTracingResult.NONE);
}
public static Enqueuer createForFinalTreeShaking(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
SubtypingInfo subtypingInfo,
GraphConsumer keptGraphConsumer,
Set<DexType> initialPrunedTypes) {
Enqueuer enqueuer =
new Enqueuer(
appView,
+ executorService,
subtypingInfo,
keptGraphConsumer,
Mode.FINAL_TREE_SHAKING,
@@ -40,18 +49,27 @@
}
public static Enqueuer createForInitialMainDexTracing(
- AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
+ SubtypingInfo subtypingInfo) {
return new Enqueuer(
- appView, subtypingInfo, null, Mode.INITIAL_MAIN_DEX_TRACING, MainDexTracingResult.NONE);
+ appView,
+ executorService,
+ subtypingInfo,
+ null,
+ Mode.INITIAL_MAIN_DEX_TRACING,
+ MainDexTracingResult.NONE);
}
public static Enqueuer createForFinalMainDexTracing(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
SubtypingInfo subtypingInfo,
GraphConsumer keptGraphConsumer,
MainDexTracingResult previousMainDexTracingResult) {
return new Enqueuer(
appView,
+ executorService,
subtypingInfo,
keptGraphConsumer,
Mode.FINAL_MAIN_DEX_TRACING,
@@ -60,10 +78,12 @@
public static Enqueuer createForWhyAreYouKeeping(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
SubtypingInfo subtypingInfo,
GraphConsumer keptGraphConsumer) {
return new Enqueuer(
appView,
+ executorService,
subtypingInfo,
keptGraphConsumer,
Mode.WHY_ARE_YOU_KEEPING,
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 0be900c..b403b54 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -174,6 +174,19 @@
}
}
+ static class TraceCodeAction extends EnqueuerAction {
+ private final ProgramMethod method;
+
+ TraceCodeAction(ProgramMethod method) {
+ this.method = method;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.traceCode(method);
+ }
+ }
+
static class TraceConstClassAction extends EnqueuerAction {
private final DexType type;
// TODO(b/175854431): Avoid pushing context on worklist.
@@ -304,6 +317,10 @@
queue.add(new MarkFieldKeptAction(field, witness));
}
+ public void enqueueTraceCodeAction(ProgramMethod method) {
+ queue.add(new TraceCodeAction(method));
+ }
+
public void enqueueTraceConstClassAction(DexType type, ProgramMethod context) {
queue.add(new TraceConstClassAction(type, context));
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 11dd893..9c03710 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -300,7 +300,13 @@
Consumer<SyntheticMethodBuilder> fn,
MethodProcessingId methodProcessingId) {
return createMethod(
- kind, context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
+ kind,
+ context,
+ factory,
+ fn,
+ methodProcessingId != null
+ ? methodProcessingId::getFullyQualifiedIdAndIncrement
+ : this::getNextSyntheticId);
}
// TODO(b/172194101): Remove/private this once the uniqueness is a property of the context.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index c501eb4..93c3f64 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -28,6 +28,7 @@
STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true),
+ THROW_NSME("ThrowNSME", true),
TWR_CLOSE_RESOURCE("TwrCloseResource", true),
SERVICE_LOADER("ServiceLoad", true);
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
index f46ac1c..f6b790d 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.utils;
+import java.util.function.BooleanSupplier;
+
public class BooleanBox {
private boolean value;
@@ -14,10 +16,24 @@
set(initialValue);
}
+ public void computeIfNotSet(BooleanSupplier supplier) {
+ if (isFalse()) {
+ set(supplier.getAsBoolean());
+ }
+ }
+
public boolean get() {
return value;
}
+ public boolean isFalse() {
+ return !get();
+ }
+
+ public boolean isTrue() {
+ return get();
+ }
+
public void set() {
set(true);
}
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 2e2a75c..decafef 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import static com.google.common.base.Predicates.not;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
@@ -22,7 +21,6 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.ExperimentalClassFileVersionDiagnostic;
-import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.errors.InvalidDebugInfoException;
import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
@@ -32,10 +30,11 @@
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -48,6 +47,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodProcessingId;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.nest.Nest;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
@@ -73,13 +73,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import java.io.IOException;
import java.io.PrintStream;
-import java.lang.reflect.GenericSignatureFormatError;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -493,7 +487,7 @@
if (testing.enableForceNestBasedAccessDesugaringForTest) {
return true;
}
- return enableNestBasedAccessDesugaring && !canUseNestBasedAccess();
+ return !canUseNestBasedAccess();
}
public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
@@ -514,10 +508,6 @@
public boolean enableLambdaMerging = false;
// Flag to turn on/off desugaring in D8/R8.
public DesugarState desugarState = DesugarState.ON;
- // Flag to turn on/off desugaring of invoke-special to a virtual method on the current class.
- public boolean enableInvokeSpecialToVirtualMethodDesugaring = true;
- // Flag to turn on/off JDK11+ nest-access control
- public boolean enableNestBasedAccessDesugaring = true;
// Flag to turn on/off reduction of nest to improve class merging optimizations.
public boolean enableNestReduction = true;
// Defines interface method rewriter behavior.
@@ -681,19 +671,6 @@
return ImmutableSet.of();
}
- private static Set<String> getExtensiveFieldMinifierLoggingFilter() {
- String property =
- System.getProperty("com.android.tools.r8.extensiveFieldMinifierLoggingFilter");
- if (property != null) {
- ImmutableSet.Builder<String> builder = ImmutableSet.builder();
- for (String method : property.split(";")) {
- builder.add(method);
- }
- return builder.build();
- }
- return ImmutableSet.of();
- }
-
private static Set<String> getExtensiveInterfaceMethodMinifierLoggingFilter() {
String property =
System.getProperty("com.android.tools.r8.extensiveInterfaceMethodMinifierLoggingFilter");
@@ -707,40 +684,6 @@
return ImmutableSet.of();
}
- private static Set<String> getNullableReceiverInliningFilter() {
- String property = System.getProperty("com.android.tools.r8.nullableReceiverInliningFilter");
- if (property != null) {
- // The property is allowed to be either (1) a path to a file where each line is a method
- // signature, or (2) a semicolon separated list of method signatures.
- Path path = null;
- try {
- Path tmp = Paths.get(property);
- if (Files.exists(tmp)) {
- path = tmp;
- }
- } catch (InvalidPathException | NullPointerException e) {
- // Ignore, treat as a semicolon separated list of method signatures.
- }
- ImmutableSet.Builder<String> builder = ImmutableSet.builder();
- if (path != null) {
- try {
- Files.readAllLines(path).stream()
- .map(String::trim)
- .filter(not(String::isEmpty))
- .forEach(builder::add);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- } else {
- for (String method : property.split(";")) {
- builder.add(method);
- }
- }
- return builder.build();
- }
- return ImmutableSet.of();
- }
-
public static class InvalidParameterAnnotationInfo {
final DexMethod method;
@@ -846,20 +789,13 @@
private final Set<DexItem> invalidLibraryClasses = Sets.newConcurrentHashSet();
- public void errorMissingClassMissingNestHost(DexClass compiledClass) {
+ public RuntimeException errorMissingClassMissingNestHost(DexClass compiledClass) {
throw reporter.fatalError(messageErrorMissingNestHost(compiledClass));
}
- public void warningMissingClassMissingNestHost(DexClass compiledClass) {
- if (compiledClass.isLibraryClass()) {
- errorMissingClassMissingNestHost(compiledClass);
- }
- reporter.warning(new StringDiagnostic(messageWarningMissingNestHost(compiledClass)));
- }
-
public void nestDesugaringWarningMissingNestHost(DexClass compiledClass) {
if (compiledClass.isLibraryClass()) {
- errorMissingClassMissingNestHost(compiledClass);
+ throw errorMissingClassMissingNestHost(compiledClass);
}
reporter.warning(
new MissingNestHostNestDesugarDiagnostic(
@@ -868,38 +804,8 @@
messageWarningMissingNestHost(compiledClass)));
}
- public void errorMissingClassIncompleteNest(List<DexType> nest, AppView<?> appView) {
- throw reporter.fatalError(messageErrorIncompleteNest(nest, appView));
- }
-
- public void warningMissingClassIncompleteNest(List<DexType> nest, AppView<?> appView) {
- for (DexType type : nest) {
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null && clazz.isLibraryClass()) {
- errorMissingClassIncompleteNest(nest, appView);
- return;
- }
- }
- reporter.warning(new StringDiagnostic(messageWarningIncompleteNest(nest, appView)));
- }
-
- public void nestDesugaringWarningIncompleteNest(List<DexType> nest, AppView<?> appView) {
- DexClass availableClass = null;
- for (DexType type : nest) {
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null && clazz.isProgramClass()) {
- availableClass = clazz;
- } else if (clazz != null && clazz.isLibraryClass()) {
- errorMissingClassIncompleteNest(nest, appView);
- return;
- }
- }
- assert availableClass != null;
- reporter.warning(
- new IncompleteNestNestDesugarDiagnosic(
- availableClass.getOrigin(),
- Position.UNKNOWN,
- messageWarningIncompleteNest(nest, appView)));
+ public RuntimeException errorMissingClassIncompleteNest(Nest nest) {
+ throw reporter.fatalError(messageErrorIncompleteNest(nest));
}
private String messageErrorMissingNestHost(DexClass compiledClass) {
@@ -918,49 +824,43 @@
+ " is considered as not being part of any nest.";
}
- private String messageErrorIncompleteNest(List<DexType> nest, AppView<?> appView) {
- List<String> programClassesFromNest = new ArrayList<>();
- List<String> unavailableClasses = new ArrayList<>();
- List<String> classPathClasses = new ArrayList<>();
- List<String> libraryClasses = new ArrayList<>();
- for (DexType type : nest) {
- DexClass clazz = appView.definitionFor(appView.graphLens().lookupType(type));
- if (clazz == null) {
- unavailableClasses.add(type.getName());
- } else if (clazz.isLibraryClass()) {
- libraryClasses.add(type.getName());
- } else if (clazz.isProgramClass()) {
- programClassesFromNest.add(type.getName());
- } else {
- assert clazz.isClasspathClass();
- classPathClasses.add(type.getName());
- }
+ private String messageErrorIncompleteNest(Nest nest) {
+ List<DexProgramClass> programClassesFromNest = new ArrayList<>();
+ List<DexClasspathClass> classpathClassesFromNest = new ArrayList<>();
+ List<DexLibraryClass> libraryClassesFromNest = new ArrayList<>();
+ nest.getHostClass()
+ .accept(
+ programClassesFromNest::add,
+ classpathClassesFromNest::add,
+ libraryClassesFromNest::add);
+ for (DexClass memberClass : nest.getMembers()) {
+ memberClass.accept(
+ programClassesFromNest::add, classpathClassesFromNest::add, libraryClassesFromNest::add);
}
StringBuilder stringBuilder =
new StringBuilder("Compilation of classes ")
- .append(String.join(", ", programClassesFromNest))
+ .append(StringUtils.join(", ", programClassesFromNest, DexClass::getTypeName))
.append(" requires its nest mates ");
- if (!unavailableClasses.isEmpty()) {
- stringBuilder.append(String.join(", ", unavailableClasses)).append(" (unavailable) ");
+ if (nest.hasMissingMembers()) {
+ stringBuilder
+ .append(StringUtils.join(", ", nest.getMissingMembers(), DexType::getTypeName))
+ .append(" (unavailable) ");
}
- if (!libraryClasses.isEmpty()) {
- stringBuilder.append(String.join(", ", unavailableClasses)).append(" (on library path) ");
+ if (!libraryClassesFromNest.isEmpty()) {
+ stringBuilder
+ .append(StringUtils.join(", ", libraryClassesFromNest, DexClass::getTypeName))
+ .append(" (on library path) ");
}
stringBuilder.append("to be on program or class path.");
- if (!classPathClasses.isEmpty()) {
+ if (!classpathClassesFromNest.isEmpty()) {
stringBuilder
.append("(Classes ")
- .append(String.join(", ", classPathClasses))
+ .append(StringUtils.join(", ", classpathClassesFromNest, DexClass::getTypeName))
.append(" from the same nest are on class path).");
}
return stringBuilder.toString();
}
- private String messageWarningIncompleteNest(List<DexType> nest, AppView<?> appView) {
- return messageErrorIncompleteNest(nest, appView)
- + " Unavailable classes are considered as not being part of the nest.";
- }
-
public void warningMissingTypeForDesugar(
Origin origin, Position position, DexType missingType, DexMethod context) {
if (reportedMissingForDesugaring.add(missingType)) {
@@ -1035,31 +935,6 @@
}
}
- public void warningInvalidSignature(
- DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
- StringBuilder message = new StringBuilder("Invalid signature '");
- message.append(signature);
- message.append("' for ");
- if (item.isDexClass()) {
- message.append("class ");
- message.append((item.asDexClass()).getType().toSourceString());
- } else if (item.isDexEncodedField()) {
- message.append("field ");
- message.append(item.toSourceString());
- } else {
- assert item.isDexEncodedMethod();
- message.append("method ");
- message.append(item.toSourceString());
- }
- message.append(".");
- message.append(System.lineSeparator());
- message.append("Signature is ignored and will not be present in the output.");
- message.append(System.lineSeparator());
- message.append("Parser error: ");
- message.append(e.getMessage());
- reporter.warning(new StringDiagnostic(message.toString(), origin));
- }
-
private final Box<Boolean> reportedExperimentClassFileVersion = new Box<>(false);
public void warningExperimentalClassFileVersion(Origin origin) {
@@ -1431,6 +1306,7 @@
public boolean addCallEdgesForLibraryInvokes = false;
public boolean allowCheckDiscardedErrors = false;
+ public boolean allowDexInputForTesting = false;
public boolean allowInjectedAnnotationMethods = false;
public boolean allowTypeErrors =
!Version.isDevelopmentVersion()
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index cd1c0a0..914307f 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -59,10 +59,14 @@
return -1;
}
- public static <T> Iterable<T> filter(Iterable<T> iterable, Predicate<T> predicate) {
+ public static <T> Iterable<T> filter(Iterable<T> iterable, Predicate<? super T> predicate) {
return () -> IteratorUtils.filter(iterable.iterator(), predicate);
}
+ public static <T> boolean hasNext(Iterable<T> iterable) {
+ return iterable.iterator().hasNext();
+ }
+
public static <T> int size(Iterable<T> iterable) {
int result = 0;
for (T element : iterable) {
@@ -77,6 +81,10 @@
return result;
}
+ public static <S, T> Iterable<T> transform(Iterable<S> iterable, Function<S, T> fn) {
+ return Iterables.transform(iterable, fn::apply);
+ }
+
public static <T> boolean isEmpty(Iterable<T> iterable) {
return !iterable.iterator().hasNext();
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index fe6feab..e6b0085 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -14,6 +14,36 @@
public class ListUtils {
+ /**
+ * Maps each element in the list using on the given function. Returns the mapped list if any
+ * elements were rewritten, otherwise returns the original list.
+ *
+ * <p>If the given function {@param fn} returns null for an element {@code v}, this is interpreted
+ * as the singleton list containing {@code v} (i.e., no changes should be made to the given
+ * element).
+ */
+ public static <T> List<T> flatMap(List<T> list, Function<T, List<T>> fn, List<T> defaultValue) {
+ List<T> result = null;
+ for (int i = 0; i < list.size(); i++) {
+ T element = list.get(i);
+ List<T> replacement = fn.apply(element);
+ if (replacement == null) {
+ if (result != null) {
+ result.add(element);
+ }
+ } else {
+ if (result == null) {
+ result = new ArrayList<>(list.size() + replacement.size() - 1);
+ for (int j = 0; j < i; j++) {
+ result.add(list.get(j));
+ }
+ }
+ result.addAll(replacement);
+ }
+ }
+ return result != null ? result : defaultValue;
+ }
+
public static <T> T first(List<T> list) {
return list.get(0);
}
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index ac79586..abae424 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -45,6 +45,6 @@
public static String toString(Map<?, ?> map) {
return StringUtils.join(
- map.entrySet(), ",", BraceType.TUBORG, entry -> entry.getKey() + ":" + entry.getValue());
+ ",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index a90d2bc..8f398ac 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -108,8 +108,8 @@
return append(builder, collection, ", ", BraceType.PARENS);
}
- public static <T> StringBuilder append(StringBuilder builder, Collection<T> collection,
- String seperator, BraceType brace) {
+ public static <T> StringBuilder append(
+ StringBuilder builder, Iterable<T> collection, String seperator, BraceType brace) {
builder.append(brace.left());
boolean first = true;
for (T element : collection) {
@@ -128,18 +128,22 @@
return join(collection, separator, BraceType.NONE);
}
+ public static <T> String join(String separator, Iterable<T> iterable, Function<T, String> fn) {
+ return join(separator, iterable, fn, BraceType.NONE);
+ }
+
public static String join(String separator, String... strings) {
return join(Arrays.asList(strings), separator, BraceType.NONE);
}
public static <T> String join(Collection<T> collection, String separator, BraceType brace) {
- return join(collection, separator, brace, Object::toString);
+ return join(separator, collection, Object::toString, brace);
}
- public static <T> String join(Collection<T> collection, String separator, BraceType brace,
- Function<T, String> fn) {
+ public static <T> String join(
+ String separator, Iterable<T> iterable, Function<T, String> fn, BraceType brace) {
StringBuilder builder = new StringBuilder();
- append(builder, ListUtils.map(collection, fn), separator, brace);
+ append(builder, IterableUtils.transform(iterable, fn), separator, brace);
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 2a16dbf..a8fa794 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -8,6 +8,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -18,6 +19,11 @@
public static final int NOT_SPECIFIED = -1;
+ public static <T> Future<T> processAsynchronously(
+ Callable<T> callable, ExecutorService executorService) {
+ return executorService.submit(callable);
+ }
+
public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
Iterable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
throws ExecutionException {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 23ec0c9..8a004a9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -169,6 +169,7 @@
protected void configure(InternalOptions options) {
options.lineNumberOptimization = LineNumberOptimization.OFF;
+ options.testing.allowDexInputForTesting = input == Input.DX;
}
private boolean shouldCompileFail() {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bb69054..533cbe4 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -720,9 +720,9 @@
MainDexClasses.createEmptyMainDexClasses());
}
- protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(AndroidApp app)
- throws Exception {
- return computeAppViewWithSubtyping(
+ protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
+ AndroidApp app) throws Exception {
+ return computeAppViewWithClassHierachy(
app,
factory ->
buildConfigForRules(
@@ -730,7 +730,7 @@
Collections.singletonList(ProguardKeepRule.defaultKeepAllRule(unused -> {}))));
}
- private static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(
+ private static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
InternalOptions options = new InternalOptions(keepConfig.apply(dexItemFactory), new Reporter());
@@ -759,7 +759,7 @@
protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
- AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithSubtyping(app, keepConfig);
+ AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithClassHierachy(app, keepConfig);
// Run the tree shaker to compute an instance of AppInfoWithLiveness.
ExecutorService executor = Executors.newSingleThreadExecutor();
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
@@ -769,7 +769,7 @@
.run(executor);
appView.setRootSet(rootSet);
AppInfoWithLiveness appInfoWithLiveness =
- EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo)
+ EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo)
.traceApplication(rootSet, ProguardClassFilter.empty(), executor, Timing.empty());
// We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
// due to liveness.
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 72a22e6..4f8713f 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static org.hamcrest.CoreMatchers.not;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -64,6 +65,11 @@
return assertWarningsMatch(Collections.singletonList(matcher));
}
+ @SuppressWarnings("unchecked")
+ default TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers) {
+ return assertWarningsMatch(Arrays.asList(matchers));
+ }
+
TestDiagnosticMessages assertWarningsMatch(Collection<Matcher<Diagnostic>> matchers);
default TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic> matcher) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 3da831f..12b42cb 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -101,7 +101,11 @@
}
public T addDontWarn(Class<?> clazz) {
- return addKeepRules("-dontwarn " + clazz.getTypeName());
+ return addDontWarn(clazz.getTypeName());
+ }
+
+ public T addDontWarn(String className) {
+ return addKeepRules("-dontwarn " + className);
}
public T addKeepKotlinMetadata() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 5b87397..8a9fc12 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -24,7 +24,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.tracereferences.TraceReferences;
import com.android.tools.r8.utils.AndroidApiLevel;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 394f36c..7a1d422 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -287,14 +287,17 @@
D8TestRunResult result =
compileResult.run(
parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
- if (result.getStdOut().contains("java.lang.NoSuchMethodError")
- && Arrays.stream(missingDesugaredMethods())
- .anyMatch(method -> result.getStdOut().contains(method))) {
+ String stdout = result.getStdOut();
+ if (stdout.contains("java.lang.NoSuchMethodError")
+ && Arrays.stream(missingDesugaredMethods()).anyMatch(method -> stdout.contains(method))) {
// TODO(b/134732760): support Java 9 APIs.
- } else if (result.getStdOut().contains("in class Ljava/util/Random")
- && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
+ } else if (stdout.contains("java.lang.NoSuchMethodError")
+ && stdout.contains("org.openjdk.tests.java.util.stream.IterateTest.testIterate")) {
+ // TODO(b/134732760): support Java 9 APIs.
+ } else if (stdout.contains("in class Ljava/util/Random")
+ && stdout.contains("java.lang.NoSuchMethodError")) {
// TODO(b/134732760): Random Java 9 Apis, support or do not use them.
- } else if (result.getStdOut().contains("java.lang.AssertionError")) {
+ } else if (stdout.contains("java.lang.AssertionError")) {
// TODO(b/134732760): Investigate and fix these issues.
} else {
String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
index e4447b53..1fa2bdc 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
@@ -107,11 +107,7 @@
.addKeepAllAttributes()
.setMinApi(parameters.getApiLevel())
.addProgramFiles(classesOfNest(nestID))
- .addOptionsModification(
- options -> {
- options.enableNestBasedAccessDesugaring = true;
- options.enableNestReduction = false;
- })
+ .addOptionsModification(options -> options.enableNestReduction = false)
.compile()
.run(parameters.getRuntime(), getMainClass(nestID))
.assertSuccessWithOutput(getExpectedResult(nestID));
@@ -125,7 +121,6 @@
testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramFiles(classesOfNest(nestID))
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
.compile()
.run(parameters.getRuntime(), getMainClass(nestID))
.assertSuccessWithOutput(getExpectedResult(nestID));
@@ -136,10 +131,6 @@
throws CompilationFailedException {
return testForD8(getStaticTemp())
.addProgramFiles(JAR)
- .addOptionsModification(
- options -> {
- options.enableNestBasedAccessDesugaring = true;
- })
.setMinApi(minApi)
.compile();
}
@@ -150,11 +141,7 @@
.noTreeShaking()
.noMinification()
.addKeepAllAttributes()
- .addOptionsModification(
- options -> {
- options.enableNestBasedAccessDesugaring = true;
- options.enableNestReduction = false;
- })
+ .addOptionsModification(options -> options.enableNestReduction = false)
.addProgramFiles(JAR)
.setMinApi(minApi)
.compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
index 9d01c91..56b025c 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 7f2fede..e9cdbe0 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -4,12 +4,15 @@
package com.android.tools.r8.desugar.nestaccesscontrol;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.core.StringEndsWith.endsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -20,9 +23,12 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic;
+import com.android.tools.r8.shaking.MissingClassesDiagnostic;
import java.nio.file.Path;
import java.util.List;
import org.hamcrest.Matcher;
@@ -81,10 +87,7 @@
.map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
.collect(toList());
if (d8) {
- return testForD8()
- .setMinApi(parameters.getApiLevel())
- .addProgramFiles(matchingClasses)
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true);
+ return testForD8().setMinApi(parameters.getApiLevel()).addProgramFiles(matchingClasses);
} else {
return testForR8(parameters.getBackend())
.noTreeShaking()
@@ -92,10 +95,12 @@
.addKeepAllAttributes()
.setMinApi(parameters.getApiLevel())
.addProgramFiles(matchingClasses)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addDontWarn("java.lang.invoke.StringConcatFactory")
.addOptionsModification(
options -> {
- options.enableNestBasedAccessDesugaring = true;
options.ignoreMissingClasses = ignoreMissingClasses;
+ options.testing.enableExperimentalMissingClassesReporting = true;
})
.allowDiagnosticWarningMessages(allowDiagnosticWarningMessages);
}
@@ -108,7 +113,16 @@
compileOnlyClassesMatching(innerClassMatcher, false, false, false)
.compileWithExpectedDiagnostics(
diagnostics -> {
- diagnostics.assertErrorMessageThatMatches(containsString("requires its nest host"));
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+
+ MissingClassesDiagnostic diagnostic =
+ (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+ assertEquals(1, diagnostic.getMissingClasses().size());
+ assertEquals(
+ "nesthostexample.BasicNestHostWithInnerClassMethods",
+ diagnostic.getMissingClasses().iterator().next().getTypeName());
});
} catch (CompilationFailedException e) {
// Expected failure.
@@ -123,8 +137,16 @@
compileOnlyClassesMatching(innerClassMatcher, false, false, false)
.compileWithExpectedDiagnostics(
diagnostics -> {
- diagnostics.assertErrorMessageThatMatches(
- containsString("requires its nest mates"));
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+
+ MissingClassesDiagnostic diagnostic =
+ (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+ assertEquals(1, diagnostic.getMissingClasses().size());
+ assertEquals(
+ "nesthostexample.BasicNestHostWithInnerClassMethods$BasicNestedClass",
+ diagnostic.getMissingClasses().iterator().next().getTypeName());
});
} catch (Exception e) {
// Expected failure.
@@ -139,15 +161,31 @@
TestCompileResult<?, ?> compileResult =
compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
- if (desugarWarning) {
+ if (d8 && desugarWarning) {
assertTrue(
compileResult.getDiagnosticMessages().getWarnings().stream()
.anyMatch(warn -> warn instanceof MissingNestHostNestDesugarDiagnostic));
- } else if (!d8) {
+ }
+ if (!d8) {
// R8 should raise extra warning when cleaning the nest.
- assertTrue(
- compileResult.getDiagnosticMessages().getWarnings().stream()
- .anyMatch(warn -> warn.getDiagnosticMessage().contains("requires its nest host")));
+ compileResult.inspectDiagnosticMessages(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ if (parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ diagnostics.assertWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+ } else {
+ diagnostics.assertWarningsMatch(
+ diagnosticType(MissingClassesDiagnostic.class),
+ diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class));
+ }
+
+ MissingClassesDiagnostic diagnostic =
+ (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+ assertEquals(1, diagnostic.getMissingClasses().size());
+ assertEquals(
+ "nesthostexample.BasicNestHostWithInnerClassMethods",
+ diagnostic.getMissingClasses().iterator().next().getTypeName());
+ });
}
}
@@ -156,13 +194,32 @@
TestCompileResult<?, ?> compileResult =
compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
- if (desugarWarning) {
+ if (d8 && desugarWarning) {
assertTrue(
compileResult.getDiagnosticMessages().getWarnings().stream()
.anyMatch(warn -> warn instanceof IncompleteNestNestDesugarDiagnosic));
- } else if (!d8) {
+ }
+ if (!d8) {
// R8 should raise extra warning when cleaning the nest.
- compileResult.assertWarningMessageThatMatches(containsString("requires its nest mates"));
+ compileResult.inspectDiagnosticMessages(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ if (parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ diagnostics.assertWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+ } else {
+ diagnostics.assertWarningsMatch(
+ diagnosticType(MissingClassesDiagnostic.class),
+ diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class));
+ }
+
+ MissingClassesDiagnostic diagnostic =
+ (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+ assertNotNull(diagnostic);
+ assertEquals(1, diagnostic.getMissingClasses().size());
+ assertEquals(
+ "nesthostexample.BasicNestHostWithInnerClassMethods$BasicNestedClass",
+ diagnostic.getMissingClasses().iterator().next().getTypeName());
+ });
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
index bb59533..4029469 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
@@ -99,9 +99,7 @@
testForD8()
.addProgramFiles(inner.writeToZip(), host.writeToZip())
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
.compile()
- .inspect(inspector -> assertEquals(3, inspector.allClasses().size()))
.run(parameters.getRuntime(), getMainClass("constructors"))
.assertSuccessWithOutput(getExpectedResult("constructors"));
}
@@ -117,7 +115,6 @@
.setMinApi(parameters.getApiLevel())
.addProgramFiles(matchingClasses)
.addClasspathFiles(JAR)
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
.compile();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java
index f147d3b..d36953c 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java
@@ -6,7 +6,6 @@
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 org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,33 +27,19 @@
@Test
public void testReference() throws Exception {
- TestRunResult<?> result =
- testForRuntime(parameters)
- .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
- .run(parameters.getRuntime(), "Main");
- if (parameters.isDexRuntime()
- && parameters.getApiLevel().isLessThan(apiLevelWithStaticInterfaceMethodsSupport())) {
- // TODO(b/69835274): Desugaring should preserve exception.
- result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
- } else {
- result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
- }
+ testForRuntime(parameters)
+ .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
+ .run(parameters.getRuntime(), "Main")
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
}
@Test
public void testR8() throws Exception {
- TestRunResult<?> result =
- testForR8(parameters.getBackend())
- .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
- .addKeepMainRule("Main")
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), "Main");
- if (parameters.isDexRuntime()
- && parameters.getApiLevel().isLessThan(apiLevelWithStaticInterfaceMethodsSupport())) {
- // TODO(b/69835274): Desugaring should preserve exception.
- result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
- } else {
- result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
- }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
+ .addKeepMainRule("Main")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), "Main")
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index cb48232..4d8926c 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -75,7 +75,8 @@
ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
.run(executorService));
Timing timing = Timing.empty();
- Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
+ Enqueuer enqueuer =
+ EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
appView.setAppInfo(
enqueuer.traceApplication(
appView.rootSet(), ProguardClassFilter.empty(), executorService, timing));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index a2fe521..553a2f4 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -36,7 +36,6 @@
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -49,7 +48,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public FieldBitAccessInfoTest(TestParameters parameters) {
@@ -61,7 +60,7 @@
testForR8(parameters.getBackend())
.addProgramClasses(TestClass.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines(
@@ -93,7 +92,7 @@
clazz.forEachProgramMethod(
method -> {
IRCode code = method.buildIR(appView);
- fieldAccessAnalysis.recordFieldAccesses(code, feedback, new MethodProcessorMock());
+ fieldAccessAnalysis.recordFieldAccesses(code, feedback, new PrimaryMethodProcessorMock());
});
int bitsReadInBitField = feedback.bitsReadPerField.getInt(uniqueFieldByName(clazz, "bitField"));
@@ -116,7 +115,7 @@
}
}
- private AppView<AppInfoWithClassHierarchy> buildApp() throws IOException, ExecutionException {
+ private AppView<AppInfoWithClassHierarchy> buildApp() throws IOException {
DexItemFactory dexItemFactory = new DexItemFactory();
InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
options.programConsumer =
@@ -210,12 +209,7 @@
}
}
- static class MethodProcessorMock extends MethodProcessor {
-
- @Override
- public Phase getPhase() {
- return Phase.PRIMARY;
- }
+ static class PrimaryMethodProcessorMock extends MethodProcessor {
@Override
public boolean shouldApplyCodeRewritings(ProgramMethod method) {
@@ -223,6 +217,11 @@
}
@Override
+ public boolean isPrimaryMethodProcessor() {
+ return true;
+ }
+
+ @Override
public boolean isProcessedConcurrently(ProgramMethod method) {
return false;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
index aa385fd..895e6ab 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
@@ -25,8 +25,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- // TODO(b/172194277): Add support for synthetics when generating CF.
- return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public CastToUninstantiatedClassTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
index 8f7c579..9dd23f8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -17,4 +17,8 @@
throw new ClassCastException();
}
}
+
+ public static NoSuchMethodError throwNoSuchMethodError() {
+ throw new NoSuchMethodError();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 8d01a11..105008c 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -99,6 +99,17 @@
}
@Override
+ public void replaceCurrentInstructionWithThrow(
+ AppView<?> appView,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ Value exceptionValue,
+ Set<BasicBlock> blocksToRemove,
+ Set<Value> affectedValues) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCode code,
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 3f7568a..425f3b0 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -11,14 +11,15 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
+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.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
@@ -108,15 +109,15 @@
Class<?> declaredClass = symbolicReferenceIsDefiningType ? definingClass : A.class;
Class<?> callerClass = A.class;
- AppView<AppInfoWithLiveness> appView = getAppView();
- AppInfoWithLiveness appInfo = appView.appInfo();
+ AppView<AppInfoWithClassHierarchy> appView = getAppView();
+ AppInfoWithClassHierarchy appInfo = appView.appInfo();
- DexProgramClass definingClassDefinition = getDexProgramClass(definingClass, appInfo);
- DexProgramClass declaredClassDefinition = getDexProgramClass(declaredClass, appInfo);
- DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appInfo);
+ DexProgramClass definingClassDefinition = getDexProgramClass(definingClass, appView);
+ DexProgramClass declaredClassDefinition = getDexProgramClass(declaredClass, appView);
+ DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appView);
- DexMethod method = getTargetMethodSignature(declaredClass, appInfo);
- assertCallingClassCallsTarget(callerClass, appInfo, method);
+ DexMethod method = getTargetMethodSignature(declaredClass, appView.dexItemFactory());
+ assertCallingClassCallsTarget(callerClass, appView, method);
// Resolve the method from the point of the declared holder.
assertEquals(method.holder, declaredClassDefinition.type);
@@ -136,7 +137,7 @@
DexClassAndMethod targetSuper =
resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
if (inSameNest) {
- assertEquals(definingClassDefinition.type, targetSpecial.getHolderType());
+ assertEquals(definingClassDefinition.getType(), targetSpecial.getHolderType());
assertEquals(targetSpecial.getReference(), targetSuper.getReference());
} else {
assertNull(targetSpecial);
@@ -145,30 +146,30 @@
}
private void assertCallingClassCallsTarget(
- Class<?> callerClass, AppInfoWithLiveness appInfo, DexMethod target) {
- CodeInspector inspector = new CodeInspector(appInfo.app());
+ Class<?> callerClass, AppView<?> appView, DexMethod target) {
+ CodeInspector inspector = new CodeInspector(appView.appInfo().app());
MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
assertTrue(
foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
}
- private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
+ private DexMethod getTargetMethodSignature(
+ Class<?> declaredClass, DexItemFactory dexItemFactory) {
return buildMethod(
Reference.method(Reference.classFromClass(declaredClass), "bar", ImmutableList.of(), null),
- appInfo.dexItemFactory());
+ dexItemFactory);
}
- private DexProgramClass getDexProgramClass(Class<?> clazz, AppInfoWithLiveness appInfo) {
- return appInfo.definitionFor(buildType(clazz, appInfo.dexItemFactory())).asProgramClass();
+ private DexProgramClass getDexProgramClass(Class<?> clazz, AppView<?> appView) {
+ return appView.definitionFor(buildType(clazz, appView.dexItemFactory())).asProgramClass();
}
- private AppView<AppInfoWithLiveness> getAppView() throws Exception {
- return computeAppViewWithLiveness(
+ private AppView<AppInfoWithClassHierarchy> getAppView() throws Exception {
+ return computeAppViewWithClassHierachy(
buildClasses(getClasses())
.addClassProgramData(getTransformedClasses())
.addLibraryFile(TestBase.runtimeJar(parameters.getBackend()))
- .build(),
- Main.class);
+ .build());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index 5a3d01e..c6a2dbf 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -18,7 +19,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.OptionalBool;
@@ -82,16 +82,16 @@
Class<?> declaredClass = A.class;
Class<?> callerClass = B.class;
- AppView<AppInfoWithLiveness> appView = getAppView();
- AppInfoWithLiveness appInfo = appView.appInfo();
+ AppView<AppInfoWithClassHierarchy> appView = getAppView();
+ AppInfoWithClassHierarchy appInfo = appView.appInfo();
- DexClass definingClassDefinition = getDexProgramClass(definingClass, appInfo);
- DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appInfo);
- DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appInfo);
+ DexClass definingClassDefinition = getDexProgramClass(definingClass, appView);
+ DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appView);
+ DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appView);
- DexMethod method = getTargetMethodSignature(declaredClass, appInfo);
+ DexMethod method = getTargetMethodSignature(declaredClass, appView);
- assertCallingClassCallsTarget(callerClass, appInfo, method);
+ assertCallingClassCallsTarget(callerClass, appView, method);
// Resolve the method from the point of the declared holder.
assertEquals(method.holder, declaredClassDefinition.type);
@@ -121,29 +121,28 @@
}
private void assertCallingClassCallsTarget(
- Class<?> callerClass, AppInfoWithLiveness appInfo, DexMethod target) {
- CodeInspector inspector = new CodeInspector(appInfo.app());
+ Class<?> callerClass, AppView<?> appView, DexMethod target) {
+ CodeInspector inspector = new CodeInspector(appView.appInfo().app());
MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
assertTrue(
foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
}
- private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
+ private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppView<?> appView) {
return buildMethod(
Reference.method(Reference.classFromClass(declaredClass), "bar", ImmutableList.of(), null),
- appInfo.dexItemFactory());
+ appView.dexItemFactory());
}
- private DexProgramClass getDexProgramClass(Class<?> definingClass, AppInfoWithLiveness appInfo) {
- return appInfo
- .definitionFor(buildType(definingClass, appInfo.dexItemFactory()))
+ private DexProgramClass getDexProgramClass(Class<?> definingClass, AppView<?> appView) {
+ return appView
+ .definitionFor(buildType(definingClass, appView.dexItemFactory()))
.asProgramClass();
}
- private AppView<AppInfoWithLiveness> getAppView() throws Exception {
- return computeAppViewWithLiveness(
- buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
- Main.class);
+ private AppView<AppInfoWithClassHierarchy> getAppView() throws Exception {
+ return computeAppViewWithClassHierachy(
+ buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index b291f91..ce6f4d7 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -19,7 +20,6 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -107,16 +107,16 @@
Class<?> declaredClass = symbolicReferenceIsDefiningType ? definingClass : B.class;
Class<?> callerClass = C.class;
- AppView<AppInfoWithLiveness> appView = getAppView();
- AppInfoWithLiveness appInfo = appView.appInfo();
+ AppView<AppInfoWithClassHierarchy> appView = getAppView();
+ AppInfoWithClassHierarchy appInfo = appView.appInfo();
- DexClass definingClassDefinition = getDexProgramClass(definingClass, appInfo);
- DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appInfo);
- DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appInfo);
+ DexClass definingClassDefinition = getDexProgramClass(definingClass, appView);
+ DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appView);
+ DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appView);
- DexMethod method = getTargetMethodSignature(declaredClass, appInfo);
+ DexMethod method = getTargetMethodSignature(declaredClass, appView);
- assertCallingClassCallsTarget(callerClass, appInfo, method);
+ assertCallingClassCallsTarget(callerClass, appView, method);
// Resolve the method from the point of the declared holder.
assertEquals(method.holder, declaredClassDefinition.type);
@@ -153,27 +153,26 @@
}
private void assertCallingClassCallsTarget(
- Class<?> callerClass, AppInfoWithLiveness appInfo, DexMethod target) {
- CodeInspector inspector = new CodeInspector(appInfo.app());
+ Class<?> callerClass, AppView<?> appView, DexMethod target) {
+ CodeInspector inspector = new CodeInspector(appView.appInfo().app());
MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
assertTrue(
foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
}
- private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
+ private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppView<?> appView) {
return buildMethod(
Reference.method(Reference.classFromClass(declaredClass), "bar", ImmutableList.of(), null),
- appInfo.dexItemFactory());
+ appView.dexItemFactory());
}
- private DexProgramClass getDexProgramClass(Class<?> clazz, AppInfoWithLiveness appInfo) {
- return appInfo.definitionFor(buildType(clazz, appInfo.dexItemFactory())).asProgramClass();
+ private DexProgramClass getDexProgramClass(Class<?> clazz, AppView<?> appView) {
+ return appView.definitionFor(buildType(clazz, appView.dexItemFactory())).asProgramClass();
}
- private AppView<AppInfoWithLiveness> getAppView() throws Exception {
- return computeAppViewWithLiveness(
- buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
- Main.class);
+ private AppView<AppInfoWithClassHierarchy> getAppView() throws Exception {
+ return computeAppViewWithClassHierachy(
+ buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index 3566d3a..bc35d2b 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -232,7 +232,7 @@
// ----- Program -----
// B extends A { } <-- initial
AppView<AppInfoWithClassHierarchy> appView =
- computeAppViewWithSubtyping(
+ computeAppViewWithClassHierachy(
buildClasses(Collections.singletonList(B.class), Arrays.asList(A.class, I.class))
.build());
AppInfoWithClassHierarchy appInfo = appView.appInfo();
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 96c6fb3..3a58245 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -179,6 +179,9 @@
if (optionsConsumer != null) {
optionsConsumer.accept(options);
}
+ if (frontend == Frontend.DEX) {
+ options.testing.allowDexInputForTesting = true;
+ }
})
.allowStdoutMessages()
.applyIf(testBuilderConsumer != null, testBuilderConsumer);
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
index 5a3efa6..484526f 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
@@ -5,13 +5,11 @@
package com.android.tools.r8.shaking.clinit;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,15 +43,8 @@
}
private void inspect(CodeInspector inspector) {
- if (parameters.isCfRuntime()) {
- // Check that A.<clinit>() is removed.
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
- assertThat(aClassSubject.clinit(), isAbsent());
- } else {
- // Check that A is removed.
- assertThat(inspector.clazz(A.class), isAbsent());
- }
+ // Check that A is removed.
+ assertThat(inspector.clazz(A.class), isAbsent());
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 04e2c9f..dd1b4bc 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -215,7 +215,7 @@
testCheckCast(
TestMainWithCheckCast.class,
TestClassWithDefaultConstructor.class,
- forceProguardCompatibility || parameters.isCfRuntime());
+ forceProguardCompatibility);
testCheckCast(TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
}
diff --git a/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java b/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
index cac07ed..8279af3 100644
--- a/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
@@ -33,9 +33,9 @@
private void assertListEquals(List<String> xs, List<String> ys) {
assertEquals(
- StringUtils.join(xs, ", ", BraceType.SQUARE, s -> '"' + StringUtils.toASCIIString(s) + '"'),
- StringUtils.join(ys, ", ", BraceType.SQUARE, s -> '"' + StringUtils.toASCIIString(s) + '"')
- );
+ StringUtils.join(", ", xs, s -> '"' + StringUtils.toASCIIString(s) + '"', BraceType.SQUARE),
+ StringUtils.join(
+ ", ", ys, s -> '"' + StringUtils.toASCIIString(s) + '"', BraceType.SQUARE));
}
@Test