blob: 38b5e1a651fff79ef7f71a9652bb7ae74fb3d541 [file] [log] [blame]
// Copyright (c) 2016, 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.graph;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InvalidDebugInfoException;
import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.JarSourceCode;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.jar.InliningConstraintVisitor;
import com.android.tools.r8.jar.JarArgumentUseVisitor;
import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.BitSet;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BiFunction;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
public class JarCode extends Code {
// TODO(zerny): Write via the IR.
public void writeTo(MethodVisitor visitor) {
node.accept(visitor);
}
private final DexMethod method;
private final Origin origin;
private MethodNode node;
protected ReparseContext context;
protected final JarApplicationReader application;
private boolean reachabilitySensitive = false;
public JarCode(
DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
this.method = method;
this.origin = origin;
this.context = context;
this.application = application;
context.codeList.add(this);
}
public void markReachabilitySensitive() {
// We need to mark before we have reparsed so that the method code is reparsed
// including debug information.
assert context != null;
this.reachabilitySensitive = true;
}
public MethodNode getNode() {
triggerDelayedParsingIfNeccessary();
return node;
}
@Override
public boolean isJarCode() {
return true;
}
@Override
public JarCode asJarCode() {
return this;
}
@Override
protected int computeHashCode() {
triggerDelayedParsingIfNeccessary();
return node.hashCode();
}
@Override
protected boolean computeEquals(Object other) {
triggerDelayedParsingIfNeccessary();
if (this == other) {
return true;
}
if (other instanceof JarCode) {
JarCode o = (JarCode) other;
o.triggerDelayedParsingIfNeccessary();
// TODO(zerny): This amounts to object equality.
return node.equals(o.node);
}
return false;
}
@Override
public boolean isEmptyVoidMethod() {
for (Iterator<AbstractInsnNode> it = getNode().instructions.iterator(); it.hasNext(); ) {
AbstractInsnNode insn = it.next();
if (insn.getType() != Opcodes.RETURN
&& !(insn instanceof LabelNode)
&& !(insn instanceof LineNumberNode)) {
return false;
}
}
return true;
}
public boolean hasLocalVariableTable() {
return getNode().localVariables != null && !getNode().localVariables.isEmpty();
}
@Override
public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
return internalBuildPossiblyWithLocals(encodedMethod, encodedMethod, appView, null, null);
}
@Override
public IRCode buildInliningIR(
DexEncodedMethod context,
DexEncodedMethod encodedMethod,
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition,
Origin origin) {
assert generator != null;
return internalBuildPossiblyWithLocals(
context, encodedMethod, appView, generator, callerPosition);
}
private IRCode internalBuildPossiblyWithLocals(
DexEncodedMethod context,
DexEncodedMethod encodedMethod,
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition) {
triggerDelayedParsingIfNeccessary();
if (!keepLocals(encodedMethod, appView.options())) {
// If the locals are not kept, we might still need a bit of locals information to satisfy
// -keepparameternames for R8.
Map<Integer, DebugLocalInfo> parameterInfo = collectParameterInfo(encodedMethod, appView);
// We strip locals here because we will not be able to recover from invalid info.
if (canStripLocals(encodedMethod, appView)) {
node.localVariables.clear();
}
return internalBuild(
context, encodedMethod, appView, generator, callerPosition, parameterInfo);
} else {
return internalBuildWithLocals(context, encodedMethod, appView, generator, callerPosition);
}
}
private IRCode internalBuildWithLocals(
DexEncodedMethod context,
DexEncodedMethod encodedMethod,
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition) {
try {
return internalBuild(
context, encodedMethod, appView, generator, callerPosition, IRCode.NO_PARAMETER_INFO);
} catch (InvalidDebugInfoException e) {
appView.options().warningInvalidDebugInfo(encodedMethod, origin, e);
node.localVariables.clear();
return internalBuild(
context, encodedMethod, appView, generator, callerPosition, IRCode.NO_PARAMETER_INFO);
}
}
private boolean keepLocals(DexEncodedMethod encodedMethod, InternalOptions options) {
if (options.testing.noLocalsTableOnInput) {
return false;
}
if (options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()) {
return true;
}
return false;
}
private Map<Integer, DebugLocalInfo> collectParameterInfo(
DexEncodedMethod encodedMethod, AppView<?> appView) {
if (!appView.options().hasProguardConfiguration()
|| !appView.options().getProguardConfiguration().isKeepParameterNames()) {
return IRCode.NO_PARAMETER_INFO;
}
// The enqueuer might build IR to trace reflective behaviour. At that point liveness is not
// known, so be conservative with collection parameter name information.
if (appView.appInfo().hasLiveness()
&& !appView.appInfo().withLiveness().isPinned(encodedMethod.method)) {
return IRCode.NO_PARAMETER_INFO;
}
// Collect the local slots used for parameters.
BitSet localSlotsForParameters = new BitSet(0);
int nextLocalSlotsForParameters = 0;
if (!encodedMethod.isStatic()) {
localSlotsForParameters.set(nextLocalSlotsForParameters++);
}
for (DexType type : encodedMethod.method.proto.parameters.values) {
localSlotsForParameters.set(nextLocalSlotsForParameters);
nextLocalSlotsForParameters += type.isLongType() || type.isDoubleType() ? 2 : 1;
}
// Collect the first piece of local variable information for each argument local slot,
// assuming that that does actually describe the parameter (name, type and possibly
// signature).
DexItemFactory factory = appView.options().itemFactory;
Map<Integer, DebugLocalInfo> parameterInfo =
new Int2ReferenceArrayMap<>(localSlotsForParameters.cardinality());
for (Object o : node.localVariables) {
LocalVariableNode node = (LocalVariableNode) o;
if (node.index < nextLocalSlotsForParameters
&& localSlotsForParameters.get(node.index)
&& !parameterInfo.containsKey(node.index)) {
parameterInfo.put(
node.index,
new DebugLocalInfo(
factory.createString(node.name),
factory.createType(factory.createString(node.desc)),
node.signature == null ? null : factory.createString(node.signature)));
}
}
return parameterInfo;
}
private boolean canStripLocals(DexEncodedMethod encodedMethod, AppView<?> appView) {
// If not keeping parameter names the locals can always be stripped.
if (!appView.options().hasProguardConfiguration()
|| !appView.options().getProguardConfiguration().isKeepParameterNames()) {
return true;
}
// The enqueuer might build IR to trace reflective behaviour. At that point liveness is not
// known, so locals cannot be stripped as IR will built again in the IR converter.
if (appView.appInfo().hasLiveness()
&& !appView.appInfo().withLiveness().isPinned(encodedMethod.method)) {
return true;
}
return false;
}
private IRCode internalBuild(
DexEncodedMethod context,
DexEncodedMethod encodedMethod,
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition,
Map<Integer, DebugLocalInfo> parameterInfo) {
assert node.localVariables.isEmpty()
|| keepLocals(encodedMethod, appView.options())
|| !canStripLocals(encodedMethod, appView);
JarSourceCode source =
new JarSourceCode(
method.holder,
node,
application,
appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
callerPosition);
IRBuilder builder = new IRBuilder(encodedMethod, appView, source, origin, generator);
return builder.build(context, parameterInfo);
}
@Override
public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
triggerDelayedParsingIfNeccessary();
node.instructions.accept(
new JarRegisterEffectsVisitor(method.method.holder, registry, application));
for (TryCatchBlockNode tryCatchBlockNode : node.tryCatchBlocks) {
// Exception type can be null for "catch all" used for try/finally.
if (tryCatchBlockNode.type != null) {
registry.registerTypeReference(application.getTypeFromDescriptor(
DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type)));
}
}
}
@Override
public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) {
triggerDelayedParsingIfNeccessary();
node.instructions.accept(new JarArgumentUseVisitor(method, registry));
}
public ConstraintWithTarget computeInliningConstraint(
DexEncodedMethod encodedMethod,
AppView<AppInfoWithLiveness> appView,
GraphLense graphLense,
DexType invocationContext) {
triggerDelayedParsingIfNeccessary();
InliningConstraintVisitor visitor =
new InliningConstraintVisitor(
application, appView, graphLense, encodedMethod, invocationContext);
if (appView.options().enableDesugaring
&& appView.options().interfaceMethodDesugaring == OffOrAuto.Auto
&& !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
// TODO(b/120130831): Conservatively need to say "no" at this point if there are invocations
// to static interface methods. This should be fixed by making sure that the desugared
// versions of default and static interface methods are present in the application during
// IR processing.
visitor.disallowStaticInterfaceMethodCalls();
}
AbstractInsnNode insn = node.instructions.getFirst();
while (insn != null) {
insn.accept(visitor);
if (visitor.isFinished()) {
return visitor.getConstraint();
}
insn = insn.getNext();
}
for (TryCatchBlockNode block : node.tryCatchBlocks) {
visitor.accept(block);
if (visitor.isFinished()) {
return visitor.getConstraint();
}
}
return visitor.getConstraint();
}
@Override
public String toString() {
triggerDelayedParsingIfNeccessary();
TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
node.accept(visitor);
StringWriter writer = new StringWriter();
visitor.p.print(new PrintWriter(writer));
return writer.toString();
}
@Override
public String toString(DexEncodedMethod method, ClassNameMapper naming) {
return toString();
}
private void triggerDelayedParsingIfNeccessary() {
if (this.context != null) {
// The SecondVisitor is in charge of setting this.context to null.
ReparseContext context = this.context;
parseCode(context, false);
if (hasJsr(context)) {
parseCode(context, true);
assert !hasJsr(context);
}
assert verifyNoReparseContext(context.owner);
}
}
private void parseCode(ReparseContext context, boolean useJsrInliner) {
// If -keepparameternames is not specified and the keep attributes do not specify keeping
// either of LocalVariableTable, LocalVariableTypeTable or LineNumberTable, then we can skip
// parsing all the debug related attributes during code read. If the method is reachability
// sensitive we have to include debug information in order to get locals information which we
// need to extend the live ranges of locals for their entire scope.
int parsingOptions = ClassReader.SKIP_FRAMES;
ProguardConfiguration configuration = application.options.getProguardConfiguration();
if (configuration != null && !configuration.isKeepParameterNames()) {
ProguardKeepAttributes keep =
application.options.getProguardConfiguration().getKeepAttributes();
if (!application.options.getProguardConfiguration().isKeepParameterNames()
&& !keep.localVariableTable
&& !keep.localVariableTypeTable
&& !keep.lineNumberTable
&& !reachabilitySensitive) {
parsingOptions |= ClassReader.SKIP_DEBUG;
}
}
SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
try {
new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
} catch (Exception exception) {
throw new CompilationError(
"Unable to parse method `" + method.toSourceString() + "`", exception);
}
}
protected BiFunction<String, String, JarCode> createCodeLocator(ReparseContext context) {
return new DefaultCodeLocator(context, application);
}
private boolean hasJsr(ReparseContext context) {
for (Code code : context.codeList) {
if (hasJsr(code.asJarCode().node)) {
return true;
}
}
return false;
}
private boolean hasJsr(MethodNode node) {
Iterator<AbstractInsnNode> it = node.instructions.iterator();
while (it.hasNext()) {
int opcode = it.next().getOpcode();
if (opcode == Opcodes.JSR || opcode == Opcodes.RET) {
return true;
}
}
return false;
}
private static class DefaultCodeLocator implements BiFunction<String, String, JarCode> {
private final ReparseContext context;
private final JarApplicationReader application;
private int methodIndex = 0;
private DefaultCodeLocator(ReparseContext context, JarApplicationReader application) {
this.context = context;
this.application = application;
}
@Override
public JarCode apply(String name, String desc) {
JarCode code = context.codeList.get(methodIndex++).asJarCode();
assert code.method == application.getMethod(context.owner.type, name, desc);
return code;
}
}
/**
* Fills the MethodNodes of all the methods in the class and removes the ReparseContext.
*/
private static class SecondVisitor extends ClassVisitor {
private final BiFunction<String, String, JarCode> codeLocator;
private final boolean useJsrInliner;
public SecondVisitor(BiFunction<String, String, JarCode> codeLocator, boolean useJsrInliner) {
super(InternalOptions.ASM_VERSION);
this.codeLocator = codeLocator;
this.useJsrInliner = useJsrInliner;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
MethodNode node =
useJsrInliner
? new JSRInlinerAdapter(null, access, name, desc, signature, exceptions)
: new MethodNode(InternalOptions.ASM_VERSION, access, name, desc, signature, exceptions);
JarCode code = null;
MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
if (!flags.isAbstract() && !flags.isNative()) {
code = codeLocator.apply(name, desc);
}
if (code != null) {
code.context = null;
code.node = node;
return node;
}
return null;
}
}
private static boolean verifyNoReparseContext(DexProgramClass owner) {
for (DexEncodedMethod method : owner.virtualMethods()) {
Code code = method.getCode();
if (code != null && code.isJarCode()) {
if (code.asJarCode().context != null) {
return false;
}
}
}
for (DexEncodedMethod method : owner.directMethods()) {
Code code = method.getCode();
if (code != null && code.isJarCode()) {
if (code.asJarCode().context != null) {
return false;
}
}
}
return true;
}
}