FeatureSet ClassProperties::compute_transitive_class_features()

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();
}