Fix race condition when using validations

To allow a validation B on a target A to depend on A I previously
decoupled resolution of A from resolution of B. While testing this with
fuchsia I ran into a race condition where target A would be written
while B was not fully resolved. This caused the Ninja file to contain
garbled output for B.

This CL decouples resolution and writing so that a target A can resolve
but waits with writing until all its validations are resolved too.

Bug: 478798763
Change-Id: Ica3d89f90b3753fde9edbbf8ad7d9fe2e1ca372a
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/20920
Commit-Queue: Neri Marschik <nerima@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/builder.cc b/src/gn/builder.cc
index 84eff84..51f9a02 100644
--- a/src/gn/builder.cc
+++ b/src/gn/builder.cc
@@ -529,7 +529,7 @@
     if (!ResolveDeps(&target->public_deps(), err) ||
         !ResolveDeps(&target->private_deps(), err) ||
         !ResolveDeps(&target->data_deps(), err) ||
-        !ResolveDeps(&target->validations(), err) ||
+        !ResolveValidationDeps(&target->validations(), err) ||
         !ResolveConfigs(&target->configs(), err) ||
         !ResolveConfigs(&target->all_dependent_configs(), err) ||
         !ResolveConfigs(&target->public_configs(), err) ||
@@ -586,8 +586,10 @@
 bool Builder::CompleteItemResolution(BuilderRecord* record, Err* err) {
   record->set_resolved(true);
 
-  if (record->should_generate() && resolved_and_generated_callback_)
+  if (record->should_generate() && record->can_write() &&
+      resolved_and_generated_callback_) {
     resolved_and_generated_callback_(record);
+  }
 
   // Recursively update everybody waiting on this item to be resolved.
   const BuilderRecordSet waiting_deps = record->waiting_on_resolution();
@@ -599,6 +601,21 @@
     }
   }
   record->waiting_on_resolution().clear();
+
+  // Check for any targets waiting on this one for writing.
+  const BuilderRecordSet waiting_for_writing =
+      record->waiting_on_resolution_for_writing();
+  for (auto it = waiting_for_writing.begin(); it.valid(); ++it) {
+    BuilderRecord* waiting = *it;
+    if (waiting->OnResolvedValidationDep(record)) {
+      if (waiting->can_write() && waiting->should_generate() &&
+          resolved_and_generated_callback_) {
+        resolved_and_generated_callback_(waiting);
+      }
+    }
+  }
+  record->waiting_on_resolution_for_writing().clear();
+
   return true;
 }
 
@@ -615,6 +632,34 @@
   return true;
 }
 
