source/Dependencies.cpp (105 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 <fmt/format.h> #include <IRInstruction.h> #include <Show.h> #include <SpartaWorkQueue.h> #include <mariana-trench/Assert.h> #include <mariana-trench/Dependencies.h> #include <mariana-trench/Heuristics.h> #include <mariana-trench/JsonValidation.h> #include <mariana-trench/Log.h> #include <mariana-trench/Methods.h> namespace marianatrench { Dependencies::Dependencies( const Options& options, const Methods& methods, const Overrides& overrides, const CallGraph& call_graph, const Registry& registry) { ConcurrentSet<const Method*> warn_many_overrides; auto queue = sparta::work_queue<const Method*>( [&](const Method* caller) { auto add_caller_as_dependency = [&](const Method* /* method */, std::unordered_set<const Method*>& dependencies, bool /* is_new */) { auto* code = caller->get_code(); if (!code) { return; } auto model = registry.get(caller); if (model.skip_analysis()) { return; } dependencies.insert(caller); }; auto callees = call_graph.callees(caller); for (const auto& call_target : callees) { if (!call_target.resolved()) { continue; } dependencies_.update( call_target.resolved_base_callee(), add_caller_as_dependency); if (!call_target.is_virtual()) { // We don't add a dependency for overrides of direct invocations. continue; } auto model = registry.get(call_target.resolved_base_callee()); if (model.no_join_virtual_overrides()) { continue; } if (Heuristics::kWarnOverrideThreshold && overrides.get(call_target.resolved_base_callee()).size() >= *Heuristics::kWarnOverrideThreshold) { warn_many_overrides.insert(call_target.resolved_base_callee()); } for (const auto* override : call_target.overrides()) { dependencies_.update(override, add_caller_as_dependency); } } const auto& artificial_callees = call_graph.artificial_callees(caller); for (const auto& [instruction, callees] : artificial_callees) { for (const auto& artificial_callee : callees) { dependencies_.update( artificial_callee.call_target.resolved_base_callee(), add_caller_as_dependency); } } }, sparta::parallel::default_num_threads()); for (const auto* method : methods) { queue.add_item(method); } queue.run_all(); for (const auto* method : warn_many_overrides) { WARNING( 1, "Method `{}` has {} overrides, consider marking it with `no-join-virtual-overrides` if the analysis is slow.", method->show(), overrides.get(method).size()); } if (options.dump_dependencies()) { auto dependencies_path = options.dependencies_output_path(); LOG(1, "Writing dependencies to `{}`", dependencies_path.native()); JsonValidation::write_json_file(dependencies_path, to_json()); } } const std::unordered_set<const Method*>& Dependencies::dependencies( const Method* method) const { // Note that `find` is not thread-safe, but this is fine because // `dependencies_` is read-only after the constructor completed. auto found = dependencies_.find(method); if (found != dependencies_.end()) { return found->second; } else { return empty_method_set_; } } Json::Value Dependencies::to_json() const { auto value = Json::Value(Json::objectValue); for (const auto& [method, dependencies] : dependencies_) { auto dependencies_value = Json::Value(Json::arrayValue); for (const auto* dependency : dependencies) { dependencies_value.append(Json::Value(show(dependency))); } value[method->show()] = dependencies_value; } return value; } } // namespace marianatrench