1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/operators.h"
6
7 #include <stdint.h>
8
9 #include <memory>
10 #include <utility>
11
12 #include "gn/parse_tree.h"
13 #include "gn/pattern.h"
14 #include "gn/test_with_scope.h"
15 #include "util/test/test.h"
16
17 namespace {
18
IsValueIntegerEqualing(const Value & v,int64_t i)19 bool IsValueIntegerEqualing(const Value& v, int64_t i) {
20 if (v.type() != Value::INTEGER)
21 return false;
22 return v.int_value() == i;
23 }
24
IsValueStringEqualing(const Value & v,const char * s)25 bool IsValueStringEqualing(const Value& v, const char* s) {
26 if (v.type() != Value::STRING)
27 return false;
28 return v.string_value() == s;
29 }
30
31 // This parse node is for passing to tests. It returns a canned value for
32 // Execute().
33 class TestParseNode : public ParseNode {
34 public:
TestParseNode(const Value & v)35 TestParseNode(const Value& v) : value_(v) {}
36
Execute(Scope * scope,Err * err) const37 Value Execute(Scope* scope, Err* err) const override { return value_; }
GetRange() const38 LocationRange GetRange() const override { return LocationRange(); }
MakeErrorDescribing(const std::string & msg,const std::string & help) const39 Err MakeErrorDescribing(const std::string& msg,
40 const std::string& help) const override {
41 return Err(this, msg);
42 }
GetJSONNode() const43 base::Value GetJSONNode() const override {
44 return base::Value();
45 }
46
47 private:
48 Value value_;
49 };
50
51 // Sets up a BinaryOpNode for testing.
52 class TestBinaryOpNode : public BinaryOpNode {
53 public:
54 // Input token value string must outlive class.
TestBinaryOpNode(Token::Type op_token_type,const char * op_token_value)55 TestBinaryOpNode(Token::Type op_token_type, const char* op_token_value)
56 : BinaryOpNode(),
57 op_token_ownership_(Location(), op_token_type, op_token_value) {
58 set_op(op_token_ownership_);
59 }
60
SetLeftToValue(const Value & value)61 void SetLeftToValue(const Value& value) {
62 set_left(std::make_unique<TestParseNode>(value));
63 }
64
65 // Sets the left-hand side of the operator to an identifier node, this is
66 // used for testing assignments. Input string must outlive class.
SetLeftToIdentifier(const char * identifier)67 void SetLeftToIdentifier(const char* identifier) {
68 left_identifier_token_ownership_ =
69 Token(Location(), Token::IDENTIFIER, identifier);
70 set_left(
71 std::make_unique<IdentifierNode>(left_identifier_token_ownership_));
72 }
73
SetRightToValue(const Value & value)74 void SetRightToValue(const Value& value) {
75 set_right(std::make_unique<TestParseNode>(value));
76 }
SetRightToListOfValue(const Value & value)77 void SetRightToListOfValue(const Value& value) {
78 Value list(nullptr, Value::LIST);
79 list.list_value().push_back(value);
80 set_right(std::make_unique<TestParseNode>(list));
81 }
SetRightToListOfValue(const Value & value1,const Value & value2)82 void SetRightToListOfValue(const Value& value1, const Value& value2) {
83 Value list(nullptr, Value::LIST);
84 list.list_value().push_back(value1);
85 list.list_value().push_back(value2);
86 set_right(std::make_unique<TestParseNode>(list));
87 }
88
89 private:
90 // The base class takes the Token by reference, this manages the lifetime.
91 Token op_token_ownership_;
92
93 // When setting the left to an identifier, this manages the lifetime of
94 // the identifier token.
95 Token left_identifier_token_ownership_;
96 };
97
98 } // namespace
99
TEST(Operators,SourcesAppend)100 TEST(Operators, SourcesAppend) {
101 Err err;
102 TestWithScope setup;
103
104 // Set up "sources" with an empty list.
105 const char sources[] = "sources";
106 setup.scope()->SetValue(sources, Value(nullptr, Value::LIST), nullptr);
107
108 // Set up the operator.
109 TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
110 node.SetLeftToIdentifier(sources);
111
112 // Set up the filter on the scope to remove everything ending with "rm"
113 std::unique_ptr<PatternList> pattern_list = std::make_unique<PatternList>();
114 pattern_list->Append(Pattern("*rm"));
115 setup.scope()->set_sources_assignment_filter(std::move(pattern_list));
116
117 // Append an integer.
118 node.SetRightToListOfValue(Value(nullptr, static_cast<int64_t>(5)));
119 node.Execute(setup.scope(), &err);
120 EXPECT_FALSE(err.has_error());
121
122 // Append a string that doesn't match the pattern, it should get appended.
123 const char string1[] = "good";
124 node.SetRightToListOfValue(Value(nullptr, string1));
125 node.Execute(setup.scope(), &err);
126 EXPECT_FALSE(err.has_error());
127
128 // Append a string that does match the pattern, it should be a no-op.
129 const char string2[] = "foo-rm";
130 node.SetRightToListOfValue(Value(nullptr, string2));
131 node.Execute(setup.scope(), &err);
132 EXPECT_FALSE(err.has_error());
133
134 // Append a list with the two strings from above.
135 node.SetRightToListOfValue(Value(nullptr, string1), Value(nullptr, string2));
136 node.Execute(setup.scope(), &err);
137 EXPECT_FALSE(err.has_error());
138
139 // The sources variable in the scope should now have: [ 5, "good", "good" ]
140 const Value* value = setup.scope()->GetValue(sources);
141 ASSERT_TRUE(value);
142 ASSERT_EQ(Value::LIST, value->type());
143 ASSERT_EQ(3u, value->list_value().size());
144 EXPECT_TRUE(IsValueIntegerEqualing(value->list_value()[0], 5));
145 EXPECT_TRUE(IsValueStringEqualing(value->list_value()[1], "good"));
146 EXPECT_TRUE(IsValueStringEqualing(value->list_value()[2], "good"));
147 }
148
149 // Note that the SourcesAppend test above tests the basic list + list features,
150 // this test handles the other cases.
TEST(Operators,ListAppend)151 TEST(Operators, ListAppend) {
152 Err err;
153 TestWithScope setup;
154
155 // Set up "foo" with an empty list.
156 const char foo[] = "foo";
157 setup.scope()->SetValue(foo, Value(nullptr, Value::LIST), nullptr);
158
159 // Set up the operator to append to "foo".
160 TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
161 node.SetLeftToIdentifier(foo);
162
163 // Append a list with a list, the result should be a nested list.
164 Value inner_list(nullptr, Value::LIST);
165 inner_list.list_value().push_back(Value(nullptr, static_cast<int64_t>(12)));
166 node.SetRightToListOfValue(inner_list);
167
168 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
169 node.right(), &err);
170 EXPECT_FALSE(err.has_error());
171
172 // Return from the operator should always be "none", it should update the
173 // value only.
174 EXPECT_EQ(Value::NONE, ret.type());
175
176 // The value should be updated with "[ [ 12 ] ]"
177 Value result = *setup.scope()->GetValue(foo);
178 ASSERT_EQ(Value::LIST, result.type());
179 ASSERT_EQ(1u, result.list_value().size());
180 ASSERT_EQ(Value::LIST, result.list_value()[0].type());
181 ASSERT_EQ(1u, result.list_value()[0].list_value().size());
182 ASSERT_EQ(Value::INTEGER, result.list_value()[0].list_value()[0].type());
183 ASSERT_EQ(12, result.list_value()[0].list_value()[0].int_value());
184
185 // Try to append an integer and a string directly (e.g. foo += "hi").
186 // This should fail.
187 const char str_str[] = "\"hi\"";
188 Token str(Location(), Token::STRING, str_str);
189 node.set_right(std::make_unique<LiteralNode>(str));
190 ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
191 EXPECT_TRUE(err.has_error());
192 err = Err();
193
194 node.SetRightToValue(Value(nullptr, static_cast<int64_t>(12)));
195 ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
196 EXPECT_TRUE(err.has_error());
197 }
198
TEST(Operators,ListRemove)199 TEST(Operators, ListRemove) {
200 Err err;
201 TestWithScope setup;
202
203 const char foo_str[] = "foo";
204 const char bar_str[] = "bar";
205 Value test_list(nullptr, Value::LIST);
206 test_list.list_value().push_back(Value(nullptr, foo_str));
207 test_list.list_value().push_back(Value(nullptr, bar_str));
208 test_list.list_value().push_back(Value(nullptr, foo_str));
209
210 // Set up "var" with an the test list.
211 const char var[] = "var";
212 setup.scope()->SetValue(var, test_list, nullptr);
213
214 TestBinaryOpNode node(Token::MINUS_EQUALS, "-=");
215 node.SetLeftToIdentifier(var);
216
217 // Subtract a list consisting of "foo".
218 node.SetRightToListOfValue(Value(nullptr, foo_str));
219 Value result = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
220 node.right(), &err);
221 EXPECT_FALSE(err.has_error());
222
223 // -= returns an empty value to reduce the possibility of writing confusing
224 // cases like foo = bar += 1.
225 EXPECT_EQ(Value::NONE, result.type());
226
227 // The "var" variable should have been updated. Both instances of "foo" are
228 // deleted.
229 const Value* new_value = setup.scope()->GetValue(var);
230 ASSERT_TRUE(new_value);
231 ASSERT_EQ(Value::LIST, new_value->type());
232 ASSERT_EQ(1u, new_value->list_value().size());
233 ASSERT_EQ(Value::STRING, new_value->list_value()[0].type());
234 EXPECT_EQ("bar", new_value->list_value()[0].string_value());
235 }
236
TEST(Operators,ListSubtractWithScope)237 TEST(Operators, ListSubtractWithScope) {
238 Err err;
239 TestWithScope setup;
240
241 Scope* scope_a = new Scope(setup.settings());
242 Value scopeval_a(nullptr, std::unique_ptr<Scope>(scope_a));
243 scope_a->SetValue("a", Value(nullptr, "foo"), nullptr);
244
245 Scope* scope_b = new Scope(setup.settings());
246 Value scopeval_b(nullptr, std::unique_ptr<Scope>(scope_b));
247 scope_b->SetValue("b", Value(nullptr, "bar"), nullptr);
248
249 Value lval(nullptr, Value::LIST);
250 lval.list_value().push_back(scopeval_a);
251 lval.list_value().push_back(scopeval_b);
252
253 Scope* scope_a_other = new Scope(setup.settings());
254 Value scopeval_a_other(nullptr, std::unique_ptr<Scope>(scope_a_other));
255 scope_a_other->SetValue("a", Value(nullptr, "foo"), nullptr);
256
257 Value rval(nullptr, Value::LIST);
258 rval.list_value().push_back(scopeval_a_other);
259
260 TestBinaryOpNode node(Token::MINUS, "-");
261 node.SetLeftToValue(lval);
262 node.SetRightToValue(rval);
263 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
264 node.right(), &err);
265 ASSERT_FALSE(err.has_error());
266 ASSERT_EQ(Value::LIST, ret.type());
267
268 std::vector<Value> expected;
269 Scope* scope_expected = new Scope(setup.settings());
270 Value scopeval_expected(nullptr, std::unique_ptr<Scope>(scope_expected));
271 scope_expected->SetValue("b", Value(nullptr, "bar"), nullptr);
272 expected.push_back(scopeval_expected);
273 EXPECT_EQ(expected, ret.list_value());
274 }
275
TEST(Operators,IntegerAdd)276 TEST(Operators, IntegerAdd) {
277 Err err;
278 TestWithScope setup;
279
280 TestBinaryOpNode node(Token::PLUS, "+");
281 node.SetLeftToValue(Value(nullptr, static_cast<int64_t>(123)));
282 node.SetRightToValue(Value(nullptr, static_cast<int64_t>(456)));
283 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
284 node.right(), &err);
285 ASSERT_FALSE(err.has_error());
286 ASSERT_EQ(Value::INTEGER, ret.type());
287 EXPECT_EQ(579, ret.int_value());
288 }
289
TEST(Operators,IntegerSubtract)290 TEST(Operators, IntegerSubtract) {
291 Err err;
292 TestWithScope setup;
293
294 TestBinaryOpNode node(Token::MINUS, "-");
295 node.SetLeftToValue(Value(nullptr, static_cast<int64_t>(123)));
296 node.SetRightToValue(Value(nullptr, static_cast<int64_t>(456)));
297 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
298 node.right(), &err);
299 ASSERT_FALSE(err.has_error());
300 ASSERT_EQ(Value::INTEGER, ret.type());
301 EXPECT_EQ(-333, ret.int_value());
302 }
303
TEST(Operators,ShortCircuitAnd)304 TEST(Operators, ShortCircuitAnd) {
305 Err err;
306 TestWithScope setup;
307
308 // Set a && operator with the left to false.
309 TestBinaryOpNode node(Token::BOOLEAN_AND, "&&");
310 node.SetLeftToValue(Value(nullptr, false));
311
312 // Set right as foo, but don't define a value for it.
313 const char foo[] = "foo";
314 Token identifier_token(Location(), Token::IDENTIFIER, foo);
315 node.set_right(std::make_unique<IdentifierNode>(identifier_token));
316
317 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
318 node.right(), &err);
319 EXPECT_FALSE(err.has_error());
320 }
321
TEST(Operators,ShortCircuitOr)322 TEST(Operators, ShortCircuitOr) {
323 Err err;
324 TestWithScope setup;
325
326 // Set a || operator with the left to true.
327 TestBinaryOpNode node(Token::BOOLEAN_OR, "||");
328 node.SetLeftToValue(Value(nullptr, true));
329
330 // Set right as foo, but don't define a value for it.
331 const char foo[] = "foo";
332 Token identifier_token(Location(), Token::IDENTIFIER, foo);
333 node.set_right(std::make_unique<IdentifierNode>(identifier_token));
334
335 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
336 node.right(), &err);
337 EXPECT_FALSE(err.has_error());
338 }
339
340 // Overwriting nonempty lists and scopes with other nonempty lists and scopes
341 // should be disallowed.
TEST(Operators,NonemptyOverwriting)342 TEST(Operators, NonemptyOverwriting) {
343 Err err;
344 TestWithScope setup;
345
346 // Set up "foo" with a nonempty list.
347 const char foo[] = "foo";
348 Value old_value(nullptr, Value::LIST);
349 old_value.list_value().push_back(Value(nullptr, "string"));
350 setup.scope()->SetValue(foo, old_value, nullptr);
351
352 TestBinaryOpNode node(Token::EQUAL, "=");
353 node.SetLeftToIdentifier(foo);
354
355 // Assigning a nonempty list should fail.
356 node.SetRightToListOfValue(Value(nullptr, "string"));
357 node.Execute(setup.scope(), &err);
358 ASSERT_TRUE(err.has_error());
359 EXPECT_EQ("Replacing nonempty list.", err.message());
360 err = Err();
361
362 // Assigning an empty list should succeed.
363 node.SetRightToValue(Value(nullptr, Value::LIST));
364 node.Execute(setup.scope(), &err);
365 ASSERT_FALSE(err.has_error());
366 const Value* new_value = setup.scope()->GetValue(foo);
367 ASSERT_TRUE(new_value);
368 ASSERT_EQ(Value::LIST, new_value->type());
369 ASSERT_TRUE(new_value->list_value().empty());
370
371 // Set up "foo" with a nonempty scope.
372 const char bar[] = "bar";
373 old_value = Value(nullptr, std::make_unique<Scope>(setup.settings()));
374 old_value.scope_value()->SetValue(bar, Value(nullptr, "bar"), nullptr);
375 setup.scope()->SetValue(foo, old_value, nullptr);
376
377 // Assigning a nonempty scope should fail (re-use old_value copy).
378 node.SetRightToValue(old_value);
379 node.Execute(setup.scope(), &err);
380 ASSERT_TRUE(err.has_error());
381 EXPECT_EQ("Replacing nonempty scope.", err.message());
382 err = Err();
383
384 // Assigning an empty list should succeed.
385 node.SetRightToValue(
386 Value(nullptr, std::make_unique<Scope>(setup.settings())));
387 node.Execute(setup.scope(), &err);
388 ASSERT_FALSE(err.has_error());
389 new_value = setup.scope()->GetValue(foo);
390 ASSERT_TRUE(new_value);
391 ASSERT_EQ(Value::SCOPE, new_value->type());
392 ASSERT_FALSE(new_value->scope_value()->HasValues(Scope::SEARCH_CURRENT));
393 }
394
395 // Tests this case:
396 // foo = 1
397 // target(...) {
398 // foo += 1
399 //
400 // This should mark the outer "foo" as used, and the inner "foo" as unused.
TEST(Operators,PlusEqualsUsed)401 TEST(Operators, PlusEqualsUsed) {
402 Err err;
403 TestWithScope setup;
404
405 // Outer "foo" definition, it should be unused.
406 const char foo[] = "foo";
407 Value old_value(nullptr, static_cast<int64_t>(1));
408 setup.scope()->SetValue(foo, old_value, nullptr);
409 EXPECT_TRUE(setup.scope()->IsSetButUnused(foo));
410
411 // Nested scope.
412 Scope nested(setup.scope());
413
414 // Run "foo += 1".
415 TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
416 node.SetLeftToIdentifier(foo);
417 node.SetRightToValue(Value(nullptr, static_cast<int64_t>(1)));
418 node.Execute(&nested, &err);
419 ASSERT_FALSE(err.has_error());
420
421 // Outer foo should be used, inner foo should not be.
422 EXPECT_FALSE(setup.scope()->IsSetButUnused(foo));
423 EXPECT_TRUE(nested.IsSetButUnused(foo));
424 }
425