blob: 9fd0c740f319f2533f7f0c7df136aa370f72588a [file] [log] [blame]
// 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.code;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
import com.android.tools.r8.utils.structural.Equatable;
import com.android.tools.r8.utils.structural.HashCodeVisitor;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import java.util.function.Predicate;
public abstract class Position implements StructuralItem<Position> {
// Compare ID(s) for positions.
private static final int SOURCE_POSITION_COMPARE_ID = 1;
private static final int SYNTHETIC_POSITION_COMPARE_ID = 2;
private static final int OUTLINE_POSITION_COMPARE_ID = 3;
private static final int OUTLINE_CALLER_POSITION_COMPARE_ID = 4;
protected final int line;
protected final DexMethod method;
// If there's no inlining, callerPosition is null.
//
// For an inlined instruction its Position contains the inlinee's line and method and
// callerPosition is the position of the invoke instruction in the caller.
protected final Position callerPosition;
private final boolean removeInnerFramesIfThrowingNpe;
private Position(
int line, DexMethod method, Position callerPosition, boolean removeInnerFramesIfThrowingNpe) {
this.line = line;
this.method = method;
this.callerPosition = callerPosition;
this.removeInnerFramesIfThrowingNpe = removeInnerFramesIfThrowingNpe;
}
public boolean isSyntheticPosition() {
return false;
}
public boolean isAdditionalMappingInfoPosition() {
return false;
}
public boolean isRemoveInnerFramesIfThrowingNpe() {
return removeInnerFramesIfThrowingNpe;
}
public boolean isOutline() {
return false;
}
public DexMethod getOutlineCallee() {
return null;
}
public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
return null;
}
public boolean hasCallerPosition() {
return callerPosition != null;
}
public Position getCallerPosition() {
return callerPosition;
}
public int getLine() {
return line;
}
public DexMethod getMethod() {
return method;
}
public static Position none() {
return SourcePosition.NO_POSITION;
}
public boolean hasFile() {
return false;
}
public DexString getFile() {
return null;
}
@Override
public Position self() {
return this;
}
// Unique id to determine the ordering of positions
public abstract int getCompareToId();
@Override
public abstract StructuralMapping<Position> getStructuralMapping();
private static void specifyBasePosition(StructuralSpecification<Position, ?> spec) {
spec.withInt(Position::getCompareToId)
.withInt(Position::getLine)
.withNullableItem(Position::getMethod)
.withNullableItem(Position::getCallerPosition)
.withBool(Position::isRemoveInnerFramesIfThrowingNpe);
}
public static Position syntheticNone() {
return SyntheticPosition.NO_POSITION_SYNTHETIC;
}
public static Position getPositionForInlining(
AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
Position position = invoke.getPosition();
if (position.method == null) {
assert position.isNone();
position = SourcePosition.builder().setMethod(context.getReference()).build();
}
assert position.getOutermostCaller().method
== appView.graphLens().getOriginalMethodSignature(context.getReference());
return position;
}
public boolean isNone() {
return line == -1;
}
public boolean isSyntheticNone() {
return this == syntheticNone();
}
public boolean isSome() {
return !isNone();
}
// Follow the linked list of callerPositions and return the last.
// Return this if no inliner.
public Position getOutermostCaller() {
Position lastPosition = this;
while (lastPosition.callerPosition != null) {
lastPosition = lastPosition.callerPosition;
}
return lastPosition;
}
public Position getOutermostCallerMatchingOrElse(
Predicate<Position> predicate, Position defaultValue) {
return getOutermostCallerMatchingOrElse(predicate, defaultValue, false);
}
private Position getOutermostCallerMatchingOrElse(
Predicate<Position> predicate, Position defaultValue, boolean isCallerPosition) {
if (hasCallerPosition()) {
Position position =
getCallerPosition().getOutermostCallerMatchingOrElse(predicate, defaultValue, true);
if (position != null) {
return position;
}
}
if (isCallerPosition && predicate.test(this)) {
return this;
}
return defaultValue;
}
public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
return builderWithCopy()
.setCallerPosition(
hasCallerPosition()
? getCallerPosition().withOutermostCallerPosition(newOutermostCallerPosition)
: newOutermostCallerPosition)
.build();
}
@Override
public final boolean equals(Object other) {
return Equatable.equalsImpl(this, other);
}
@Override
public final int hashCode() {
return HashCodeVisitor.run(this);
}
private String toString(boolean forceMethod) {
if (isNone()) {
return "--";
}
StringBuilder builder = new StringBuilder();
if (hasFile()) {
builder.append(getFile()).append(":");
}
builder.append("#").append(line);
if (method != null && (forceMethod || callerPosition != null)) {
builder.append(":").append(method.name);
}
if (callerPosition != null) {
Position caller = callerPosition;
while (caller != null) {
builder.append(";").append(caller.line).append(":").append(caller.method.name);
caller = caller.callerPosition;
}
}
return builder.toString();
}
@Override
public String toString() {
return toString(false);
}
public abstract PositionBuilder<?, ?> builderWithCopy();
public abstract static class PositionBuilder<
P extends Position, B extends PositionBuilder<P, B>> {
protected int line = -1;
protected DexMethod method;
protected Position callerPosition;
protected boolean removeInnerFramesIfThrowingNpe;
protected boolean noCheckOfPosition;
protected boolean noCheckOfMethod;
abstract B self();
public B setLine(int line) {
this.line = line;
return self();
}
public boolean hasLine() {
return line > -1;
}
public B setMethod(DexMethod method) {
this.method = method;
return self();
}
public B setCallerPosition(Position callerPosition) {
this.callerPosition = callerPosition;
return self();
}
public B setRemoveInnerFramesIfThrowingNpe(boolean removeInnerFramesIfThrowingNpe) {
this.removeInnerFramesIfThrowingNpe = removeInnerFramesIfThrowingNpe;
return self();
}
public B disableLineCheck() {
noCheckOfPosition = true;
return self();
}
public B disableMethodCheck() {
noCheckOfMethod = true;
return self();
}
public abstract P build();
}
public static class SourcePosition extends Position {
// A no-position marker. Not having a position means the position is implicitly defined by the
// context, e.g., the marker does not materialize anything concrete.
private static final SourcePosition NO_POSITION =
new SourcePosition(-1, null, null, false, null);
public final DexString file;
private static void specify(StructuralSpecification<Position, ?> spec) {
spec.withSpec(Position::specifyBasePosition).withNullableItem(Position::getFile);
}
private SourcePosition(
int line,
DexMethod method,
Position callerPosition,
boolean removeInnerFramesIfThrowingNpe,
DexString file) {
super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
this.file = file;
assert callerPosition == null || callerPosition.method != null;
}
@Override
public boolean hasFile() {
return file != null;
}
@Override
public DexString getFile() {
return file;
}
@Override
public int getCompareToId() {
return SOURCE_POSITION_COMPARE_ID;
}
@Override
public PositionBuilder<?, ?> builderWithCopy() {
return builder()
.setLine(line)
.setFile(file)
.setMethod(method)
.setCallerPosition(callerPosition);
}
@Override
public StructuralMapping<Position> getStructuralMapping() {
return SourcePosition::specify;
}
public static SourcePositionBuilder builder() {
return new SourcePositionBuilder();
}
public static class SourcePositionBuilder
extends PositionBuilder<SourcePosition, SourcePositionBuilder> {
private DexString file;
@Override
SourcePositionBuilder self() {
return this;
}
public SourcePositionBuilder setFile(DexString file) {
this.file = file;
return this;
}
@Override
public SourcePosition build() {
assert noCheckOfPosition || line >= 0;
assert noCheckOfMethod || method != null;
return new SourcePosition(
line, method, callerPosition, removeInnerFramesIfThrowingNpe, file);
}
}
}
public static class SyntheticPosition extends Position {
// A synthetic marker position that should never materialize.
// This is used specifically to mark exceptional exit blocks from synchronized methods in
// release.
private static final Position NO_POSITION_SYNTHETIC =
new SyntheticPosition(-1, null, null, false);
private SyntheticPosition(
int line,
DexMethod method,
Position callerPosition,
boolean removeInnerFramesIfThrowingNpe) {
super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
}
@Override
public boolean isSyntheticPosition() {
return true;
}
@Override
public int getCompareToId() {
return SYNTHETIC_POSITION_COMPARE_ID;
}
@Override
public PositionBuilder<?, ?> builderWithCopy() {
return builder().setLine(line).setMethod(method).setCallerPosition(callerPosition);
}
@Override
public StructuralMapping<Position> getStructuralMapping() {
return Position::specifyBasePosition;
}
public static SyntheticPositionBuilder builder() {
return new SyntheticPositionBuilder();
}
public static class SyntheticPositionBuilder
extends PositionBuilder<SyntheticPosition, SyntheticPositionBuilder> {
private SyntheticPositionBuilder() {}
@Override
SyntheticPositionBuilder self() {
return this;
}
@Override
public SyntheticPosition build() {
assert noCheckOfPosition || line >= 0;
assert noCheckOfMethod || method != null;
return new SyntheticPosition(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
}
}
}
public static class OutlinePosition extends Position {
private OutlinePosition(
int line,
DexMethod method,
Position callerPosition,
boolean removeInnerFramesIfThrowingNpe) {
super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
}
@Override
public boolean isOutline() {
return true;
}
@Override
public int getCompareToId() {
return OUTLINE_POSITION_COMPARE_ID;
}
@Override
public PositionBuilder<?, ?> builderWithCopy() {
return builder().setLine(line).setMethod(method).setCallerPosition(callerPosition);
}
@Override
public StructuralMapping<Position> getStructuralMapping() {
return Position::specifyBasePosition;
}
public static OutlinePositionBuilder builder() {
return new OutlinePositionBuilder();
}
public static class OutlinePositionBuilder
extends PositionBuilder<OutlinePosition, OutlinePositionBuilder> {
private OutlinePositionBuilder() {}
@Override
OutlinePositionBuilder self() {
return this;
}
@Override
public OutlinePosition build() {
return new OutlinePosition(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
}
}
}
public static class OutlineCallerPosition extends Position {
private final Int2StructuralItemArrayMap<Position> outlinePositions;
private final DexMethod outlineCallee;
private final boolean isOutline;
public static void specify(StructuralSpecification<Position, ?> spec) {
spec.withSpec(Position::specifyBasePosition)
.withBool(Position::isOutline)
.withItem(Position::getOutlineCallee)
.withItem(Position::getOutlinePositions);
}
private OutlineCallerPosition(
int line,
DexMethod method,
Position callerPosition,
boolean removeInnerFramesIfThrowingNpe,
Int2StructuralItemArrayMap<Position> outlinePositions,
DexMethod outlineCallee,
boolean isOutline) {
super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
this.outlinePositions = outlinePositions;
this.outlineCallee = outlineCallee;
this.isOutline = isOutline;
}
@Override
public boolean isNone() {
return false;
}
@Override
public int getCompareToId() {
return OUTLINE_CALLER_POSITION_COMPARE_ID;
}
@Override
public PositionBuilder<?, ?> builderWithCopy() {
OutlineCallerPositionBuilder outlineCallerPositionBuilder =
builder()
.setLine(line)
.setMethod(method)
.setCallerPosition(callerPosition)
.setOutlineCallee(outlineCallee)
.setIsOutline(isOutline);
outlinePositions.forEach(outlineCallerPositionBuilder::addOutlinePosition);
return outlineCallerPositionBuilder;
}
@Override
public boolean isOutline() {
return isOutline;
}
@Override
public DexMethod getOutlineCallee() {
return outlineCallee;
}
@Override
public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
return outlinePositions;
}
@Override
public StructuralMapping<Position> getStructuralMapping() {
return OutlineCallerPosition::specify;
}
public static OutlineCallerPositionBuilder builder() {
return new OutlineCallerPositionBuilder();
}
public static class OutlineCallerPositionBuilder
extends PositionBuilder<OutlineCallerPosition, OutlineCallerPositionBuilder> {
private final Int2StructuralItemArrayMap.Builder<Position> outlinePositionsBuilder =
Int2StructuralItemArrayMap.builder();
private DexMethod outlineCallee;
private boolean isOutline;
private OutlineCallerPositionBuilder() {}
@Override
OutlineCallerPositionBuilder self() {
return this;
}
public OutlineCallerPositionBuilder setOutlineCallee(DexMethod outlineCallee) {
this.outlineCallee = outlineCallee;
return this;
}
public OutlineCallerPositionBuilder addOutlinePosition(int line, Position callerPosition) {
outlinePositionsBuilder.put(line, callerPosition);
return this;
}
public OutlineCallerPositionBuilder setIsOutline(boolean isOutline) {
this.isOutline = isOutline;
return this;
}
public boolean hasOutlinePositions() {
return !outlinePositionsBuilder.isEmpty();
}
@Override
public OutlineCallerPosition build() {
assert noCheckOfPosition || line >= 0;
assert noCheckOfMethod || method != null;
return new OutlineCallerPosition(
line,
method,
callerPosition,
removeInnerFramesIfThrowingNpe,
outlinePositionsBuilder.build(),
outlineCallee,
isOutline);
}
}
}
}