Merge "Extend context sensitive GraphLense API"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2e0b531..484bfe8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -331,6 +331,14 @@
GraphLense graphLense = GraphLense.getIdentityLense();
if (appInfo.hasLiveness()) {
+ if (options.proguardConfiguration.hasApplyMappingFile()) {
+ SeedMapper seedMapper =
+ SeedMapper.seedMapperFromFile(options.proguardConfiguration.getApplyMappingFile());
+ timing.begin("apply-mapping");
+ graphLense =
+ new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper).run(timing);
+ timing.end();
+ }
graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
// Class merging requires inlining.
if (options.enableClassMerging && options.enableInlining) {
@@ -343,14 +351,6 @@
appInfo = appInfo.withLiveness()
.prunedCopyFrom(application, classMerger.getRemovedClasses());
}
- if (options.proguardConfiguration.hasApplyMappingFile()) {
- SeedMapper seedMapper = SeedMapper.seedMapperFromFile(
- options.proguardConfiguration.getApplyMappingFile());
- timing.begin("apply-mapping");
- graphLense = new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper)
- .run(timing);
- timing.end();
- }
application = application.asDirect().rewrittenWithLense(graphLense);
appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
// Collect switch maps and ordinals maps.
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 06ea28d..cd3c093 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
@@ -159,6 +160,26 @@
public boolean isInvokeConstructor() {
return this == MethodHandleType.INVOKE_CONSTRUCTOR;
}
+
+ public Type toInvokeType() {
+ assert isMethodType();
+ switch (this) {
+ case INVOKE_STATIC:
+ return Type.STATIC;
+ case INVOKE_INSTANCE:
+ return Type.VIRTUAL;
+ case INVOKE_CONSTRUCTOR:
+ return Type.DIRECT;
+ case INVOKE_DIRECT:
+ return Type.DIRECT;
+ case INVOKE_INTERFACE:
+ return Type.INTERFACE;
+ case INVOKE_SUPER:
+ return Type.SUPER;
+ default:
+ throw new Unreachable("DexMethodHandle with unexpected type: " + this);
+ }
+ }
}
public MethodHandleType type;
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 bd22444..e4f1c8c 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -69,7 +69,6 @@
}
public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
- assert graphLense.isContextFreeForMethods();
assert mappingIsValid(graphLense, programClasses.getAllTypes());
assert mappingIsValid(graphLense, libraryClasses.keySet());
// As a side effect, this will rebuild the program classes and library classes maps.
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 7c6c941..096de5a 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -3,8 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Set;
/**
* A GraphLense implements a virtual view on top of the graph, used to delay global rewrites until
@@ -61,17 +65,32 @@
public abstract DexType lookupType(DexType type);
+ // This overload can be used when the graph lense is known to be context insensitive.
public DexMethod lookupMethod(DexMethod method) {
- assert isContextFreeForMethods();
- return lookupMethod(method, null);
+ assert isContextFreeForMethod(method);
+ return lookupMethod(method, null, null);
}
- public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context);
+ public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type);
+
+ // Context sensitive graph lenses should override this method.
+ public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+ assert isContextFreeForMethod(method);
+ DexMethod result = lookupMethod(method);
+ if (result != null) {
+ return ImmutableSet.of(result);
+ }
+ return ImmutableSet.of();
+ }
public abstract DexField lookupField(DexField field);
public abstract boolean isContextFreeForMethods();
+ public boolean isContextFreeForMethod(DexMethod method) {
+ return isContextFreeForMethods();
+ }
+
public static GraphLense getIdentityLense() {
return new IdentityGraphLense();
}
@@ -88,7 +107,7 @@
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
+ public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
return method;
}
@@ -146,12 +165,21 @@
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
- DexMethod previous = previousLense.lookupMethod(method, context);
+ public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+ DexMethod previous = previousLense.lookupMethod(method, context, type);
return methodMap.getOrDefault(previous, previous);
}
@Override
+ public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+ Set<DexMethod> result = new HashSet<>();
+ for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
+ result.add(methodMap.getOrDefault(previous, previous));
+ }
+ return result;
+ }
+
+ @Override
public DexField lookupField(DexField field) {
DexField previous = previousLense.lookupField(field);
return fieldMap.getOrDefault(previous, previous);
@@ -163,6 +191,11 @@
}
@Override
+ public boolean isContextFreeForMethod(DexMethod method) {
+ return previousLense.isContextFreeForMethod(method);
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 8330e06..21b8dc4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -363,7 +363,7 @@
private void processInvoke(Type type, DexMethod method) {
DexEncodedMethod source = caller.method;
- method = graphLense.lookupMethod(method, source);
+ method = graphLense.lookupMethod(method, source, type);
DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
if (definition != null) {
assert !source.accessFlags.isBridge() || definition != caller.method;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 28213e1..1926b14 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -96,7 +96,7 @@
if (!invokedHolder.isClassType()) {
continue;
}
- DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method);
+ DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method, invoke.getType());
Invoke.Type invokeType = getInvokeType(invoke, actualTarget, invokedMethod);
if (actualTarget != invokedMethod || invoke.getType() != invokeType) {
Invoke newInvoke = Invoke.create(invokeType, actualTarget, null,
@@ -216,7 +216,8 @@
DexEncodedMethod method, DexMethodHandle methodHandle) {
if (methodHandle.isMethodHandle()) {
DexMethod invokedMethod = methodHandle.asMethod();
- DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method);
+ DexMethod actualTarget =
+ graphLense.lookupMethod(invokedMethod, method, methodHandle.type.toInvokeType());
if (actualTarget != invokedMethod) {
DexClass clazz = appInfo.definitionFor(actualTarget.holder);
MethodHandleType newType = methodHandle.type;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 45200d4..7dfd840 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -37,6 +37,7 @@
AppInfoWithLiveness appInfo,
GraphLense previousLense,
SeedMapper seedMapper) {
+ assert previousLense.isContextFreeForMethods();
this.appInfo = appInfo;
this.previousLense = previousLense;
this.seedMapper = seedMapper;
@@ -44,7 +45,7 @@
public GraphLense run(Timing timing) {
timing.begin("from-pg-map-to-lense");
- GraphLense lenseFromMap = new MapToLenseConverter().run(previousLense);
+ GraphLense lenseFromMap = new MapToLenseConverter().run();
timing.end();
timing.begin("fix-types-in-programs");
GraphLense typeFixedLense = new TreeFixer(lenseFromMap).run();
@@ -61,7 +62,7 @@
lenseBuilder = new ConflictFreeBuilder();
}
- private GraphLense run(GraphLense previousLense) {
+ private GraphLense run() {
// To handle inherited yet undefined methods in library classes, we are traversing types in
// a subtyping order. That also helps us detect conflicted mappings in a diamond case:
// LibItfA#foo -> a, LibItfB#foo -> b, while PrgA implements LibItfA and LibItfB.
@@ -308,7 +309,7 @@
}
for (int i = 0; i < methods.length; i++) {
DexEncodedMethod encodedMethod = methods[i];
- DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method, encodedMethod);
+ DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method);
DexType newHolderType = substituteType(appliedMethod.holder, encodedMethod);
DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod);
DexMethod newMethod;
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index a920455d..56089f4 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -43,16 +44,17 @@
InvokeKind kind = targetExtractor.getKind();
if (target != null && target.getArity() == method.method.getArity()) {
assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
- target = lense.lookupMethod(target, method);
if (kind == InvokeKind.STATIC) {
assert method.accessFlags.isStatic();
- DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(target);
+ DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC);
+ DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(actualTarget);
if (targetMethod != null) {
addForwarding(method, targetMethod);
}
} else if (kind == InvokeKind.VIRTUAL) {
// TODO(herhut): Add support for bridges with multiple targets.
- DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(target);
+ DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL);
+ DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
if (targetMethod != null) {
addForwarding(method, targetMethod);
}
@@ -91,17 +93,15 @@
}
@Override
- public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
- DexMethod previous = previousLense.lookupMethod(method, context);
+ public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+ DexMethod previous = previousLense.lookupMethod(method, context, type);
DexMethod bridge = bridgeTargetToBridgeMap.get(previous);
// Do not forward calls from a bridge method to itself while the bridge method is still
// a bridge.
- if (bridge == null
- || (context.accessFlags.isBridge() && bridge == context.method)) {
+ if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
return previous;
- } else {
- return bridge;
}
+ return bridge;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 43b45b4..fc408e3 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -115,7 +115,7 @@
private void computeMethodRebinding(Set<DexMethod> methods,
Function<DexMethod, DexEncodedMethod> lookupTarget) {
for (DexMethod method : methods) {
- method = lense.lookupMethod(method, null);
+ method = lense.lookupMethod(method);
// We can safely ignore array types, as the corresponding methods are defined in a library.
if (!method.getHolder().isClassType()) {
continue;
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 9387e3e..2b51720 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1638,8 +1638,8 @@
this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
- this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
- this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
+ this.targetedMethods = rewriteMethodsConservatively(previous.targetedMethods, lense);
+ this.liveMethods = rewriteMethodsConservatively(previous.liveMethods, lense);
this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
this.instanceFieldReads =
rewriteKeysWhileMergingValues(previous.instanceFieldReads, lense::lookupField);
@@ -1652,11 +1652,11 @@
this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
this.pinnedItems = rewriteMixedItems(previous.pinnedItems, lense);
- this.virtualInvokes = rewriteItems(previous.virtualInvokes, lense::lookupMethod);
- this.interfaceInvokes = rewriteItems(previous.interfaceInvokes, lense::lookupMethod);
- this.superInvokes = rewriteItems(previous.superInvokes, lense::lookupMethod);
- this.directInvokes = rewriteItems(previous.directInvokes, lense::lookupMethod);
- this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
+ this.virtualInvokes = rewriteMethodsConservatively(previous.virtualInvokes, lense);
+ this.interfaceInvokes = rewriteMethodsConservatively(previous.interfaceInvokes, lense);
+ this.superInvokes = rewriteMethodsConservatively(previous.superInvokes, lense);
+ this.directInvokes = rewriteMethodsConservatively(previous.directInvokes, lense);
+ this.staticInvokes = rewriteMethodsConservatively(previous.staticInvokes, lense);
this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
// TODO(herhut): Migrate these to Descriptors, as well.
assert assertNotModifiedByLense(previous.noSideEffects.keySet(), lense);
@@ -1744,7 +1744,7 @@
// We only allow changes to bridge methods, as these get retargeted even if they
// are kept.
assert method.accessFlags.isBridge()
- || lense.lookupMethod(method.method, null) == method.method;
+ || lense.lookupMethod(method.method) == method.method;
} else if (item instanceof DexEncodedField) {
DexField field = ((DexEncodedField) item).field;
assert lense.lookupField(field) == field;
@@ -1792,6 +1792,29 @@
return builder.build();
}
+ private static ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(
+ Set<DexMethod> original, GraphLense lense) {
+ ImmutableSortedSet.Builder<DexMethod> builder =
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
+ if (lense.isContextFreeForMethods()) {
+ for (DexMethod item : original) {
+ builder.add(lense.lookupMethod(item));
+ }
+ } else {
+ for (DexMethod item : original) {
+ // Avoid using lookupMethodInAllContexts when possible.
+ if (lense.isContextFreeForMethod(item)) {
+ builder.add(lense.lookupMethod(item));
+ } else {
+ // The lense is context sensitive, but we do not have the context here. Therefore, we
+ // conservatively look up the method in all contexts.
+ builder.addAll(lense.lookupMethodInAllContexts(item));
+ }
+ }
+ }
+ return builder.build();
+ }
+
private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
Set<T> original, Function<T, T> rewrite) {
ImmutableSortedSet.Builder<T> builder =
@@ -1892,7 +1915,6 @@
public AppInfoWithLiveness rewrittenWithLense(DirectMappedDexApplication application,
GraphLense lense) {
- assert lense.isContextFreeForMethods();
return new AppInfoWithLiveness(this, application, lense);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index a80e758..c9d34ee 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -181,7 +181,8 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
}
- return renamedMembersLense.build(application.dexItemFactory, graphLense);
+ return new VerticalClassMergerGraphLense(
+ renamedMembersLense.build(application.dexItemFactory, graphLense));
}
private class ClassMerger {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
new file mode 100644
index 0000000..1bdeb7c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+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.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+// This graph lense is instantiated during vertical class merging. The graph lense is context
+// sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke-
+// super vs invoke-virtual). This is illustrated by the following example.
+//
+// public class A {
+// public void m() { ... }
+// }
+// public class B extends A {
+// @Override
+// public void m() { invoke-super A.m(); ... }
+//
+// public void m2() { invoke-virtual A.m(); ... }
+// }
+//
+// Vertical class merging will merge class A into class B. Since class B already has a method with
+// the signature "void B.m()", the method A.m will be given a fresh name and moved to class B.
+// During this process, the method corresponding to A.m will be made private such that it can be
+// called via an invoke-direct instruction.
+//
+// For the invocation "invoke-super A.m()" in B.m, this graph lense will return the newly created,
+// private method corresponding to A.m (that is now in B.m with a fresh name), such that the
+// invocation will hit the same implementation as the original super.m() call.
+//
+// For the invocation "invoke-virtual A.m()" in B.m2, this graph lense will return the method B.m.
+public class VerticalClassMergerGraphLense extends GraphLense {
+ private final GraphLense previousLense;
+
+ public VerticalClassMergerGraphLense(GraphLense previousLense) {
+ this.previousLense = previousLense;
+ }
+
+ @Override
+ public DexType lookupType(DexType type) {
+ return previousLense.lookupType(type);
+ }
+
+ @Override
+ public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+ // TODO(christofferqa): If [type] is Type.SUPER and [method] has been merged into the class of
+ // [context], then return the DIRECT method that has been created for [method] by SimpleClass-
+ // Merger. Otherwise, return the VIRTUAL method corresponding to [method].
+ return previousLense.lookupMethod(method, context, type);
+ }
+
+ @Override
+ public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+ DexMethod result = lookupMethod(method);
+ if (result != null) {
+ return ImmutableSet.of(result);
+ }
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public DexField lookupField(DexField field) {
+ return previousLense.lookupField(field);
+ }
+
+ @Override
+ public boolean isContextFreeForMethods() {
+ return false;
+ }
+
+ @Override
+ public boolean isContextFreeForMethod(DexMethod method) {
+ // TODO(christofferqa): Should return false for methods where this graph lense is context
+ // sensitive.
+ return true;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index cf356df..65181dc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -36,6 +36,9 @@
import org.junit.Ignore;
import org.junit.Test;
+// TODO(christofferqa): Add tests to check that statically typed invocations on method handles
+// continue to work after class merging. Rewriting of method handles should be carried out by
+// LensCodeRewriter.rewriteDexMethodHandle.
public class ClassMergingTest extends TestBase {
private static final Path CF_DIR =