source/Rules.cpp (118 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/JsonValidation.h>
#include <mariana-trench/Kinds.h>
#include <mariana-trench/Log.h>
#include <mariana-trench/MultiSourceMultiSinkRule.h>
#include <mariana-trench/Options.h>
#include <mariana-trench/Rules.h>
#include <mariana-trench/SourceSinkRule.h>
namespace marianatrench {
Rules::Rules() = default;
Rules::Rules(Context& context, std::vector<std::unique_ptr<Rule>> rules) {
for (auto& rule : rules) {
add(context, std::move(rule));
}
}
Rules::Rules(Context& context, const Json::Value& rules_value) {
for (const auto& rule_value : JsonValidation::null_or_array(rules_value)) {
add(context, Rule::from_json(rule_value, context));
}
}
Rules Rules::load(Context& context, const Options& options) {
Rules rules;
for (const auto& rules_path : options.rules_paths()) {
auto rules_value = JsonValidation::parse_json_file(rules_path);
for (const auto& rule_value : JsonValidation::null_or_array(rules_value)) {
rules.add(context, Rule::from_json(rule_value, context));
}
}
return rules;
}
void Rules::add(Context& context, std::unique_ptr<Rule> rule) {
auto existing = rules_.find(rule->code());
if (existing != rules_.end()) {
ERROR(
1,
"A rule for code {} already exists! Duplicate rules are:\n{}\n{}",
rule->code(),
JsonValidation::to_styled_string(rule->to_json()),
JsonValidation::to_styled_string(existing->second->to_json()));
return;
}
auto code = rule->code();
auto result = rules_.emplace(code, std::move(rule));
const Rule* rule_pointer = result.first->second.get();
if (auto* source_sink_rule = rule_pointer->as<SourceSinkRule>()) {
for (const auto* source_kind : source_sink_rule->source_kinds()) {
for (const auto* sink_kind : source_sink_rule->sink_kinds()) {
source_to_sink_to_rules_[source_kind][sink_kind].push_back(
rule_pointer);
}
}
} else if (
const auto* multi_source_rule =
rule_pointer->as<MultiSourceMultiSinkRule>()) {
// Consider the rule:
// Code: 1000
// Sources: { lblA: [SourceA], lblB: [SourceB] }
// Sinks: [ Partial(SinkX, lblA), Partial(SinkX, lblB) ]
//
// A flow like SourceA -> Partial(SinkX, lblA) fulfills half the rule
// (tracked in `source_to_partial_sink_to_rules_`).
//
// When a half-fulfilled rule is seen, the analysis creates a triggered
// partial sink:
// B = Triggered(SinkX, lblB, rule: 1000)
//
// The rule is completely fulfilled when "SourceB -> B" is detected,
// which is what `source_to_sink_to_rules_` tracks.
//
// Tracking the rule in the triggered sink is necessary because there can
// be another rule with the same sinks but different sources:
// Code: 2000
// Sources: { lblA: [SourceC], lblB: [SourceB] }
// Sinks: [ Partial(SinkX, lblA), Partial(SinkX, lblB) ]
//
// Without the rule, we cannot tell which rule is satisfied:
// SourceB -> Triggered(SinkX, lblB) can match either rule.
// With the rule, it is clear that only the first rule applies:
// SourceB -> Triggered(SinkX, lblB, rule: 1000)
for (const auto& [source_label, source_kinds] :
multi_source_rule->multi_source_kinds()) {
for (const auto* source_kind : source_kinds) {
for (const auto* sink_kind :
multi_source_rule->partial_sink_kinds(source_label)) {
const auto* triggered =
context.kinds->get_triggered(sink_kind, multi_source_rule);
source_to_partial_sink_to_rules_[source_kind][sink_kind].push_back(
multi_source_rule);
source_to_sink_to_rules_[source_kind][triggered].push_back(
multi_source_rule);
}
}
}
} else {
// Unreachable code. Did we add a new type of rule?
mt_unreachable();
}
}
const std::vector<const Rule*>& Rules::rules(
const Kind* source_kind,
const Kind* sink_kind) const {
auto sink_to_rules = source_to_sink_to_rules_.find(source_kind);
if (sink_to_rules == source_to_sink_to_rules_.end()) {
return empty_rule_set_;
}
auto rules = sink_to_rules->second.find(sink_kind);
if (rules == sink_to_rules->second.end()) {
return empty_rule_set_;
}
return rules->second;
}
const std::vector<const MultiSourceMultiSinkRule*>& Rules::partial_rules(
const Kind* source_kind,
const PartialKind* sink_kind) const {
auto sink_to_rules = source_to_partial_sink_to_rules_.find(source_kind);
if (sink_to_rules == source_to_partial_sink_to_rules_.end()) {
return empty_multi_source_rule_set_;
}
auto rules = sink_to_rules->second.find(sink_kind);
if (rules == sink_to_rules->second.end()) {
return empty_multi_source_rule_set_;
}
return rules->second;
}
std::unordered_set<const Kind*> Rules::collect_unused_kinds(
const Kinds& kinds) const {
std::unordered_set<const Kind*> unused_kinds;
for (const auto* kind : kinds.kinds()) {
if (kind->as<TriggeredPartialKind>() != nullptr) {
// Triggered kinds are never used in rules. No need to warn.
continue;
}
if (std::all_of(begin(), end(), [kind](const Rule* rule) {
return !rule->uses(kind);
})) {
unused_kinds.insert(kind);
WARNING(
1,
"Kind `{}` is not used in any rule! You may want to add one for it.",
show(kind));
}
}
return unused_kinds;
}
} // namespace marianatrench