blob: 17ac83aca0291643530bd1d3efdea0d308914d81 [file] [log] [blame]
// 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.naming.dexitembasedstring;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.IntFunction;
/**
* Specific subclass for Record field names. Record field names are specific DexString instances
* used at the Java class file level for Records which encode the record field names in a single
* String separated by semi-colon. The computation is able to minify and prune such names.
*
* <p>Example: record Person(String name, int age) {} The string is: name;age
*
* <p>The JVM normally generates the Record field names string such as it includes each field name
* in the string. However, bytecode manipulation tools may rewrite the field name and not the
* string. For this reason we have two subclasses. - MatchingRecordFieldNamesComputationInfo is used
* when the name in the string matches the field names, and the R8 compilation is able to minify and
* prune the string based on the fields. - MissMatchingRecordFieldNamesComputationInfo is used when
* the name in the string does not match the field names, and the R8 compilation is then only able
* to prune the fields while maintaining the non-matching field names.
*/
public abstract class RecordFieldNamesComputationInfo extends NameComputationInfo<DexType> {
final DexField[] fields;
protected RecordFieldNamesComputationInfo(DexField[] fields) {
this.fields = fields;
}
private static class MissMatchingRecordFieldNamesComputationInfo
extends RecordFieldNamesComputationInfo {
private final String[] fieldNames;
private MissMatchingRecordFieldNamesComputationInfo(String[] fieldNames, DexField[] fields) {
super(fields);
this.fieldNames = fieldNames;
}
@Override
public DexString internalComputeNameFor(
DexType type,
DexDefinitionSupplier definitions,
GraphLens graphLens,
NamingLens namingLens) {
return internalComputeNameFor(type, definitions, graphLens, i -> fieldNames[i]);
}
}
private static class MatchingRecordFieldNamesComputationInfo
extends RecordFieldNamesComputationInfo {
public MatchingRecordFieldNamesComputationInfo(DexField[] fields) {
super(fields);
}
@Override
public DexString internalComputeNameFor(
DexType type,
DexDefinitionSupplier definitions,
GraphLens graphLens,
NamingLens namingLens) {
return internalComputeNameFor(
type,
definitions,
graphLens,
i ->
namingLens
.lookupField(
graphLens.getRenamedFieldSignature(fields[i]), definitions.dexItemFactory())
.name
.toString());
}
}
static DexString dexStringFromFieldNames(List<String> fieldNames, DexItemFactory factory) {
return factory.createString(StringUtils.join(";", fieldNames));
}
public static RecordFieldNamesComputationInfo forFieldNamesAndFields(
DexString fieldNames, DexField[] fields) {
String fieldNamesString = fieldNames.toString();
String[] fieldNamesSplit =
fieldNamesString.isEmpty() ? new String[0] : fieldNamesString.split(";");
assert fieldNamesSplit.length == fields.length;
if (fieldsMatchNames(fieldNamesSplit, fields)) {
return new MatchingRecordFieldNamesComputationInfo(fields);
}
return new MissMatchingRecordFieldNamesComputationInfo(fieldNamesSplit, fields);
}
private static boolean fieldsMatchNames(String[] fieldNames, DexField[] fields) {
for (int i = 0; i < fieldNames.length; i++) {
if (!(fields[i].name.toString().equals(fieldNames[i]))) {
return false;
}
}
return true;
}
public DexString internalComputeNameFor(
DexType type,
DexDefinitionSupplier definitions,
GraphLens graphLens,
IntFunction<String> nameSupplier) {
assert Arrays.stream(fields).allMatch(f -> f.holder == type);
DexClass recordClass = definitions.contextIndependentDefinitionFor(type);
assert recordClass != null;
List<String> names = new ArrayList<>(fields.length);
for (int i = 0; i < fields.length; i++) {
DexEncodedField recordField =
recordClass.lookupInstanceField(graphLens.getRenamedFieldSignature(fields[i]));
if (recordField != null) {
names.add(nameSupplier.apply(i));
} else {
// TODO(b/201277582): Temporarily work-around as long as the field values are not also
// shrunk in dex.
names.add("<pruned>");
}
}
return dexStringFromFieldNames(names, definitions.dexItemFactory());
}
@Override
DexString internalComputeNameFor(
DexType reference, DexDefinitionSupplier definitions, NamingLens namingLens) {
throw new Unreachable();
}
public abstract DexString internalComputeNameFor(
DexType type, DexDefinitionSupplier definitions, GraphLens graphLens, NamingLens namingLens);
@Override
public boolean needsToComputeName() {
return true;
}
@Override
public boolean needsToRegisterReference() {
return false;
}
@Override
public boolean isRecordFieldNamesComputationInfo() {
return true;
}
@Override
public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
return this;
}
}