blob: e279600453633277dfbe0a48ac3fca2c8c29ff2f [file] [log] [blame]
// Copyright (c) 2022, 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.profile.art;
import com.android.tools.r8.TextInputStream;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.profile.art.diagnostic.HumanReadableArtProfileParserErrorDiagnostic;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.MethodReferenceUtils;
import com.android.tools.r8.utils.Reporter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.util.function.Consumer;
public class HumanReadableArtProfileParser {
private final Consumer<HumanReadableArtProfileParserErrorDiagnostic> diagnosticConsumer;
private final ArtProfileBuilder profileBuilder;
private final ArtProfileRulePredicate rulePredicate;
private final Reporter reporter;
HumanReadableArtProfileParser(
Consumer<HumanReadableArtProfileParserErrorDiagnostic> diagnosticConsumer,
ArtProfileBuilder profileBuilder,
ArtProfileRulePredicate rulePredicate,
Reporter reporter) {
this.diagnosticConsumer = diagnosticConsumer;
this.profileBuilder = profileBuilder;
this.rulePredicate = rulePredicate;
this.reporter = reporter;
}
public static Builder builder() {
return new Builder();
}
public void parse(TextInputStream textInputStream, Origin origin) {
try {
try (InputStreamReader inputStreamReader =
new InputStreamReader(
textInputStream.getInputStream(), textInputStream.getCharset());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
int lineNumber = 1;
while (bufferedReader.ready()) {
String line = bufferedReader.readLine();
String lineWithoutComment = removeCommentFromLine(line);
if (isWhitespace(lineWithoutComment)) {
// Skip.
} else if (!parseRule(lineWithoutComment)) {
parseError(line, lineNumber, origin);
}
lineNumber++;
}
}
if (reporter != null) {
reporter.failIfPendingErrors();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void parseError(String rule, int lineNumber, Origin origin) {
if (diagnosticConsumer != null) {
diagnosticConsumer.accept(
new HumanReadableArtProfileParserErrorDiagnostic(rule, lineNumber, origin));
}
}
private boolean isWhitespace(String line) {
for (int i = 0; i < line.length(); i++) {
if (!Character.isWhitespace(line.charAt(i))) {
return false;
}
}
return true;
}
public boolean parseRule(String rule) {
try {
ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
ArtProfileMethodRuleInfoImpl.builder();
rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setIsHot);
rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setIsStartup);
rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setIsPostStartup);
return parseClassOrMethodRule(rule, methodRuleInfoBuilder.build());
} catch (Throwable t) {
return false;
}
}
private static String parseFlag(String rule, char c, Action action) {
if (!rule.isEmpty() && rule.charAt(0) == c) {
action.execute();
return rule.substring(1);
}
return rule;
}
private boolean parseClassOrMethodRule(String rule, ArtProfileMethodRuleInfoImpl methodRuleInfo) {
int arrowStartIndex = rule.indexOf("->");
if (arrowStartIndex >= 0) {
return parseMethodRule(rule, methodRuleInfo, arrowStartIndex);
} else if (methodRuleInfo.isEmpty()) {
return parseClassRule(rule);
} else {
return false;
}
}
private boolean parseClassRule(String descriptor) {
descriptor = DescriptorUtils.toBaseDescriptor(descriptor);
if (!DescriptorUtils.isValidClassDescriptor(descriptor)) {
return false;
}
TypeReference typeReference = Reference.typeFromDescriptor(descriptor);
assert typeReference != null;
assert typeReference.isClass();
ClassReference classReference = typeReference.asClass();
if (rulePredicate.testClassRule(classReference, ArtProfileClassRuleInfoImpl.empty())) {
profileBuilder.addClassRule(
classRuleBuilder -> classRuleBuilder.setClassReference(classReference));
}
return true;
}
private boolean parseMethodRule(
String rule, ArtProfileMethodRuleInfoImpl methodRuleInfo, int arrowStartIndex) {
int inlineCacheStartIndex = rule.indexOf('+', arrowStartIndex + 2);
String descriptor = inlineCacheStartIndex > 0 ? rule.substring(0, inlineCacheStartIndex) : rule;
MethodReference methodReference =
MethodReferenceUtils.parseSmaliString(descriptor, arrowStartIndex);
if (methodReference == null) {
return false;
}
if (!DescriptorUtils.isValidClassDescriptor(methodReference.getHolderClass().getDescriptor())) {
return false;
}
for (TypeReference formalType : methodReference.getFormalTypes()) {
if (!DescriptorUtils.isValidDescriptor(formalType.getDescriptor())) {
return false;
}
}
if (methodReference.getReturnType() != null
&& !DescriptorUtils.isValidDescriptor(methodReference.getReturnType().getDescriptor())) {
return false;
}
if (rulePredicate.testMethodRule(methodReference, methodRuleInfo)) {
profileBuilder.addMethodRule(
methodRuleBuilder ->
methodRuleBuilder
.setMethodReference(methodReference)
.setMethodRuleInfo(
methodRuleInfoBuilder ->
methodRuleInfoBuilder
.setIsHot(methodRuleInfo.isHot())
.setIsStartup(methodRuleInfo.isStartup())
.setIsPostStartup(methodRuleInfo.isPostStartup())));
}
return true;
}
private static String removeCommentFromLine(String line) {
int commentStartIndex = line.indexOf('#');
if (commentStartIndex >= 0) {
return line.substring(0, commentStartIndex).stripTrailing();
}
return line;
}
public static class Builder implements HumanReadableArtProfileParserBuilder {
private Consumer<HumanReadableArtProfileParserErrorDiagnostic> diagnosticConsumer;
private ArtProfileBuilder profileBuilder;
private ArtProfileRulePredicate rulePredicate = new AlwaysTrueArtProfileRulePredicate();
private Reporter reporter;
public Builder setDiagnosticConsumer(
Consumer<HumanReadableArtProfileParserErrorDiagnostic> diagnosticConsumer) {
this.diagnosticConsumer = diagnosticConsumer;
return this;
}
public Builder setReporter(Reporter reporter) {
this.reporter = reporter;
return this;
}
@Override
public Builder setRulePredicate(ArtProfileRulePredicate rulePredicate) {
this.rulePredicate = rulePredicate;
return this;
}
public Builder setProfileBuilder(ArtProfileBuilder profileBuilder) {
this.profileBuilder = profileBuilder;
return this;
}
public HumanReadableArtProfileParser build() {
if (diagnosticConsumer == null && reporter != null) {
diagnosticConsumer = reporter::error;
}
return new HumanReadableArtProfileParser(
diagnosticConsumer, profileBuilder, rulePredicate, reporter);
}
}
}