source/CallGraph.h (162 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <boost/iterator/filter_iterator.hpp>
#include <boost/range/iterator_range.hpp>
#include <json/json.h>
#include <ConcurrentContainers.h>
#include <mariana-trench/Access.h>
#include <mariana-trench/ClassHierarchies.h>
#include <mariana-trench/Compiler.h>
#include <mariana-trench/Feature.h>
#include <mariana-trench/FeatureSet.h>
#include <mariana-trench/Field.h>
#include <mariana-trench/Fields.h>
#include <mariana-trench/Method.h>
#include <mariana-trench/Options.h>
#include <mariana-trench/Overrides.h>
#include <mariana-trench/Types.h>
namespace marianatrench {
/**
* Represents information about a specific call.
*/
class CallTarget final {
private:
struct FilterOverrides {
bool operator()(const Method*) const;
const std::unordered_set<const DexType*>* MT_NULLABLE extends;
};
public:
using OverridesRange = boost::iterator_range<boost::filter_iterator<
FilterOverrides,
std::unordered_set<const Method*>::const_iterator>>;
public:
static CallTarget static_call(
const IRInstruction* instruction,
const Method* MT_NULLABLE callee);
static CallTarget virtual_call(
const IRInstruction* instruction,
const Method* MT_NULLABLE resolved_base_callee,
const DexType* MT_NULLABLE receiver_type,
const ClassHierarchies& class_hierarchies,
const Overrides& override_factory);
static CallTarget from_call_instruction(
const Method* caller,
const IRInstruction* instruction,
const Method* MT_NULLABLE resolved_base_callee,
const Types& types,
const ClassHierarchies& class_hierarchies,
const Overrides& override_factory);
CallTarget(const CallTarget&) = default;
CallTarget(CallTarget&&) = default;
CallTarget& operator=(const CallTarget&) = default;
CallTarget& operator=(CallTarget&&) = default;
~CallTarget() = default;
/*
* The instruction that triggered the call.
* Note that this is not necessarily an invoke, see artificial calls.
*/
const IRInstruction* instruction() const {
return instruction_;
}
/* Return true if the call behaves like a virtual call. */
bool is_virtual() const {
return overrides_ != nullptr;
}
bool resolved() const {
return resolved_base_callee_ != nullptr;
}
/**
* For a static call, returns the callee.
* For a virtual call, returns the resolved base callee. This is the base
* method of all possible callees (i.e, all overrides). The method is
* resolved, i.e if the method is not defined, we use the first defined
* method in the closest parent class.
*
* For instance:
* ```
* class A { void f() { ... } }
* class B implements A {}
* class C extends B { void f() { ... } }
* ```
* A virtual call to `B::f` has a resolved base callee of `A::f`.
*/
const Method* MT_NULLABLE resolved_base_callee() const {
return resolved_base_callee_;
}
/* For a virtual call, returns the inferred type of `this`. */
const DexType* MT_NULLABLE receiver_type() const {
return receiver_type_;
}
/**
* For a virtual call, returns all overriding methods that could be called.
* This does not include the resolved base callee.
*/
OverridesRange overrides() const;
bool operator==(const CallTarget& other) const;
friend std::ostream& operator<<(
std::ostream& out,
const CallTarget& call_target);
friend struct std::hash<CallTarget>;
private:
CallTarget(
const IRInstruction* instruction,
const Method* MT_NULLABLE resolved_base_callee,
const DexType* MT_NULLABLE receiver_type,
const std::unordered_set<const Method*>* MT_NULLABLE overrides,
const std::unordered_set<const DexType*>* MT_NULLABLE receiver_extends);
private:
const IRInstruction* instruction_;
const Method* MT_NULLABLE resolved_base_callee_;
const DexType* MT_NULLABLE receiver_type_;
const std::unordered_set<const Method*>* MT_NULLABLE overrides_;
const std::unordered_set<const DexType*>* MT_NULLABLE receiver_extends_;
};
} // namespace marianatrench
template <>
struct std::hash<marianatrench::CallTarget> {
std::size_t operator()(const marianatrench::CallTarget& call_target) const {
std::size_t seed = 0;
boost::hash_combine(seed, call_target.instruction_);
boost::hash_combine(seed, call_target.resolved_base_callee_);
boost::hash_combine(seed, call_target.receiver_type_);
boost::hash_combine(seed, call_target.overrides_);
boost::hash_combine(seed, call_target.receiver_extends_);
return seed;
}
};
namespace marianatrench {
/**
* Represents an artificial edge in the call graph.
*
* This is currently used to simulate calls to methods of anonymous classes
* flowing through an external method.
*
* `register_parameters` is the list of register parameters for the artificial
* invoke instruction.
*/
struct ArtificialCallee {
CallTarget call_target;
std::vector<Register> register_parameters;
FeatureSet features;
bool operator==(const ArtificialCallee& other) const;
friend std::ostream& operator<<(
std::ostream& out,
const ArtificialCallee& callee);
};
using ArtificialCallees = std::vector<ArtificialCallee>;
class CallGraph final {
public:
explicit CallGraph(
const Options& options,
Methods& methods,
Fields& fields,
const Types& types,
const ClassHierarchies& class_hierarchies,
Overrides& overrides,
const Features& features);
CallGraph(const CallGraph&) = delete;
CallGraph(CallGraph&&) = delete;
CallGraph& operator=(const CallGraph&) = delete;
CallGraph& operator=(CallGraph&&) = delete;
~CallGraph() = default;
/* Return all call targets for the given method. */
std::vector<CallTarget> callees(const Method* caller) const;
/* Return the call target for the given method and instruction. */
CallTarget callee(const Method* caller, const IRInstruction* instruction)
const;
/* Return the resolved base callee for the given method and instruction. */
const Method* MT_NULLABLE resolved_base_callee(
const Method* caller,
const IRInstruction* instruction) const;
/* Return a mapping from invoke instruction to artificial callees. */
const std::unordered_map<const IRInstruction*, ArtificialCallees>&
artificial_callees(const Method* caller) const;
/* Return the artificial callees for an invoke instruction. */
const ArtificialCallees& artificial_callees(
const Method* caller,
const IRInstruction* instruction) const;
/* Return the field being accessed within the given method and instruction */
const Field* MT_NULLABLE resolved_field_access(
const Method* caller,
const IRInstruction* instruction) const;
Json::Value to_json(bool with_overrides = true) const;
private:
const Types& types_;
const ClassHierarchies& class_hierarchies_;
const Overrides& overrides_;
ConcurrentMap<
const Method*,
std::unordered_map<const IRInstruction*, const Method*>>
resolved_base_callees_;
ConcurrentMap<
const Method*,
std::unordered_map<const IRInstruction*, const Field*>>
resolved_fields_;
ConcurrentMap<
const Method*,
std::unordered_map<const IRInstruction*, ArtificialCallees>>
artificial_callees_;
std::unordered_map<const IRInstruction*, ArtificialCallees>
empty_artificial_callees_map_;
ArtificialCallees empty_artificial_callees_;
};
} // namespace marianatrench