blob: 09a26310d0a67f283e064c017d1c6992eff09817 [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.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import com.google.common.annotations.VisibleForTesting;
import java.util.Objects;
public class Position implements StructuralItem<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 Position NO_POSITION = new Position(-1, null, null, null, false);
// 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 Position(-1, null, null, null, true);
// Fake position to use for representing an actual position in testing code.
private static final Position TESTING_POSITION = new Position(0, null, null, null, true);
public final int line;
public final DexString file;
public final boolean synthetic;
// 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.
public final DexMethod method;
public final Position callerPosition;
private static void specify(StructuralSpecification<Position, ?> spec) {
spec.withInt(p -> p.line)
.withNullableItem(p -> p.file)
.withBool(p -> p.synthetic)
.withNullableItem(p -> p.method)
.withNullableItem(p -> p.callerPosition);
}
public Position(int line, DexString file, DexMethod method, Position callerPosition) {
this(line, file, method, callerPosition, false);
assert line >= 0;
assert method != null;
}
private Position(
int line, DexString file, DexMethod method, Position callerPosition, boolean synthetic) {
this.line = line;
this.file = file;
this.synthetic = synthetic;
this.method = method;
this.callerPosition = callerPosition;
assert callerPosition == null || callerPosition.method != null;
}
public static Position synthetic(int line, DexMethod method, Position callerPosition) {
assert line >= 0;
assert method != null;
return new Position(line, null, method, callerPosition, true);
}
public static Position none() {
return NO_POSITION;
}
public static Position syntheticNone() {
return NO_POSITION_SYNTHETIC;
}
@VisibleForTesting
public static Position testingPosition() {
return TESTING_POSITION;
}
// This factory method is used by the Inliner to create Positions when the caller has no valid
// positions. Since the callee still may have valid positions we need a non-null Position to set
// it as the caller of the inlined Positions.
public static Position noneWithMethod(DexMethod method, Position callerPosition) {
assert method != null;
return new Position(-1, null, method, callerPosition, false);
}
public static Position getPositionForInlining(
AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
Position position = invoke.getPosition();
if (position.method == null) {
assert position.isNone();
position = Position.noneWithMethod(context.getReference(), null);
}
assert position.getOutermostCaller().method
== appView.graphLens().getOriginalMethodSignature(context.getReference());
return position;
}
public boolean hasCallerPosition() {
return callerPosition != null;
}
@Override
public Position self() {
return this;
}
@Override
public StructuralMapping<Position> getStructuralMapping() {
return Position::specify;
}
public boolean isNone() {
return line == -1;
}
public boolean isSyntheticNone() {
return this == NO_POSITION_SYNTHETIC;
}
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 getCallerPosition() {
return callerPosition;
}
public Position withCallerPosition(Position callerPosition) {
return new Position(line, file, method, callerPosition, synthetic);
}
public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
return withCallerPosition(
hasCallerPosition()
? getCallerPosition().withOutermostCallerPosition(newOutermostCallerPosition)
: newOutermostCallerPosition);
}
@Override
public boolean equals(Object other) {
return other instanceof Position && compareTo((Position) other) == 0;
}
@Override
public int hashCode() {
int result = line;
result = 31 * result + Objects.hashCode(file);
result = 31 * result + (synthetic ? 1 : 0);
result = 31 * result + Objects.hashCode(method);
result = 31 * result + Objects.hashCode(callerPosition);
return result;
}
private String toString(boolean forceMethod) {
if (isNone()) {
return "--";
}
StringBuilder builder = new StringBuilder();
if (file != null) {
builder.append(file).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);
}
}