blob: 48f21991c41dbe34eb1f4a01ce7e3301d03d1802 [file] [log] [blame]
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize.string;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.optimize.CodeRewriter.removeOrReplaceByDebugLocalWrite;
import static com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping.CANONICAL_NAME;
import static com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping.NAME;
import static com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping.SIMPLE_NAME;
import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis;
import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
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.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.io.UTFDataFormatException;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
public class StringOptimizer {
private final AppView<?> appView;
private final DexItemFactory factory;
private int numberOfSimplifiedOperations = 0;
private final Object2IntMap<ClassNameMapping> numberOfComputedNames;
private final Object2IntMap<ClassNameMapping> numberOfDeferredComputationOfNames;
private final Object2IntMap<Integer> histogramOfLengthOfNames;
private final Object2IntMap<Integer> histogramOfLengthOfDeferredNames;
private int numberOfSimplifiedConversions = 0;
public StringOptimizer(AppView<?> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
if (Log.ENABLED && Log.isLoggingEnabledFor(StringOptimizer.class)) {
numberOfComputedNames = new Object2IntArrayMap<>();
numberOfDeferredComputationOfNames = new Object2IntArrayMap<>();
histogramOfLengthOfNames = new Object2IntArrayMap<>();
histogramOfLengthOfDeferredNames = new Object2IntArrayMap<>();
} else {
numberOfComputedNames = null;
numberOfDeferredComputationOfNames = null;
histogramOfLengthOfNames = null;
histogramOfLengthOfDeferredNames = null;
}
}
public void logResult() {
assert Log.ENABLED;
Log.info(getClass(),
"# trivial operations on const-string: %s", numberOfSimplifiedOperations);
Log.info(getClass(),
"# trivial conversions from/to const-string: %s", numberOfSimplifiedConversions);
if (numberOfComputedNames != null) {
Log.info(getClass(), "------ # of get*Name() computation ------");
numberOfComputedNames.forEach((kind, count) -> {
Log.info(getClass(),
"%s: %s (%s)", kind, StringUtils.times("*", Math.min(count, 53)), count);
});
}
if (numberOfDeferredComputationOfNames != null) {
Log.info(getClass(), "------ # of deferred get*Name() computation ------");
numberOfDeferredComputationOfNames.forEach((kind, count) -> {
Log.info(getClass(),
"%s: %s (%s)", kind, StringUtils.times("*", Math.min(count, 53)), count);
});
}
if (histogramOfLengthOfNames != null) {
Log.info(getClass(), "------ histogram of get*Name() result lengths ------");
histogramOfLengthOfNames.forEach((length, count) -> {
Log.info(getClass(),
"%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
});
}
if (histogramOfLengthOfDeferredNames != null) {
Log.info(getClass(),
"------ histogram of original type length for deferred get*Name() ------");
histogramOfLengthOfDeferredNames.forEach((length, count) -> {
Log.info(getClass(),
"%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
});
}
}
// boolean String#isEmpty()
// boolean String#startsWith(String)
// boolean String#endsWith(String)
// boolean String#contains(String)
// boolean String#equals(String)
// boolean String#equalsIgnoreCase(String)
// boolean String#contentEquals(String)
// int String#hashCode()
// int String#length()
// int String#indexOf(String)
// int String#indexOf(int)
// int String#lastIndexOf(String)
// int String#lastIndexOf(int)
// int String#compareTo(String)
// int String#compareToIgnoreCase(String)
// String String#substring(int)
// String String#substring(int, int)
// String String#trim()
public void computeTrivialOperationsOnConstString(IRCode code) {
if (!code.metadata().mayHaveConstString()) {
return;
}
Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator it = code.instructionListIterator();
while (it.hasNext()) {
Instruction instr = it.next();
if (!instr.isInvokeVirtual()) {
continue;
}
InvokeVirtual invoke = instr.asInvokeVirtual();
if (!invoke.hasOutValue()) {
continue;
}
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod.name == factory.substringName) {
assert invoke.inValues().size() == 2 || invoke.inValues().size() == 3;
Value rcv = invoke.getReceiver().getAliasedValue();
if (rcv.definition == null
|| !rcv.definition.isConstString()
|| rcv.hasLocalInfo()) {
continue;
}
Value beginIndex = invoke.inValues().get(1).getAliasedValue();
if (beginIndex.definition == null
|| !beginIndex.definition.isConstNumber()
|| beginIndex.hasLocalInfo()) {
continue;
}
int beginIndexValue = beginIndex.definition.asConstNumber().getIntValue();
Value endIndex = null;
if (invoke.inValues().size() == 3) {
endIndex = invoke.inValues().get(2).getAliasedValue();
if (endIndex.definition == null
|| !endIndex.definition.isConstNumber()
|| endIndex.hasLocalInfo()) {
continue;
}
}
String rcvString = rcv.definition.asConstString().getValue().toString();
int endIndexValue =
endIndex == null
? rcvString.length()
: endIndex.definition.asConstNumber().getIntValue();
if (beginIndexValue < 0
|| endIndexValue > rcvString.length()
|| beginIndexValue > endIndexValue) {
// This will raise StringIndexOutOfBoundsException.
continue;
}
String sub = rcvString.substring(beginIndexValue, endIndexValue);
Value stringValue =
code.createValue(
TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
affectedValues.addAll(invoke.outValue().affectedValues());
it.replaceCurrentInstruction(new ConstString(stringValue, factory.createString(sub)));
numberOfSimplifiedOperations++;
continue;
}
if (invokedMethod == factory.stringMembers.trim) {
Value receiver = invoke.getReceiver().getAliasedValue();
if (receiver.hasLocalInfo() || receiver.isPhi() || !receiver.definition.isConstString()) {
continue;
}
DexString resultString =
factory.createString(receiver.definition.asConstString().getValue().toString().trim());
Value newOutValue =
code.createValue(
TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
affectedValues.addAll(invoke.outValue().affectedValues());
it.replaceCurrentInstruction(new ConstString(newOutValue, resultString));
numberOfSimplifiedOperations++;
continue;
}
Function<DexString, Integer> operatorWithNoArg = null;
BiFunction<DexString, DexString, Integer> operatorWithString = null;
BiFunction<DexString, Integer, Integer> operatorWithInt = null;
if (invokedMethod == factory.stringMembers.hashCode) {
operatorWithNoArg = rcv -> {
try {
return rcv.decodedHashCode();
} catch (UTFDataFormatException e) {
// It is already guaranteed that the string does not throw.
throw new Unreachable();
}
};
} else if (invokedMethod == factory.stringMembers.length) {
operatorWithNoArg = rcv -> rcv.size;
} else if (invokedMethod == factory.stringMembers.isEmpty) {
operatorWithNoArg = rcv -> rcv.size == 0 ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.contains) {
operatorWithString = (rcv, arg) -> rcv.toString().contains(arg.toString()) ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.startsWith) {
operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.endsWith) {
operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.equals) {
operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.equalsIgnoreCase) {
operatorWithString = (rcv, arg) -> rcv.toString().equalsIgnoreCase(arg.toString()) ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.contentEqualsCharSequence) {
operatorWithString = (rcv, arg) -> rcv.toString().contentEquals(arg.toString()) ? 1 : 0;
} else if (invokedMethod == factory.stringMembers.indexOfInt) {
operatorWithInt = (rcv, idx) -> rcv.toString().indexOf(idx);
} else if (invokedMethod == factory.stringMembers.indexOfString) {
operatorWithString = (rcv, arg) -> rcv.toString().indexOf(arg.toString());
} else if (invokedMethod == factory.stringMembers.lastIndexOfInt) {
operatorWithInt = (rcv, idx) -> rcv.toString().lastIndexOf(idx);
} else if (invokedMethod == factory.stringMembers.lastIndexOfString) {
operatorWithString = (rcv, arg) -> rcv.toString().lastIndexOf(arg.toString());
} else if (invokedMethod == factory.stringMembers.compareTo) {
operatorWithString = (rcv, arg) -> rcv.toString().compareTo(arg.toString());
} else if (invokedMethod == factory.stringMembers.compareToIgnoreCase) {
operatorWithString = (rcv, arg) -> rcv.toString().compareToIgnoreCase(arg.toString());
} else {
continue;
}
Value rcv = invoke.getReceiver().getAliasedValue();
if (rcv.definition == null
|| !rcv.definition.isConstString()
|| rcv.definition.asConstString().instructionInstanceCanThrow()
|| rcv.hasLocalInfo()) {
continue;
}
DexString rcvString = rcv.definition.asConstString().getValue();
ConstNumber constNumber;
if (operatorWithNoArg != null) {
assert invoke.inValues().size() == 1;
int v = operatorWithNoArg.apply(rcvString);
constNumber = code.createIntConstant(v);
} else if (operatorWithString != null) {
assert invoke.inValues().size() == 2;
Value arg = invoke.inValues().get(1).getAliasedValue();
if (arg.definition == null
|| !arg.definition.isConstString()
|| arg.hasLocalInfo()) {
continue;
}
int v = operatorWithString.apply(rcvString, arg.definition.asConstString().getValue());
constNumber = code.createIntConstant(v);
} else {
assert operatorWithInt != null;
assert invoke.inValues().size() == 2;
Value arg = invoke.inValues().get(1).getAliasedValue();
if (arg.definition == null
|| !arg.definition.isConstNumber()
|| arg.hasLocalInfo()) {
continue;
}
int v = operatorWithInt.apply(rcvString, arg.definition.asConstNumber().getIntValue());
constNumber = code.createIntConstant(v);
}
numberOfSimplifiedOperations++;
it.replaceCurrentInstruction(constNumber);
}
// Computed substring is not null, and thus propagate that information.
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
// Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
public void rewriteClassGetName(AppView<?> appView, IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator it = code.instructionListIterator();
while (it.hasNext()) {
Instruction instr = it.next();
if (!instr.isInvokeVirtual()) {
continue;
}
InvokeVirtual invoke = instr.asInvokeVirtual();
DexMethod invokedMethod = invoke.getInvokedMethod();
if (!factory.classMethods.isReflectiveNameLookup(invokedMethod)) {
continue;
}
Value out = invoke.outValue();
// Skip the call if the computed name is already discarded or not used anywhere.
if (out == null || !out.hasAnyUsers()) {
continue;
}
assert invoke.inValues().size() == 1;
// In case of handling multiple invocations over the same const-string, all the following
// usages after the initial one will point to non-null IR (a.k.a. alias), e.g.,
//
// rcv <- invoke-virtual instance, ...#getClass() // Can be rewritten to const-class
// x <- invoke-virtual rcv, Class#getName()
// non_null_rcv <- non-null rcv
// y <- invoke-virtual non_null_rcv, Class#getCanonicalName()
// z <- invoke-virtual non_null_rcv, Class#getSimpleName()
// ... // or some other usages of the same usage.
//
// In that case, we should check if the original source is (possibly rewritten) const-class.
Value in = invoke.getReceiver().getAliasedValue();
if (in.definition == null
|| !in.definition.isConstClass()
|| in.hasLocalInfo()) {
continue;
}
ConstClass constClass = in.definition.asConstClass();
DexType type = constClass.getValue();
int arrayDepth = type.getNumberOfLeadingSquareBrackets();
DexType baseType = type.toBaseType(factory);
// Make sure base type is a class type.
if (!baseType.isClassType()) {
continue;
}
DexClass holder = appView.definitionFor(baseType);
if (holder == null) {
continue;
}
boolean mayBeRenamed =
appView.enableWholeProgramOptimizations()
&& appView.withLiveness().appInfo().isMinificationAllowed(holder.type);
// b/120138731: Filter out escaping uses. In such case, the result of this optimization will
// be stored somewhere, which can lead to a regression if the corresponding class is in a deep
// package hierarchy. For local cases, it is likely a one-time computation, but make sure the
// result is used reasonably, such as library calls. For example, if a class may be minified
// while its name is used to compute hash code, which won't be optimized, it's better not to
// compute the name.
if (!appView.options().testing.forceNameReflectionOptimization) {
if (mayBeRenamed) {
continue;
}
if (invokedMethod != factory.classMethods.getSimpleName) {
EscapeAnalysis escapeAnalysis =
new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
if (escapeAnalysis.isEscaping(code, out)) {
continue;
}
}
}
String descriptor = baseType.toDescriptorString();
boolean assumeTopLevel = descriptor.indexOf(INNER_CLASS_SEPARATOR) < 0;
DexItemBasedConstString deferred = null;
DexString name = null;
if (invokedMethod == factory.classMethods.getName) {
if (mayBeRenamed) {
deferred =
new DexItemBasedConstString(
invoke.outValue(), baseType, ClassNameComputationInfo.create(NAME, arrayDepth));
logDeferredNameComputation(NAME);
} else {
name = NAME.map(descriptor, holder, factory, arrayDepth);
logNameComputation(NAME);
}
} else if (invokedMethod == factory.classMethods.getTypeName) {
// TODO(b/119426668): desugar Type#getTypeName
continue;
} else if (invokedMethod == factory.classMethods.getCanonicalName) {
// Always returns null if the target type is local or anonymous class.
if (holder.isLocalClass() || holder.isAnonymousClass()) {
affectedValues.addAll(invoke.outValue().affectedValues());
ConstNumber constNull = code.createConstNull();
it.replaceCurrentInstruction(constNull);
} else {
// b/119471127: If an outer class is shrunk, we may compute a wrong canonical name.
// Leave it as-is so that the class's canonical name is consistent across the app.
if (!assumeTopLevel) {
continue;
}
if (mayBeRenamed) {
deferred =
new DexItemBasedConstString(
invoke.outValue(),
baseType,
ClassNameComputationInfo.create(CANONICAL_NAME, arrayDepth));
logDeferredNameComputation(CANONICAL_NAME);
} else {
name = CANONICAL_NAME.map(descriptor, holder, factory, arrayDepth);
logNameComputation(CANONICAL_NAME);
}
}
} else if (invokedMethod == factory.classMethods.getSimpleName) {
// Always returns an empty string if the target type is an anonymous class.
if (holder.isAnonymousClass()) {
name = factory.createString("");
} else {
// b/120130435: If an outer class is shrunk, we may compute a wrong simple name.
// Leave it as-is so that the class's simple name is consistent across the app.
if (!assumeTopLevel) {
continue;
}
if (mayBeRenamed) {
deferred =
new DexItemBasedConstString(
invoke.outValue(),
baseType,
ClassNameComputationInfo.create(SIMPLE_NAME, arrayDepth));
logDeferredNameComputation(SIMPLE_NAME);
} else {
name = SIMPLE_NAME.map(descriptor, holder, factory, arrayDepth);
logNameComputation(SIMPLE_NAME);
}
}
}
if (name != null) {
affectedValues.addAll(invoke.outValue().affectedValues());
Value stringValue =
code.createValue(
TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
ConstString constString = new ConstString(stringValue, name);
it.replaceCurrentInstruction(constString);
logHistogramOfNames(name);
} else if (deferred != null) {
affectedValues.addAll(invoke.outValue().affectedValues());
it.replaceCurrentInstruction(deferred);
logHistogramOfNames(deferred);
}
}
// Computed name is not null or literally null (for canonical name of local/anonymous class).
// In either way, that is narrower information, and thus propagate that.
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
private void logNameComputation(ClassNameMapping kind) {
if (Log.ENABLED && Log.isLoggingEnabledFor(StringOptimizer.class)) {
assert numberOfComputedNames != null;
synchronized (numberOfComputedNames) {
int count = numberOfComputedNames.getOrDefault(kind, 0);
numberOfComputedNames.put(kind, count + 1);
}
}
}
private void logHistogramOfNames(DexString name) {
if (Log.ENABLED && Log.isLoggingEnabledFor(StringOptimizer.class)) {
Integer length = name.size;
assert histogramOfLengthOfNames != null;
synchronized (histogramOfLengthOfNames) {
int count = histogramOfLengthOfNames.getOrDefault(length, 0);
histogramOfLengthOfNames.put(length, count + 1);
}
}
}
private void logDeferredNameComputation(ClassNameMapping kind) {
if (Log.ENABLED && Log.isLoggingEnabledFor(StringOptimizer.class)) {
assert numberOfDeferredComputationOfNames != null;
synchronized (numberOfDeferredComputationOfNames) {
int count = numberOfDeferredComputationOfNames.getOrDefault(kind, 0);
numberOfDeferredComputationOfNames.put(kind, count + 1);
}
}
}
private void logHistogramOfNames(DexItemBasedConstString deferred) {
if (Log.ENABLED && Log.isLoggingEnabledFor(StringOptimizer.class)) {
assert deferred.getItem().isDexType();
DexType original = deferred.getItem().asDexType();
Integer length = original.descriptor.size;
assert histogramOfLengthOfDeferredNames != null;
synchronized (histogramOfLengthOfDeferredNames) {
int count = histogramOfLengthOfDeferredNames.getOrDefault(length, 0);
histogramOfLengthOfDeferredNames.put(length, count + 1);
}
}
}
// String#valueOf(null) -> "null"
// String#valueOf(String s) -> s
// str.toString() -> str
public void removeTrivialConversions(IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator it = code.instructionListIterator();
while (it.hasNext()) {
Instruction instr = it.next();
if (instr.isInvokeStatic()) {
InvokeStatic invoke = instr.asInvokeStatic();
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod != factory.stringMembers.valueOf) {
continue;
}
assert invoke.inValues().size() == 1;
Value in = invoke.inValues().get(0);
if (in.hasLocalInfo()) {
continue;
}
Value out = invoke.outValue();
TypeElement inType = in.getType();
if (out != null && in.isAlwaysNull(appView)) {
affectedValues.addAll(out.affectedValues());
Value nullStringValue =
code.createValue(
TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
ConstString nullString = new ConstString(nullStringValue, factory.createString("null"));
it.replaceCurrentInstruction(nullString);
numberOfSimplifiedConversions++;
} else if (inType.nullability().isDefinitelyNotNull()
&& inType.isClassType()
&& inType.asClassType().getClassType().equals(factory.stringType)) {
if (out != null) {
affectedValues.addAll(out.affectedValues());
removeOrReplaceByDebugLocalWrite(invoke, it, in, out);
} else {
it.removeOrReplaceByDebugLocalRead();
}
numberOfSimplifiedConversions++;
}
} else if (instr.isInvokeVirtual()) {
InvokeVirtual invoke = instr.asInvokeVirtual();
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod != factory.stringMembers.toString) {
continue;
}
assert invoke.inValues().size() == 1;
Value in = invoke.getReceiver();
TypeElement inType = in.getType();
if (inType.nullability().isDefinitelyNotNull()
&& inType.isClassType()
&& inType.asClassType().getClassType().equals(factory.stringType)) {
Value out = invoke.outValue();
if (out != null) {
affectedValues.addAll(out.affectedValues());
removeOrReplaceByDebugLocalWrite(invoke, it, in, out);
} else {
it.removeOrReplaceByDebugLocalRead();
}
numberOfSimplifiedConversions++;
}
}
}
// Newly added "null" string is not null, and thus propagate that information.
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
static class StringOptimizerEscapeAnalysisConfiguration
implements EscapeAnalysisConfiguration {
private static final StringOptimizerEscapeAnalysisConfiguration INSTANCE =
new StringOptimizerEscapeAnalysisConfiguration();
private StringOptimizerEscapeAnalysisConfiguration() {}
public static StringOptimizerEscapeAnalysisConfiguration getInstance() {
return INSTANCE;
}
@Override
public boolean isLegitimateEscapeRoute(
AppView<?> appView,
EscapeAnalysis escapeAnalysis,
Instruction escapeRoute,
ProgramMethod context) {
if (escapeRoute.isReturn() || escapeRoute.isThrow() || escapeRoute.isStaticPut()) {
return false;
}
if (escapeRoute.isInvokeMethod()) {
DexMethod invokedMethod = escapeRoute.asInvokeMethod().getInvokedMethod();
// b/120138731: Only allow known simple operations on const-string
if (invokedMethod == appView.dexItemFactory().stringMembers.hashCode
|| invokedMethod == appView.dexItemFactory().stringMembers.isEmpty
|| invokedMethod == appView.dexItemFactory().stringMembers.length) {
return true;
}
// Add more cases to filter out, if any.
return false;
}
if (escapeRoute.isArrayPut()) {
Value array = escapeRoute.asArrayPut().array().getAliasedValue();
return !array.isPhi() && array.definition.isCreatingArray();
}
if (escapeRoute.isInstancePut()) {
Value instance = escapeRoute.asInstancePut().object().getAliasedValue();
return !instance.isPhi() && instance.definition.isNewInstance();
}
// All other cases are not legitimate.
return false;
}
}
}