source/CalleeFrames.cpp (212 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.
*/
#include <Show.h>
#include <mariana-trench/Assert.h>
#include <mariana-trench/CalleeFrames.h>
#include <mariana-trench/Frame.h>
#include <mariana-trench/Log.h>
namespace marianatrench {
CalleeFrames::CalleeFrames(std::initializer_list<Frame> frames)
: callee_(nullptr) {
for (const auto& frame : frames) {
add(frame);
}
}
void CalleeFrames::add(const Frame& frame) {
if (callee_ == nullptr) {
callee_ = frame.callee();
} else {
mt_assert(callee_ == frame.callee());
}
// TODO (T91357916): GroupHashedSetAbstractDomain could be more efficient.
// It supports an `add` operation that avoids making a copy.
frames_.update(
frame.call_position(), [&](const CallPositionFrames& old_frames) {
auto new_frames = old_frames;
new_frames.add(frame);
return new_frames;
});
}
bool CalleeFrames::leq(const CalleeFrames& other) const {
mt_assert(is_bottom() || other.is_bottom() || callee_ == other.callee());
return frames_.leq(other.frames_);
}
bool CalleeFrames::equals(const CalleeFrames& other) const {
mt_assert(is_bottom() || other.is_bottom() || callee_ == other.callee());
return frames_.equals(other.frames_);
}
void CalleeFrames::join_with(const CalleeFrames& other) {
mt_if_expensive_assert(auto previous = *this);
if (is_bottom()) {
mt_assert(callee_ == nullptr);
callee_ = other.callee();
}
mt_assert(other.is_bottom() || callee_ == other.callee());
frames_.join_with(other.frames_);
mt_expensive_assert(previous.leq(*this) && other.leq(*this));
}
void CalleeFrames::widen_with(const CalleeFrames& other) {
mt_if_expensive_assert(auto previous = *this);
if (is_bottom()) {
mt_assert(callee_ == nullptr);
callee_ = other.callee();
}
mt_assert(other.is_bottom() || callee_ == other.callee());
frames_.widen_with(other.frames_);
mt_expensive_assert(previous.leq(*this) && other.leq(*this));
}
void CalleeFrames::meet_with(const CalleeFrames& other) {
if (is_bottom()) {
mt_assert(callee_ == nullptr);
callee_ = other.callee();
}
mt_assert(other.is_bottom() || callee_ == other.callee());
frames_.meet_with(other.frames_);
}
void CalleeFrames::narrow_with(const CalleeFrames& other) {
if (is_bottom()) {
mt_assert(callee_ == nullptr);
callee_ = other.callee();
}
mt_assert(other.is_bottom() || callee_ == other.callee());
frames_.narrow_with(other.frames_);
}
void CalleeFrames::difference_with(const CalleeFrames& other) {
if (is_bottom()) {
mt_assert(callee_ == nullptr);
callee_ = other.callee();
}
mt_assert(other.is_bottom() || callee_ == other.callee());
frames_.difference_like_operation(
other.frames_,
[](const CallPositionFrames& frames_left,
const CallPositionFrames& frames_right) {
auto frames_copy = frames_left;
frames_copy.difference_with(frames_right);
return frames_copy;
});
}
void CalleeFrames::map(const std::function<void(Frame&)>& f) {
frames_.map([&](const CallPositionFrames& frames) {
auto new_frames = frames;
new_frames.map(f);
return new_frames;
});
}
void CalleeFrames::add_inferred_features(const FeatureMayAlwaysSet& features) {
if (features.empty()) {
return;
}
map([&features](Frame& frame) { frame.add_inferred_features(features); });
}
LocalPositionSet CalleeFrames::local_positions() const {
auto result = LocalPositionSet::bottom();
for (const auto& [_, frames] : frames_.bindings()) {
result.join_with(frames.local_positions());
}
return result;
}
void CalleeFrames::add_local_position(const Position* position) {
map([position](Frame& frame) { frame.add_local_position(position); });
}
void CalleeFrames::set_local_positions(const LocalPositionSet& positions) {
map([&positions](Frame& frame) { frame.set_local_positions(positions); });
}
void CalleeFrames::add_inferred_features_and_local_position(
const FeatureMayAlwaysSet& features,
const Position* MT_NULLABLE position) {
if (features.empty() && position == nullptr) {
return;
}
map([&features, position](Frame& frame) {
if (!features.empty()) {
frame.add_inferred_features(features);
}
if (position != nullptr) {
frame.add_local_position(position);
}
});
}
CalleeFrames CalleeFrames::propagate(
const Method* callee,
const AccessPath& callee_port,
const Position* call_position,
int maximum_source_sink_distance,
Context& context,
const std::vector<const DexType * MT_NULLABLE>& source_register_types,
const std::vector<std::optional<std::string>>& source_constant_arguments)
const {
if (is_bottom()) {
return CalleeFrames::bottom();
}
CallPositionFrames result;
for (const auto& [_, call_position_frames] : frames_.bindings()) {
result.join_with(call_position_frames.propagate(
callee,
callee_port,
call_position,
maximum_source_sink_distance,
context,
source_register_types,
source_constant_arguments));
}
mt_assert(call_position == result.position());
return CalleeFrames(
callee, FramesByCallPosition{std::pair(call_position, result)});
}
CalleeFrames CalleeFrames::attach_position(const Position* position) const {
CallPositionFrames result;
// NOTE: It is not sufficient to simply update the key in the underlying
// frames_ map. This functions similarly to `propagate`. Frame features are
// propagated here, and we must call `CallPositionFrames::attach_position`
// to ensure that.
for (const auto& [_, call_position_frames] : frames_.bindings()) {
result.join_with(call_position_frames.attach_position(position));
}
return CalleeFrames(
callee_, FramesByCallPosition{std::pair(position, result)});
}
CalleeFrames CalleeFrames::transform_kind_with_features(
const std::function<std::vector<const Kind*>(const Kind*)>& transform_kind,
const std::function<FeatureMayAlwaysSet(const Kind*)>& add_features) const {
FramesByCallPosition new_frames;
for (const auto& [position, frames] : frames_.bindings()) {
new_frames.set(
position,
frames.transform_kind_with_features(transform_kind, add_features));
}
return CalleeFrames(callee_, new_frames);
}
void CalleeFrames::append_callee_port(
Path::Element path_element,
const std::function<bool(const Kind*)>& filter) {
// TODO (T91357916): GroupHashedSetAbstractDomain could be more efficient.
// It supports in-place modifying of the elements as long as the key does
// not change.
frames_.map([&](const CallPositionFrames& frames) {
auto frames_copy = frames;
frames_copy.append_callee_port(path_element, filter);
return frames_copy;
});
}
void CalleeFrames::filter_invalid_frames(
const std::function<bool(const Method*, const AccessPath&, const Kind*)>&
is_valid) {
frames_.map([&](const CallPositionFrames& frames) {
auto frames_copy = frames;
frames_copy.filter_invalid_frames(is_valid);
return frames_copy;
});
}
bool CalleeFrames::contains_kind(const Kind* kind) const {
auto frames_iterator = frames_.bindings();
return std::any_of(
frames_iterator.begin(),
frames_iterator.end(),
[&](const std::pair<const Position*, CallPositionFrames>&
callee_frames_pair) {
return callee_frames_pair.second.contains_kind(kind);
});
}
std::ostream& operator<<(std::ostream& out, const CalleeFrames& frames) {
if (frames.is_top()) {
return out << "T";
} else {
out << "[";
for (const auto& [position, frames] : frames.frames_.bindings()) {
out << "FramesByPosition(position=" << show(position) << ","
<< "frames=" << frames << "),";
}
return out << "]";
}
}
} // namespace marianatrench