Fix metadata collection bug.
A recent CL [1] introduced a subtle bug in metadata collection,
which is only triggered when collecting through a target that
doesn't have its own metadata (but whose dependencies do).
This CL fixes the issue, and adds a new unit-test to detect
a similar issue in the future.
The issue was that the `has_metadata()` check should not have
been introduced, because when this condition is false, it
prevented a call to WalkStep() which actually modifies
`next_walk_keys`. The effect was that metadata walk was cut
too soon.
[1] https://gn-review.googlesource.com/c/gn/+/12401
Bug: chromium/1273069
Change-Id: Iae1eee1e30b6130e93294204d282474233014525
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12560
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: David Turner <digit@google.com>
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 62f17cd..0d225b6 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -1223,8 +1223,11 @@
// Origin is null because this isn't declared anywhere, and should never
// trigger any errors.
next_walk_keys.push_back(Value(nullptr, ""));
- } else if (has_metadata()) {
+ } else {
// Otherwise, we walk this target and collect the appropriate data.
+ // NOTE: Always call WalkStep() even when have_metadata() is false,
+ // because WalkStep() will append to 'next_walk_keys' in this case.
+ // See https://crbug.com/1273069.
if (!metadata().WalkStep(settings()->build_settings(), keys_to_extract,
keys_to_walk, rebase_dir, &next_walk_keys,
¤t_result, err))
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index add6d82..0dcff0c 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -1456,6 +1456,53 @@
EXPECT_EQ(result, expected);
}
+TEST(TargetTest, CollectMetadataWithRecurseHole) {
+ TestWithScope setup;
+
+ TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+ Value a_expected(nullptr, Value::LIST);
+ a_expected.list_value().push_back(Value(nullptr, "foo"));
+ one.metadata().contents().insert(
+ std::pair<std::string_view, Value>("a", a_expected));
+
+ Value b_expected(nullptr, Value::LIST);
+ b_expected.list_value().push_back(Value(nullptr, true));
+ one.metadata().contents().insert(
+ std::pair<std::string_view, Value>("b", b_expected));
+
+ // Target two does not have metadata but depends on three
+ // which does.
+ TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+
+ TestTarget three(setup, "//foo:three", Target::SOURCE_SET);
+ Value a_3_expected(nullptr, Value::LIST);
+ a_3_expected.list_value().push_back(Value(nullptr, "bar"));
+ three.metadata().contents().insert(
+ std::pair<std::string_view, Value>("a", a_3_expected));
+
+ one.public_deps().push_back(LabelTargetPair(&two));
+ two.public_deps().push_back(LabelTargetPair(&three));
+
+ std::vector<std::string> data_keys;
+ data_keys.push_back("a");
+ data_keys.push_back("b");
+
+ std::vector<std::string> walk_keys;
+
+ Err err;
+ std::vector<Value> result;
+ std::set<const Target*> targets;
+ one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+ &err);
+ EXPECT_FALSE(err.has_error());
+
+ std::vector<Value> expected;
+ expected.push_back(Value(nullptr, "bar"));
+ expected.push_back(Value(nullptr, "foo"));
+ expected.push_back(Value(nullptr, true));
+ EXPECT_EQ(result, expected);
+}
+
TEST(TargetTest, CollectMetadataWithBarrier) {
TestWithScope setup;