source/Access.cpp (206 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 <algorithm>
#include <boost/algorithm/string/predicate.hpp>
#include <fmt/format.h>
#include <Show.h>
#include <mariana-trench/Access.h>
#include <mariana-trench/JsonValidation.h>
#include <mariana-trench/Method.h>
#include <mariana-trench/Redex.h>
namespace marianatrench {
std::optional<ParameterPosition> parse_parameter_position(
const std::string& string) {
if (string.empty() || string[0] == '-') {
// `std::stoul` would wrap around.
return std::nullopt;
}
try {
std::size_t processed = 0;
ParameterPosition parameter = std::stoul(string, &processed);
// Check that there are no remaining (non-digit) characters.
if (processed != string.size()) {
return std::nullopt;
}
return parameter;
} catch (const std::invalid_argument&) {
return std::nullopt;
} catch (const std::out_of_range&) {
return std::nullopt;
}
}
bool Path::operator==(const Path& other) const {
return elements_ == other.elements_;
}
void Path::append(Element element) {
mt_assert(element != nullptr);
elements_.push_back(element);
}
void Path::extend(const Path& path) {
elements_.insert(
elements_.end(), path.elements_.begin(), path.elements_.end());
}
void Path::pop_back() {
mt_assert(!elements_.empty());
elements_.pop_back();
}
void Path::truncate(std::size_t max_size) {
if (elements_.size() > max_size) {
elements_.resize(max_size);
}
}
bool Path::is_prefix_of(const Path& other) const {
auto result = std::mismatch(
elements_.begin(),
elements_.end(),
other.elements_.begin(),
other.elements_.end());
return result.first == elements_.end();
}
void Path::reduce_to_common_prefix(const Path& other) {
auto result = std::mismatch(
elements_.begin(),
elements_.end(),
other.elements_.begin(),
other.elements_.end());
elements_.erase(result.first, elements_.end());
}
std::ostream& operator<<(std::ostream& out, const Path& path) {
out << "Path[";
for (auto iterator = path.elements_.begin(), end = path.elements_.end();
iterator != end;) {
out << "`" << show(*iterator) << "`";
++iterator;
if (iterator != end) {
out << ", ";
}
}
return out << "]";
}
std::string Root::to_string() const {
switch (kind()) {
case Root::Kind::Argument:
return fmt::format("Argument({})", parameter_position());
case Root::Kind::Return:
return "Return";
case Root::Kind::Leaf:
return "Leaf";
case Root::Kind::Anchor:
return "Anchor";
case Root::Kind::Producer:
return "Producer";
case Root::Kind::CanonicalThis:
return "Argument(-1)";
default:
mt_unreachable();
}
}
Json::Value Root::to_json() const {
return to_string();
}
Root Root::from_json(const Json::Value& value) {
auto root_string = JsonValidation::string(value);
if (boost::starts_with(root_string, "Argument(") &&
boost::ends_with(root_string, ")") && root_string.size() >= 11) {
auto parameter_string = root_string.substr(9, root_string.size() - 10);
auto parameter = parse_parameter_position(parameter_string);
if (!parameter) {
throw JsonValidationError(
value,
/* field */ std::nullopt,
/* expected */
fmt::format(
"`Argument(<number>)` for access path root, got `{}`",
root_string));
}
// Note: `Root::Kind::CanonicalThis` (Argument(-1)) cannot be specified in
// JSON.
return Root(Root::Kind::Argument, *parameter);
} else if (root_string == "Return") {
return Root(Root::Kind::Return);
} else if (root_string == "Leaf") {
return Root(Root::Kind::Leaf);
} else if (root_string == "Anchor") {
return Root(Root::Kind::Anchor);
} else if (root_string == "Producer") {
return Root(Root::Kind::Producer);
} else {
throw JsonValidationError(
value,
/* field */ std::nullopt,
fmt::format(
"valid access path root (`Return`, `Argument(...)`, `Leaf`, `Anchor` or `Producer`), got `{}`",
root_string));
}
}
std::ostream& operator<<(std::ostream& out, const Root& root) {
return out << root.to_string();
}
bool AccessPath::operator==(const AccessPath& other) const {
return root_ == other.root_ && path_ == other.path_;
}
bool AccessPath::leq(const AccessPath& other) const {
return root_ == other.root_ && other.path_.is_prefix_of(path_);
}
void AccessPath::join_with(const AccessPath& other) {
mt_assert(root_ == other.root_);
path_.reduce_to_common_prefix(other.path_);
}
AccessPath AccessPath::canonicalize_for_method(const Method* method) const {
// The canonical port takes the form anchor:<root>. Path is ignored.
// For arguments, first argument starts at index 0. Non-static methods in
// Mariana Trench have their arguments off-by-one and are shifted down.
if (!root_.is_argument() || method->is_static()) {
return AccessPath(
Root(Root::Kind::Anchor),
Path{DexString::make_string(root_.to_string())});
}
auto position = root_.parameter_position();
if (position == 0) {
position = static_cast<Root::IntegerEncoding>(Root::Kind::CanonicalThis);
} else {
position = position - 1;
}
return AccessPath(
Root(Root::Kind::Anchor),
Path{DexString::make_string(
Root(Root::Kind::Argument, position).to_string())});
}
std::vector<std::string> AccessPath::split_path(const Json::Value& value) {
auto string = JsonValidation::string(value);
// Split the string by ".".
std::vector<std::string> elements;
std::string_view current = string;
while (!current.empty()) {
auto position = current.find('.');
if (position == std::string::npos) {
elements.push_back(std::string(current));
break;
} else {
elements.push_back(std::string(current.substr(0, position)));
current = current.substr(position + 1);
}
}
return elements;
}
AccessPath AccessPath::from_json(const Json::Value& value) {
auto elements = split_path(value);
if (elements.empty()) {
throw JsonValidationError(
value, /* field */ std::nullopt, "non-empty string for access path");
}
// Parse the root.
const auto& root_string = elements.front();
auto root = Root::from_json(root_string);
Path path;
for (auto iterator = std::next(elements.begin()), end = elements.end();
iterator != end;
++iterator) {
path.append(DexString::make_string(*iterator));
}
return AccessPath(root, path);
}
Json::Value AccessPath::to_json() const {
// We could return a json array containing path elements, but this would break
// all our tests since we sort all json arrays before comparing them.
std::string value = root_.to_string();
for (auto* field : path_) {
value.append(".");
value.append(show(field));
}
return value;
}
std::ostream& operator<<(std::ostream& out, const AccessPath& access_path) {
out << "AccessPath(" << access_path.root();
if (!access_path.path().empty()) {
out << ", " << access_path.path();
}
return out << ")";
}
} // namespace marianatrench