Account for textual headers when determining the owning target of a decl

Before this cl, we did not generate bindings for decls coming from textual headers, as they didn't belong to any target/crate. Now, when we inspect a decl coming from a textual header, we go up the #include stack to locate the first header that has an owning target, and that target becomes the owner of the decl.

PiperOrigin-RevId: 423281830
diff --git a/rs_bindings_from_cc/ast_visitor.cc b/rs_bindings_from_cc/ast_visitor.cc
index 7bcb74f..10344bc 100644
--- a/rs_bindings_from_cc/ast_visitor.cc
+++ b/rs_bindings_from_cc/ast_visitor.cc
@@ -38,6 +38,7 @@
 #include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceManager.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Sema/Sema.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/Optional.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
 #include "util/gtl/flat_map.h"
 
@@ -322,17 +323,36 @@
 
 BlazeLabel AstVisitor::GetOwningTarget(const clang::Decl* decl) const {
   clang::SourceManager& source_manager = ctx_->getSourceManager();
-  llvm::StringRef filename = source_manager.getFilename(decl->getLocation());
+  auto source_location = decl->getLocation();
+  auto id = source_manager.getFileID(source_location);
 
-  if (filename.startswith("./")) filename = filename.substr(2);
-  auto target_iterator = headers_to_targets_.find(HeaderName(filename.str()));
-  if (target_iterator == headers_to_targets_.end()) {
-    // TODO(b/208377928): replace this hack with a
-    // CHECK(target_iterator != headers_to_targets_.end()) once we generate
-    // bindings for headers in Clang's resource dir.
-    return BlazeLabel("//:virtual_clang_resource_dir_target");
+  // If the header this decl comes from is not associated with a target we
+  // consider it a textual header. In that case we go up the include stack
+  // until we find a header that has an owning target.
+
+  // TODO(b/208377928): We currently don't have a target for the headers in
+  // Clang's resource directory, so for the time being we return a fictional
+  // "//:virtual_clang_resource_dir_target" for system headers.
+  while (source_location.isValid() &&
+         !source_manager.isInSystemHeader(source_location)) {
+    llvm::Optional<llvm::StringRef> filename =
+        source_manager.getNonBuiltinFilenameForID(id);
+    if (!filename) {
+      return BlazeLabel("//:builtin");
+    }
+    if (filename->startswith("./")) {
+      filename = filename->substr(2);
+    }
+    auto target_iterator =
+        headers_to_targets_.find(HeaderName(filename->str()));
+    if (target_iterator != headers_to_targets_.end()) {
+      return target_iterator->second;
+    }
+    source_location = source_manager.getIncludeLoc(id);
+    id = source_manager.getFileID(source_location);
   }
-  return target_iterator->second;
+
+  return BlazeLabel("//:virtual_clang_resource_dir_target");
 }
 
 bool AstVisitor::IsFromCurrentTarget(const clang::Decl* decl) const {
diff --git a/rs_bindings_from_cc/test/textual_headers/includes_struct_through_layers_of_textual_headers.h b/rs_bindings_from_cc/test/textual_headers/includes_struct_through_layers_of_textual_headers.h
new file mode 100644
index 0000000..154c23f
--- /dev/null
+++ b/rs_bindings_from_cc/test/textual_headers/includes_struct_through_layers_of_textual_headers.h
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_STRUCT_THROUGH_LAYERS_OF_TEXTUAL_HEADERS_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_STRUCT_THROUGH_LAYERS_OF_TEXTUAL_HEADERS_H_
+
+#include "rs_bindings_from_cc/test/textual_headers/includes_textual_header.inc"
+
+inline int getValue(MyStruct s) { return s.value; }
+
+#endif  // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_STRUCT_THROUGH_LAYERS_OF_TEXTUAL_HEADERS_H_
diff --git a/rs_bindings_from_cc/test/textual_headers/includes_textual_header.h b/rs_bindings_from_cc/test/textual_headers/includes_textual_header.h
new file mode 100644
index 0000000..74a0439
--- /dev/null
+++ b/rs_bindings_from_cc/test/textual_headers/includes_textual_header.h
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_TEXTUAL_HEADER_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_TEXTUAL_HEADER_H_
+
+#include "rs_bindings_from_cc/test/textual_headers/struct_in_textual_header.inc"
+
+inline int getValue(MyStruct s) { return s.value; }
+
+#endif  // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_TEXTUAL_HEADER_H_
diff --git a/rs_bindings_from_cc/test/textual_headers/includes_textual_header.inc b/rs_bindings_from_cc/test/textual_headers/includes_textual_header.inc
new file mode 100644
index 0000000..a819486
--- /dev/null
+++ b/rs_bindings_from_cc/test/textual_headers/includes_textual_header.inc
@@ -0,0 +1,10 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_TEXTUAL_HEADER_INC_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_TEXTUAL_HEADER_INC_
+
+#include "rs_bindings_from_cc/test/textual_headers/struct_in_textual_header.inc"
+
+#endif  // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEXTUAL_HEADERS_INCLUDES_TEXTUAL_HEADER_INC_
diff --git a/rs_bindings_from_cc/test/textual_headers/struct_in_textual_header.inc b/rs_bindings_from_cc/test/textual_headers/struct_in_textual_header.inc
new file mode 100644
index 0000000..c26caf6
--- /dev/null
+++ b/rs_bindings_from_cc/test/textual_headers/struct_in_textual_header.inc
@@ -0,0 +1,7 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+struct MyStruct {
+  int value;
+};
diff --git a/rs_bindings_from_cc/test/textual_headers/textual.inc b/rs_bindings_from_cc/test/textual_headers/textual.inc
index 9c15f8d..6b67ec2 100644
--- a/rs_bindings_from_cc/test/textual_headers/textual.inc
+++ b/rs_bindings_from_cc/test/textual_headers/textual.inc
@@ -7,4 +7,4 @@
 
 #ifndef USING_TEXTUAL_HEADER_IS_FINE
 #error "define USING_TEXTUAL_HEADER_IS_FINE to make this textual header work"
-#endif
\ No newline at end of file
+#endif
diff --git a/rs_bindings_from_cc/test/textual_headers/uses_struct_from_layers_of_textual_headers.rs b/rs_bindings_from_cc/test/textual_headers/uses_struct_from_layers_of_textual_headers.rs
new file mode 100644
index 0000000..64f2384
--- /dev/null
+++ b/rs_bindings_from_cc/test/textual_headers/uses_struct_from_layers_of_textual_headers.rs
@@ -0,0 +1,17 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+
+    #[test]
+    fn test_access_to_struct_through_the_right_crate() {
+        // MyStruct was defined in a textual header of :defines_struct_in_textual_hdr,
+        // but we should consider that header to belong to whichever target
+        // ends up including it in a nontextual header, in this case
+        // :uses_struct_from_textual_hdr_in_textual_hdr.
+        let x = uses_struct_from_textual_hdr_in_textual_hdr::MyStruct { value: 3 };
+        assert_eq!(uses_struct_from_textual_hdr_in_textual_hdr::getValue(x), 3);
+    }
+}
diff --git a/rs_bindings_from_cc/test/textual_headers/uses_struct_from_textual_header.rs b/rs_bindings_from_cc/test/textual_headers/uses_struct_from_textual_header.rs
new file mode 100644
index 0000000..9e16849
--- /dev/null
+++ b/rs_bindings_from_cc/test/textual_headers/uses_struct_from_textual_header.rs
@@ -0,0 +1,17 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+
+    #[test]
+    fn test_access_to_struct_from_textual_header() {
+        // MyStruct was defined in a textual header of :defines_struct_in_textual_hdr,
+        // but we should consider that header to belong to whichever target
+        // ends up including it through a nontextual header, in this case
+        // :uses_struct_from_textual_hdr.
+        let x = uses_struct_from_textual_hdr::MyStruct { value: 3 };
+        assert_eq!(uses_struct_from_textual_hdr::getValue(x), 3);
+    }
+}