All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.analysis.datastructure.callgraph.Call Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2023 Guardsquare NV
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package proguard.analysis.datastructure.callgraph;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import proguard.analysis.CallResolver;
import proguard.analysis.CallVisitor;
import proguard.analysis.datastructure.CodeLocation;
import proguard.classfile.Method;
import proguard.classfile.MethodSignature;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.InstructionUtil;
import proguard.classfile.util.ClassUtil;
import proguard.classfile.visitor.MemberVisitor;
import proguard.evaluation.value.IdentifiedReferenceValue;
import proguard.evaluation.value.Value;

/**
 * Represents a method call. If the call target is a {@link Method} that is present in the class
 * pool, a {@link ConcreteCall} is instantiated. If the call target is not a known method, a {@link
 * SymbolicCall} is the appropriate subclass.
 *
 * @author Samuel Hopstock
 */
public abstract class Call {

  private static final Logger log = LogManager.getLogger(Call.class);
  /** The location where the call was invoked. */
  public final CodeLocation caller;
  /**
   * Describes whether this call will throw a {@link NullPointerException} at runtime. Either {@link
   * Value#NEVER}, {@link Value#MAYBE} or {@link Value#ALWAYS}.
   */
  public final int throwsNullptr;
  /**
   * The instruction performing this call. There are several different ways of invoking a method
   * call in the JVM:
   *
   * 
    *
  • {@link Instruction#OP_INVOKESTATIC} *
  • {@link Instruction#OP_INVOKEVIRTUAL} *
  • {@link Instruction#OP_INVOKEINTERFACE} *
  • {@link Instruction#OP_INVOKESPECIAL} *
  • {@link Instruction#OP_INVOKEDYNAMIC} *
* * See the {@link CallResolver} for more details. */ public final Instruction instruction; /** * If false, control flow in the calling method will always reach this call. Otherwise, whether * the call will be invoked at runtime might depend on e.g. specific branches being taken. */ public final boolean controlFlowDependent; /** * If true, this call might only be one of several alternative targets, depending on the actual * type of the called object during runtime. Otherwise, this call is the only possible target. */ public final boolean runtimeTypeDependent; private Value instance; private List arguments; private Value returnValue; private boolean valuesCleared; protected Call( CodeLocation caller, Value instance, List arguments, Value returnValue, int throwsNullptr, Instruction instruction, boolean controlFlowDependent, boolean runtimeTypeDependent) { this.caller = caller; this.instance = instance; this.arguments = arguments; this.returnValue = returnValue; this.throwsNullptr = throwsNullptr; this.instruction = instruction; this.controlFlowDependent = controlFlowDependent; this.runtimeTypeDependent = runtimeTypeDependent; } /** Check if this call is static (no implicit {@link #instance} set) or not. */ public boolean isStatic() { return InstructionUtil.isStaticCall(instruction.opcode); } /** The {@link MethodSignature} of the method that is being called. */ public abstract MethodSignature getTarget(); /** Check if this call's target is fully known or only parts of it (e.g. only the descriptor). */ public abstract boolean hasIncompleteTarget(); /** Returns the number of arguments. */ public int getArgumentCount() { if (!valuesCleared) { return arguments.size(); } return ClassUtil.internalMethodParameterCount(getTarget().descriptor.toString()); } /** Returns the number of elements that need to be popped from the JVM stack for this call. */ public int getJvmArgumentSize() { return ClassUtil.internalMethodParameterSize(getTarget().descriptor.toString(), isStatic()); } /** * Get the value for a specific argument index. * *

Note: This is only to be used in implementations of {@link * CallVisitor#visitCall(Call)}. Afterwards, the values will have been cleared to reduce * unnecessary memory usage, as argument values are not needed for the full call graph * reconstruction. */ public Value getArgument(int index) { if (valuesCleared) { log.error("Argument requested after values have been cleared!"); return null; } if (arguments.size() <= index) { return null; } return arguments.get(index); } public void setArguments(List arguments) { this.arguments = arguments; } /** * If this is a virtual call, this describes the this pointer of the object whose * method is called, usually an {@link IdentifiedReferenceValue}. For static calls this is null. * *

Note: This is only to be used in implementations of {@link * CallVisitor#visitCall(Call)}. Afterwards, the value will have been cleared to reduce * unnecessary memory usage, as the instance value is not needed for the full call graph * reconstruction. */ public Value getInstance() { if (valuesCleared) { log.error("Instance requested after values have been cleared!"); return null; } return instance; } public void setInstance(Value instance) { this.instance = instance; } /** * Get the return value of this call. * *

Note: This is only to be used in implementations of {@link * CallVisitor#visitCall(Call)}. Afterwards, the value will have been cleared to reduce * unnecessary memory usage, as the return value is not needed for the full call graph * reconstruction. */ public Value getReturnValue() { if (valuesCleared) { log.error("Return value requested after values have been cleared!"); return null; } return returnValue; } public void setReturnValue(Value returnValue) { this.returnValue = returnValue; } /** Clear all {@link Value} object references from this call. */ public void clearValues() { arguments = Collections.nCopies(arguments.size(), null); returnValue = null; instance = null; valuesCleared = true; } /** * Returns true if this call is always executed, no matter which branch in the methods are taken * and which type the called object has during runtime. */ public boolean isCertainlyCalled() { return !controlFlowDependent && !runtimeTypeDependent; } @Override public String toString() { String nullSuffix = ""; if (throwsNullptr == Value.ALWAYS) { nullSuffix = " (always throws NullPointerException)"; } else if (throwsNullptr == Value.MAYBE) { nullSuffix = " (might throw NullPointerException)"; } return "[" + instruction.getName() + "] " + caller + " -> " + getTarget() + nullSuffix; } /** * Prints a shorter version of the regular `toString()` without the caller or null pointer * information. */ public String toSimpleString() { return "[" + instruction.getName() + "] " + getTarget(); } public void targetMethodAccept(MemberVisitor memberVisitor) {} @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Call that = (Call) o; return throwsNullptr == that.throwsNullptr && Objects.equals(caller, that.caller) && Objects.equals(arguments, that.arguments) && Objects.equals(instruction, that.instruction); } @Override public int hashCode() { return Objects.hash(caller, throwsNullptr); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy