in source/ClassProperties.cpp [374:433]
FeatureSet ClassProperties::compute_transitive_class_features(
const Method* callee) const {
// Check cache
if (const auto* target_method = via_dependencies_.get(callee, nullptr)) {
return get_class_features(
target_method->get_class()->str(), /* via_dependency */ true);
}
size_t depth = 0;
std::queue<QueueItem> queue;
queue.push(QueueItem{.method = callee, .depth = depth});
std::unordered_set<const Method*> processed;
QueueItem target{.method = nullptr, .depth = 0};
// Traverse the dependency graph till we find closest "exported" class.
// If no "exported" class is reachable, find the closest "unexported" class.
// "exported" class that is only reachable via an "unexported" class is
// ignored.
while (!queue.empty()) {
auto item = queue.front();
queue.pop();
processed.emplace(item.method);
depth = item.depth;
const auto& class_name = item.method->get_class()->str();
if (has_user_exposed_properties(class_name)) {
target = item;
break;
}
if (target.method == nullptr && has_user_unexposed_properties(class_name)) {
// Continue search for user exposed properties along other paths.
target = item;
continue;
}
if (depth == Heuristics::kMaxDepthClassProperties) {
continue;
}
for (const auto* dependency : dependencies_.dependencies(item.method)) {
if (processed.count(dependency) == 0) {
queue.push(QueueItem{.method = dependency, .depth = depth + 1});
}
}
}
if (target.method != nullptr) {
LOG(4,
"Class properties found for: `{}` via-dependency with `{}` at depth {}",
show(callee),
show(target.method),
target.depth);
via_dependencies_.insert({callee, target.method});
return get_class_features(
target.method->get_class()->str(), /* via_dependency */ true);
}
return FeatureSet();
}