+bool Builder::ResolveValidationDeps(LabelTargetVector* deps, Err* err) {
+  for (LabelTargetPair& cur : *deps) {
+    DCHECK(!cur.ptr);
+
+    // We only need the item to be defined, not resolved.
+    BuilderRecord* record = GetRecord(cur.label);
+    if (!record || !record->item()) {
+      *err = Err(cur.origin, "Item not found",
+                 "\"" + cur.label.GetUserVisibleName(true) +
+                     "\" doesn't\n"
+                     "refer to an existent thing.");
+      return false;
+    }
+
+    if (!BuilderRecord::IsItemOfType(record->item(),
+                                     BuilderRecord::ITEM_TARGET)) {
+      *err =
+          Err(cur.origin, "This is not a target",
+              "\"" + cur.label.GetUserVisibleName(true) + "\" refers to a " +
+                  record->item()->GetItemTypeName() + " instead of a target.");
+      return false;
+    }
+
+    cur.ptr = record->item()->AsTarget();
+  }
+  return true;
+}
+
 bool Builder::ResolveConfigs(UniqueVector<LabelConfigPair>* configs, Err* err) {
   for (const auto& cur : *configs) {
     DCHECK(!cur.ptr);
diff --git a/src/gn/builder.h b/src/gn/builder.h
index 6578a89..d6c42eb 100644
--- a/src/gn/builder.h
+++ b/src/gn/builder.h
@@ -132,6 +132,7 @@
   // that everything should be resolved by this point, so will return an error
   // if anything isn't found or if the type doesn't match.
   bool ResolveDeps(LabelTargetVector* deps, Err* err);
+  bool ResolveValidationDeps(LabelTargetVector* deps, Err* err);
   bool ResolveConfigs(UniqueVector<LabelConfigPair>* configs, Err* err);
   bool ResolvePool(Target* target, Err* err);
   bool ResolveToolchain(Target* target, Err* err);
diff --git a/src/gn/builder_record.cc b/src/gn/builder_record.cc
index f5af711..ff1a613 100644
--- a/src/gn/builder_record.cc
+++ b/src/gn/builder_record.cc
@@ -76,9 +76,24 @@
 }
 
 void BuilderRecord::AddValidationDep(BuilderRecord* record) {
-  if (all_deps_.add(record) && !record->item()) {
-    unresolved_count_ += 1;
-    record->waiting_on_definition_.add(this);
+  // Validation dependencies are treated differently than normal dependencies:
+  // 1. They are added to all_deps_ for graph traversal (should_generate
+  //    propagation) and error checking.
+  // 2. They block resolution ONLY if they are undefined (!item). This allows
+  //    cycles (A is validated by B, B depends on A) to resolve.
+  // 3. They block writing if they are unresolved. This prevents race conditions
+  //    where we might write the ninja file before the validation output path
+  //    is computed.
+  all_deps_.add(record);
+  if (validation_deps_.add(record)) {
+    if (!record->item()) {
+      unresolved_count_ += 1;
+      record->waiting_on_definition_.add(this);
+    }
+    if (!record->resolved()) {
+      unresolved_validation_count_ += 1;
+      record->waiting_on_resolution_for_writing_.add(this);
+    }
   }
 }
 
@@ -94,6 +109,12 @@
   return --unresolved_count_ == 0;
 }
 
+bool BuilderRecord::OnResolvedValidationDep(const BuilderRecord* dep) {
+  DCHECK(validation_deps_.contains(const_cast<BuilderRecord*>(dep)));
+  DCHECK(unresolved_validation_count_ > 0);
+  return --unresolved_validation_count_ == 0;
+}
+
 std::vector<const BuilderRecord*> BuilderRecord::GetSortedUnresolvedDeps()
     const {
   std::vector<const BuilderRecord*> result;
diff --git a/src/gn/builder_record.h b/src/gn/builder_record.h
index 06ff6c3..b3b4aba 100644
--- a/src/gn/builder_record.h
+++ b/src/gn/builder_record.h
@@ -73,6 +73,10 @@
 
   bool can_resolve() const { return item_ && unresolved_count_ == 0; }
 
+  bool can_write() const {
+    return resolved_ && unresolved_validation_count_ == 0;
+  }
+
   // All records this one is depending on. Note that this includes gen_deps for
   // targets, which can have cycles.
   BuilderRecordSet& all_deps() { return all_deps_; }
@@ -97,6 +101,19 @@
     return waiting_on_resolution_;
   }
 
+  // Records that are waiting on this one to be resolved before they can be
+  // written to the ninja file. This is used for "validations" dependencies.
+  BuilderRecordSet& waiting_on_resolution_for_writing() {
+    return waiting_on_resolution_for_writing_;
+  }
+  const BuilderRecordSet& waiting_on_resolution_for_writing() const {
+    return waiting_on_resolution_for_writing_;
+  }
+
+  // Validation dependencies.
+  BuilderRecordSet& validation_deps() { return validation_deps_; }
+  const BuilderRecordSet& validation_deps() const { return validation_deps_; }
+
   void AddDep(BuilderRecord* record);
   void AddGenDep(BuilderRecord* record);
   void AddValidationDep(BuilderRecord* record);
@@ -111,6 +128,11 @@
   // should now be resolved.
   bool OnResolvedDep(const BuilderRecord* dep);
 
+  // Call this method to notify the record that its validation dependency |dep|
+  // was just resolved. This returns true to indicate that the current record
+  // is now ready to be written.
+  bool OnResolvedValidationDep(const BuilderRecord* dep);
+
   // Comparator function used to sort records from their label.
   static bool LabelCompare(const BuilderRecord* a, const BuilderRecord* b) {
     return a->label_ < b->label_;
@@ -125,9 +147,12 @@
   const ParseNode* originally_referenced_from_ = nullptr;
 
   size_t unresolved_count_ = 0;
+  size_t unresolved_validation_count_ = 0;
   BuilderRecordSet all_deps_;
+  BuilderRecordSet validation_deps_;
   BuilderRecordSet waiting_on_resolution_;
   BuilderRecordSet waiting_on_definition_;
+  BuilderRecordSet waiting_on_resolution_for_writing_;
 
   BuilderRecord(const BuilderRecord&) = delete;
   BuilderRecord& operator=(const BuilderRecord&) = delete;
diff --git a/src/gn/builder_unittest.cc b/src/gn/builder_unittest.cc
index da497c7..e53bf29 100644
--- a/src/gn/builder_unittest.cc
+++ b/src/gn/builder_unittest.cc
@@ -431,4 +431,150 @@
   EXPECT_EQ(b_ptr, a_ptr->validations()[0].ptr);
 }
 
+// Tests that validation dependencies block writing until resolved.
+TEST_F(BuilderTest, ValidationsBlockWriting) {
+  DefineToolchain();
+  SourceDir toolchain_dir = settings_.toolchain_label().dir();
+  std::string toolchain_name = settings_.toolchain_label().name();
+
+  Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
+  Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
+  Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
+
+  // Define A. A lists B in validations.
+  auto a = std::make_unique<Target>(&settings_, a_label);
+  a->set_output_type(Target::ACTION);
+  a->validations().push_back(LabelTargetPair(b_label));
+  a->visibility().SetPublic();
+  builder_.ItemDefined(std::move(a));
+
+  // Define B. B depends on C.
+  auto b = std::make_unique<Target>(&settings_, b_label);
+  b->set_output_type(Target::ACTION);
+  b->private_deps().push_back(LabelTargetPair(c_label));
+  b->visibility().SetPublic();
+  builder_.ItemDefined(std::move(b));
+
+  // C is unresolved (waiting on definition).
+  // B is unresolved (waiting on C).
+  // A should be RESOLVED (because B is defined, and validations only wait on
+  // definition for resolution). BUT A should be blocked from WRITING because B
+  // is not resolved.
+
+  BuilderRecord* a_record = builder_.GetRecord(a_label);
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+
+  scheduler().Run();
+
+  EXPECT_TRUE(a_record->resolved());
+  EXPECT_FALSE(b_record->resolved());
+
+  // Check that B knows A is waiting on it for writing.
+  EXPECT_TRUE(b_record->waiting_on_resolution_for_writing().contains(a_record));
+}
+
+// Tests that a target can validate a target that depends on it.
+// A -> validations -> B
+// B -> deps -> A
+TEST_F(BuilderTest, ValidationsWithCycle) {
+  DefineToolchain();
+  SourceDir toolchain_dir = settings_.toolchain_label().dir();
+  std::string toolchain_name = settings_.toolchain_label().name();
+
+  Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
+  Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
+
+  // Define A. A lists B in validations.
+  auto a = std::make_unique<Target>(&settings_, a_label);
+  a->set_output_type(Target::ACTION);
+  a->validations().push_back(LabelTargetPair(b_label));
+  a->visibility().SetPublic();
+  builder_.ItemDefined(std::move(a));
+
+  // Define B. B depends on A.
+  auto b = std::make_unique<Target>(&settings_, b_label);
+  b->set_output_type(Target::ACTION);
+  b->private_deps().push_back(LabelTargetPair(a_label));
+  b->visibility().SetPublic();
+  builder_.ItemDefined(std::move(b));
+
+  BuilderRecord* a_record = builder_.GetRecord(a_label);
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+
+  scheduler().Run();
+
+  // Both should be resolved.
+  EXPECT_TRUE(a_record->resolved());
+  EXPECT_TRUE(b_record->resolved());
+
+  // There should be no errors (cycle detection passed).
+  Err err;
+  EXPECT_TRUE(builder_.CheckForBadItems(&err));
+  EXPECT_FALSE(err.has_error());
+}
+
+// Tests that if a validation resolves, the target does NOT write if it
+// is still waiting on other dependencies to resolve.
+// A -> deps -> B
+// A -> validations -> C
+// Sequence: C resolves. A should NOT write. B resolves. A writes.
+TEST_F(BuilderTest, ValidationsPrematureWrite) {
+  DefineToolchain();
+  SourceDir toolchain_dir = settings_.toolchain_label().dir();
+  std::string toolchain_name = settings_.toolchain_label().name();
+
+  Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
+  Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
+  Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
+
+  // Define A. A depends on B and has validation C.
+  auto a = std::make_unique<Target>(&settings_, a_label);
+  a->set_output_type(Target::ACTION);
+  a->private_deps().push_back(LabelTargetPair(b_label));
+  a->validations().push_back(LabelTargetPair(c_label));
+  a->visibility().SetPublic();
+  builder_.ItemDefined(std::move(a));
+
+  // Define C. C is standalone.
+  auto c = std::make_unique<Target>(&settings_, c_label);
+  c->set_output_type(Target::ACTION);
+  c->visibility().SetPublic();
+  builder_.ItemDefined(std::move(c));
+
+  // Track written targets.
+  std::vector<const BuilderRecord*> written;
+  builder_.set_resolved_and_generated_callback(
+      [&written](const BuilderRecord* record) { written.push_back(record); });
+
+  // C should resolve. A is waiting on B.
+  scheduler().Run();
+
+  BuilderRecord* a_record = builder_.GetRecord(a_label);
+  BuilderRecord* c_record = builder_.GetRecord(c_label);
+
+  EXPECT_TRUE(c_record->resolved());
+  EXPECT_FALSE(a_record->resolved());
+
+  // Only C should be written.
+  ASSERT_EQ(1u, written.size());
+  EXPECT_EQ(c_record, written[0]);
+
+  // Define B.
+  auto b = std::make_unique<Target>(&settings_, b_label);
+  b->set_output_type(Target::ACTION);
+  b->visibility().SetPublic();
+  builder_.ItemDefined(std::move(b));
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+
+  scheduler().Run();
+
+  EXPECT_TRUE(a_record->resolved());
+
+  // Order should be C, B, A.
+  ASSERT_EQ(3u, written.size());
+  EXPECT_EQ(c_record, written[0]);
+  EXPECT_EQ(b_record, written[1]);
+  EXPECT_EQ(a_record, written[2]);
+}
+
 }  // namespace gn_builder_unittest
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 9cba6af..e3c95f3 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -913,6 +913,12 @@
     }
   }
 
+  // Targets with validations must be written to ensure the validations run,
+  // even if they have no other inputs or dependencies.
+  if (!validations_.empty()) {
+    return true;
+  }
+
   if (output_type() == BUNDLE_DATA) {
     return !sources().empty();
   }