1 // Copyright 2016 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 "extensions/renderer/bindings/argument_spec.h"
6 #include "base/bind.h"
7 #include "base/callback.h"
8 #include "base/macros.h"
9 #include "base/stl_util.h"
10 #include "base/values.h"
11 #include "extensions/renderer/bindings/api_binding_test_util.h"
12 #include "extensions/renderer/bindings/api_invocation_errors.h"
13 #include "extensions/renderer/bindings/api_type_reference_map.h"
14 #include "extensions/renderer/bindings/argument_spec_builder.h"
15 #include "gin/converter.h"
16 #include "gin/dictionary.h"
17 #include "gin/public/isolate_holder.h"
18 #include "gin/test/v8_test.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20 #include "v8/include/v8.h"
21 
22 namespace extensions {
23 
24 using api_errors::IndexError;
25 using api_errors::InvalidType;
26 using api_errors::kTypeBoolean;
27 using api_errors::kTypeDouble;
28 using api_errors::kTypeFunction;
29 using api_errors::kTypeInteger;
30 using api_errors::kTypeList;
31 using api_errors::kTypeNull;
32 using api_errors::kTypeObject;
33 using api_errors::kTypeString;
34 using api_errors::kTypeUndefined;
35 using api_errors::MissingRequiredProperty;
36 
37 using V8Validator = base::OnceCallback<void(v8::Local<v8::Value>)>;
38 
39 class ArgumentSpecUnitTest : public gin::V8Test {
40  protected:
ArgumentSpecUnitTest()41   ArgumentSpecUnitTest()
42       : type_refs_(APITypeReferenceMap::InitializeTypeCallback()) {}
~ArgumentSpecUnitTest()43   ~ArgumentSpecUnitTest() override {}
44 
45   enum class TestResult {
46     PASS,
47     FAIL,
48     THROW,
49   };
50 
51   struct RunTestParams {
RunTestParamsextensions::ArgumentSpecUnitTest::RunTestParams52     RunTestParams(const ArgumentSpec& spec,
53                   base::StringPiece script_source,
54                   TestResult result)
55         : spec(spec), script_source(script_source), expected_result(result) {}
56 
57     const ArgumentSpec& spec;
58     base::StringPiece script_source;
59     TestResult expected_result;
60     base::StringPiece expected_json;
61     base::StringPiece expected_error;
62     base::StringPiece expected_thrown_message;
63     const base::Value* expected_value = nullptr;
64     bool should_convert_to_base = true;
65     bool should_convert_to_v8 = false;
66     V8Validator validate_v8;
67   };
68 
ExpectSuccess(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_json_single_quotes)69   void ExpectSuccess(const ArgumentSpec& spec,
70                      const std::string& script_source,
71                      const std::string& expected_json_single_quotes) {
72     RunTestParams params(spec, script_source, TestResult::PASS);
73     std::string expected_json =
74         ReplaceSingleQuotes(expected_json_single_quotes);
75     params.expected_json = expected_json;
76     RunTest(params);
77   }
78 
ExpectSuccess(const ArgumentSpec & spec,const std::string & script_source,const base::Value & expected_value)79   void ExpectSuccess(const ArgumentSpec& spec,
80                      const std::string& script_source,
81                      const base::Value& expected_value) {
82     RunTestParams params(spec, script_source, TestResult::PASS);
83     params.expected_value = &expected_value;
84     RunTest(params);
85   }
86 
ExpectSuccess(const ArgumentSpec & spec,const std::string & script_source,V8Validator validate_v8)87   void ExpectSuccess(const ArgumentSpec& spec,
88                      const std::string& script_source,
89                      V8Validator validate_v8) {
90     RunTestParams params(spec, script_source, TestResult::PASS);
91     params.should_convert_to_base = false;
92     params.should_convert_to_v8 = true;
93     params.validate_v8 = std::move(validate_v8);
94     RunTest(params);
95   }
96 
ExpectSuccessWithNoConversion(const ArgumentSpec & spec,const std::string & script_source)97   void ExpectSuccessWithNoConversion(const ArgumentSpec& spec,
98                                      const std::string& script_source) {
99     RunTestParams params(spec, script_source, TestResult::PASS);
100     params.should_convert_to_base = false;
101     RunTest(params);
102   }
103 
ExpectFailure(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_error)104   void ExpectFailure(const ArgumentSpec& spec,
105                      const std::string& script_source,
106                      const std::string& expected_error) {
107     RunTestParams params(spec, script_source, TestResult::FAIL);
108     params.expected_error = expected_error;
109     RunTest(params);
110   }
111 
ExpectFailureWithNoConversion(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_error)112   void ExpectFailureWithNoConversion(const ArgumentSpec& spec,
113                                      const std::string& script_source,
114                                      const std::string& expected_error) {
115     RunTestParams params(spec, script_source, TestResult::FAIL);
116     params.should_convert_to_base = false;
117     params.expected_error = expected_error;
118     RunTest(params);
119   }
120 
ExpectThrow(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_thrown_message)121   void ExpectThrow(const ArgumentSpec& spec,
122                    const std::string& script_source,
123                    const std::string& expected_thrown_message) {
124     RunTestParams params(spec, script_source, TestResult::THROW);
125     params.expected_thrown_message = expected_thrown_message;
126     RunTest(params);
127   }
128 
AddTypeRef(const std::string & id,std::unique_ptr<ArgumentSpec> spec)129   void AddTypeRef(const std::string& id, std::unique_ptr<ArgumentSpec> spec) {
130     type_refs_.AddSpec(id, std::move(spec));
131   }
132 
type_refs() const133   const APITypeReferenceMap& type_refs() const { return type_refs_; }
134 
135  private:
136   void RunTest(RunTestParams& params);
137 
138   APITypeReferenceMap type_refs_;
139 
140   DISALLOW_COPY_AND_ASSIGN(ArgumentSpecUnitTest);
141 };
142 
RunTest(RunTestParams & params)143 void ArgumentSpecUnitTest::RunTest(RunTestParams& params) {
144   v8::Isolate* isolate = instance_->isolate();
145   v8::HandleScope handle_scope(instance_->isolate());
146 
147   v8::Local<v8::Context> context =
148       v8::Local<v8::Context>::New(instance_->isolate(), context_);
149   v8::TryCatch try_catch(isolate);
150   v8::Local<v8::Value> val =
151       V8ValueFromScriptSource(context, params.script_source);
152   ASSERT_FALSE(val.IsEmpty()) << params.script_source;
153 
154   std::string error;
155   std::unique_ptr<base::Value> out_value;
156   v8::Local<v8::Value> v8_out_value;
157   bool did_succeed = params.spec.ParseArgument(
158       context, val, type_refs_,
159       params.should_convert_to_base ? &out_value : nullptr,
160       params.should_convert_to_v8 ? &v8_out_value : nullptr, &error);
161   bool should_succeed = params.expected_result == TestResult::PASS;
162   ASSERT_EQ(should_succeed, did_succeed)
163       << params.script_source << ", " << error;
164   ASSERT_EQ(did_succeed && params.should_convert_to_base, !!out_value);
165   ASSERT_EQ(did_succeed && params.should_convert_to_v8,
166             !v8_out_value.IsEmpty());
167   bool should_throw = params.expected_result == TestResult::THROW;
168   ASSERT_EQ(should_throw, try_catch.HasCaught()) << params.script_source;
169 
170   if (!params.expected_error.empty())
171     EXPECT_EQ(params.expected_error, error) << params.script_source;
172 
173   if (should_succeed) {
174     if (params.should_convert_to_base) {
175       ASSERT_TRUE(out_value);
176       if (params.expected_value) {
177         EXPECT_TRUE(params.expected_value->Equals(out_value.get()))
178             << params.script_source;
179       } else {
180         EXPECT_EQ(params.expected_json, ValueToString(*out_value));
181       }
182     }
183     if (params.should_convert_to_v8) {
184       ASSERT_FALSE(v8_out_value.IsEmpty()) << params.script_source;
185       if (!params.validate_v8.is_null())
186         std::move(params.validate_v8).Run(v8_out_value);
187     }
188   } else if (should_throw) {
189     EXPECT_EQ(params.expected_thrown_message,
190               gin::V8ToString(isolate, try_catch.Message()->Get()));
191   }
192 }
193 
TEST_F(ArgumentSpecUnitTest,Test)194 TEST_F(ArgumentSpecUnitTest, Test) {
195   {
196     ArgumentSpec spec(*ValueFromString("{'type': 'integer'}"));
197     ExpectSuccess(spec, "1", "1");
198     ExpectSuccess(spec, "-1", "-1");
199     ExpectSuccess(spec, "0", "0");
200     ExpectSuccess(spec, "0.0", "0");
201     ExpectSuccess(spec, "-0.0", "0");
202     ExpectSuccess(spec, "-0.", "0");
203     ExpectSuccess(spec, "-.0", "0");
204     ExpectSuccess(spec, "-0", "0");
205     ExpectFailure(spec, "undefined", InvalidType(kTypeInteger, kTypeUndefined));
206     ExpectFailure(spec, "null", InvalidType(kTypeInteger, kTypeNull));
207     ExpectFailure(spec, "1.1", InvalidType(kTypeInteger, kTypeDouble));
208     ExpectFailure(spec, "'foo'", InvalidType(kTypeInteger, kTypeString));
209     ExpectFailure(spec, "'1'", InvalidType(kTypeInteger, kTypeString));
210     ExpectFailure(spec, "({})", InvalidType(kTypeInteger, kTypeObject));
211     ExpectFailure(spec, "[1]", InvalidType(kTypeInteger, kTypeList));
212   }
213 
214   {
215     ArgumentSpec spec(*ValueFromString("{'type': 'number'}"));
216     ExpectSuccess(spec, "1", "1.0");
217     ExpectSuccess(spec, "-1", "-1.0");
218     ExpectSuccess(spec, "0", "0.0");
219     ExpectSuccess(spec, "1.1", "1.1");
220     ExpectSuccess(spec, "1.", "1.0");
221     ExpectSuccess(spec, ".1", "0.1");
222     ExpectFailure(spec, "undefined", InvalidType(kTypeDouble, kTypeUndefined));
223     ExpectFailure(spec, "null", InvalidType(kTypeDouble, kTypeNull));
224     ExpectFailure(spec, "'foo'", InvalidType(kTypeDouble, kTypeString));
225     ExpectFailure(spec, "'1.1'", InvalidType(kTypeDouble, kTypeString));
226     ExpectFailure(spec, "({})", InvalidType(kTypeDouble, kTypeObject));
227     ExpectFailure(spec, "[1.1]", InvalidType(kTypeDouble, kTypeList));
228   }
229 
230   {
231     ArgumentSpec spec(*ValueFromString("{'type': 'integer', 'minimum': 1}"));
232     ExpectSuccess(spec, "2", "2");
233     ExpectSuccess(spec, "1", "1");
234     ExpectFailure(spec, "0", api_errors::NumberTooSmall(1));
235     ExpectFailure(spec, "-1", api_errors::NumberTooSmall(1));
236   }
237 
238   {
239     ArgumentSpec spec(*ValueFromString("{'type': 'integer', 'maximum': 10}"));
240     ExpectSuccess(spec, "10", "10");
241     ExpectSuccess(spec, "1", "1");
242     ExpectFailure(spec, "11", api_errors::NumberTooLarge(10));
243   }
244 
245   {
246     ArgumentSpec spec(*ValueFromString("{'type': 'string'}"));
247     ExpectSuccess(spec, "'foo'", "'foo'");
248     ExpectSuccess(spec, "''", "''");
249     ExpectFailure(spec, "1", InvalidType(kTypeString, kTypeInteger));
250     ExpectFailure(spec, "({})", InvalidType(kTypeString, kTypeObject));
251     ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList));
252   }
253 
254   {
255     ArgumentSpec spec(
256         *ValueFromString("{'type': 'string', 'enum': ['foo', 'bar']}"));
257     std::set<std::string> valid_enums = {"foo", "bar"};
258     ExpectSuccess(spec, "'foo'", "'foo'");
259     ExpectSuccess(spec, "'bar'", "'bar'");
260     ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList));
261     ExpectFailure(spec, "'fo'", api_errors::InvalidEnumValue(valid_enums));
262     ExpectFailure(spec, "'foobar'", api_errors::InvalidEnumValue(valid_enums));
263     ExpectFailure(spec, "'baz'", api_errors::InvalidEnumValue(valid_enums));
264     ExpectFailure(spec, "''", api_errors::InvalidEnumValue(valid_enums));
265   }
266 
267   {
268     ArgumentSpec spec(*ValueFromString(
269         "{'type': 'string', 'enum': [{'name': 'foo'}, {'name': 'bar'}]}"));
270     std::set<std::string> valid_enums = {"foo", "bar"};
271     ExpectSuccess(spec, "'foo'", "'foo'");
272     ExpectSuccess(spec, "'bar'", "'bar'");
273     ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList));
274     ExpectFailure(spec, "'fo'", api_errors::InvalidEnumValue(valid_enums));
275     ExpectFailure(spec, "'foobar'", api_errors::InvalidEnumValue(valid_enums));
276     ExpectFailure(spec, "'baz'", api_errors::InvalidEnumValue(valid_enums));
277     ExpectFailure(spec, "''", api_errors::InvalidEnumValue(valid_enums));
278   }
279 
280   {
281     ArgumentSpec spec(*ValueFromString("{'type': 'boolean'}"));
282     ExpectSuccess(spec, "true", "true");
283     ExpectSuccess(spec, "false", "false");
284     ExpectFailure(spec, "1", InvalidType(kTypeBoolean, kTypeInteger));
285     ExpectFailure(spec, "'true'", InvalidType(kTypeBoolean, kTypeString));
286     ExpectFailure(spec, "null", InvalidType(kTypeBoolean, kTypeNull));
287   }
288 
289   {
290     ArgumentSpec spec(
291         *ValueFromString("{'type': 'array', 'items': {'type': 'string'}}"));
292     ExpectSuccess(spec, "[]", "[]");
293     ExpectSuccess(spec, "['foo']", "['foo']");
294     ExpectSuccess(spec, "['foo', 'bar']", "['foo','bar']");
295     ExpectSuccess(spec, "var x = new Array(); x[0] = 'foo'; x;", "['foo']");
296     ExpectFailure(spec, "'foo'", InvalidType(kTypeList, kTypeString));
297     ExpectFailure(spec, "[1, 2]",
298                   IndexError(0u, InvalidType(kTypeString, kTypeInteger)));
299     ExpectFailure(spec, "['foo', 1]",
300                   IndexError(1u, InvalidType(kTypeString, kTypeInteger)));
301     ExpectFailure(spec,
302                   "var x = ['a', 'b', 'c'];"
303                   "x[4] = 'd';"  // x[3] is undefined, violating the spec.
304                   "x;",
305                   IndexError(3u, InvalidType(kTypeString, kTypeUndefined)));
306     ExpectThrow(
307         spec,
308         "var x = [];"
309         "Object.defineProperty("
310         "    x, 0,"
311         "    { get: () => { throw new Error('Badness'); } });"
312         "x;",
313         "Uncaught Error: Badness");
314   }
315 
316   {
317     const char kObjectSpec[] =
318         "{"
319         "  'type': 'object',"
320         "  'properties': {"
321         "    'prop1': {'type': 'string'},"
322         "    'prop2': {'type': 'integer', 'optional': true}"
323         "  }"
324         "}";
325     ArgumentSpec spec(*ValueFromString(kObjectSpec));
326     ExpectSuccess(spec, "({prop1: 'foo', prop2: 2})",
327                   "{'prop1':'foo','prop2':2}");
328     ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
329     ExpectSuccess(spec, "({prop1: 'foo', prop2: null})", "{'prop1':'foo'}");
330     ExpectSuccess(spec, "x = {}; x.prop1 = 'foo'; x;", "{'prop1':'foo'}");
331     ExpectFailure(spec, "({prop1: 'foo', prop2: 'bar'})",
332                   api_errors::PropertyError(
333                       "prop2", InvalidType(kTypeInteger, kTypeString)));
334     ExpectFailure(spec, "({prop2: 2})", MissingRequiredProperty("prop1"));
335     // Unknown properties are not allowed.
336     ExpectFailure(spec, "({prop1: 'foo', prop2: 2, prop3: 'blah'})",
337                   api_errors::UnexpectedProperty("prop3"));
338     // We only consider properties on the object itself, not its prototype
339     // chain.
340     ExpectFailure(spec,
341                   "function X() {}\n"
342                   "X.prototype = { prop1: 'foo' };\n"
343                   "var x = new X();\n"
344                   "x;",
345                   MissingRequiredProperty("prop1"));
346     ExpectFailure(spec,
347                   "function X() {}\n"
348                   "X.prototype = { prop1: 'foo' };\n"
349                   "function Y() { this.__proto__ = X.prototype; }\n"
350                   "var z = new Y();\n"
351                   "z;",
352                   MissingRequiredProperty("prop1"));
353     // Self-referential fun. Currently we don't have to worry about these much
354     // because the spec won't match at some point (and V8ValueConverter has
355     // cycle detection and will fail).
356     ExpectFailure(spec, "x = {}; x.prop1 = x; x;",
357                   api_errors::PropertyError(
358                       "prop1", InvalidType(kTypeString, kTypeObject)));
359     ExpectThrow(
360         spec,
361         "({ get prop1() { throw new Error('Badness'); }});",
362         "Uncaught Error: Badness");
363     ExpectThrow(spec,
364                 "x = {prop1: 'foo'};\n"
365                 "Object.defineProperty(\n"
366                 "    x, 'prop2',\n"
367                 "    {\n"
368                 "      get: () => { throw new Error('Badness'); },\n"
369                 "      enumerable: true,\n"
370                 "});\n"
371                 "x;",
372                 "Uncaught Error: Badness");
373     // By default, properties from Object.defineProperty() aren't enumerable,
374     // so they will be ignored in our matching.
375     ExpectSuccess(spec,
376                   "x = {prop1: 'foo'};\n"
377                   "Object.defineProperty(\n"
378                   "    x, 'prop2',\n"
379                   "    { get: () => { throw new Error('Badness'); } });\n"
380                   "x;",
381                   "{'prop1':'foo'}");
382   }
383 
384   {
385     const char kFunctionSpec[] = "{ 'type': 'function' }";
386     ArgumentSpec spec(*ValueFromString(kFunctionSpec));
387     // Functions are serialized as empty dictionaries.
388     ExpectSuccess(spec, "(function() {})", "{}");
389     ExpectSuccessWithNoConversion(spec, "(function() {})");
390     ExpectSuccessWithNoConversion(spec, "(function(a, b) { a(); b(); })");
391     ExpectSuccessWithNoConversion(spec, "(function(a, b) { a(); b(); })");
392     ExpectFailureWithNoConversion(spec, "({a: function() {}})",
393                                   InvalidType(kTypeFunction, kTypeObject));
394     ExpectFailureWithNoConversion(spec, "([function() {}])",
395                                   InvalidType(kTypeFunction, kTypeList));
396     ExpectFailureWithNoConversion(spec, "1",
397                                   InvalidType(kTypeFunction, kTypeInteger));
398   }
399 
400   {
401     const char kBinarySpec[] = "{ 'type': 'binary' }";
402     ArgumentSpec spec(*ValueFromString(kBinarySpec));
403     // Simple case: empty ArrayBuffer -> empty BinaryValue.
404     ExpectSuccess(spec, "(new ArrayBuffer())",
405                   base::Value(base::Value::Type::BINARY));
406     {
407       // A non-empty (but zero-filled) ArrayBufferView.
408       const char kBuffer[] = {0, 0, 0, 0};
409       std::unique_ptr<base::Value> expected_value =
410           base::Value::CreateWithCopiedBuffer(kBuffer, base::size(kBuffer));
411       ASSERT_TRUE(expected_value);
412       ExpectSuccessWithNoConversion(spec, "(new Int32Array(2))");
413     }
414     {
415       // Actual data.
416       const char kBuffer[] = {'p', 'i', 'n', 'g'};
417       std::unique_ptr<base::Value> expected_value =
418           base::Value::CreateWithCopiedBuffer(kBuffer, base::size(kBuffer));
419       ASSERT_TRUE(expected_value);
420       ExpectSuccess(spec,
421                     "var b = new ArrayBuffer(4);\n"
422                     "var v = new Uint8Array(b);\n"
423                     "var s = 'ping';\n"
424                     "for (var i = 0; i < s.length; ++i)\n"
425                     "  v[i] = s.charCodeAt(i);\n"
426                     "b;",
427                     *expected_value);
428     }
429     ExpectFailure(spec, "1",
430                   InvalidType(api_errors::kTypeBinary, kTypeInteger));
431   }
432   {
433     const char kAnySpec[] = "{ 'type': 'any' }";
434     ArgumentSpec spec(*ValueFromString(kAnySpec));
435     ExpectSuccess(spec, "42", "42");
436     ExpectSuccess(spec, "'foo'", "'foo'");
437     ExpectSuccess(spec, "({prop1:'bar'})", "{'prop1':'bar'}");
438     ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
439     ExpectSuccess(spec, "[1, 'a']", "[1,'a']");
440     ExpectSuccess(spec, "null", base::Value());
441     ExpectSuccess(spec, "({prop1: 'alpha', prop2: null})", "{'prop1':'alpha'}");
442     ExpectSuccess(spec,
443                   "x = {alpha: 'alpha'};\n"
444                   "y = {beta: 'beta', x: x};\n"
445                   "y;",
446                   "{'beta':'beta','x':{'alpha':'alpha'}}");
447     // We don't serialize undefined.
448     // TODO(devlin): This matches current behavior, but should it? Part of the
449     // problem is that base::Values don't differentiate between undefined and
450     // null, which is a potentially important distinction. However, this means
451     // that in serialization of an object {a: 1, foo:undefined}, we lose the
452     // 'foo' property.
453     ExpectFailure(spec, "undefined", api_errors::UnserializableValue());
454 
455     ExpectSuccess(spec, "({prop1: 1, prop2: undefined})", "{'prop1':1}");
456   }
457 }
458 
TEST_F(ArgumentSpecUnitTest,TypeRefsTest)459 TEST_F(ArgumentSpecUnitTest, TypeRefsTest) {
460   const char kObjectType[] =
461       "{"
462       "  'id': 'refObj',"
463       "  'type': 'object',"
464       "  'properties': {"
465       "    'prop1': {'type': 'string'},"
466       "    'prop2': {'type': 'integer', 'optional': true}"
467       "  }"
468       "}";
469   const char kEnumType[] =
470       "{'id': 'refEnum', 'type': 'string', 'enum': ['alpha', 'beta']}";
471   AddTypeRef("refObj",
472              std::make_unique<ArgumentSpec>(*ValueFromString(kObjectType)));
473   AddTypeRef("refEnum",
474              std::make_unique<ArgumentSpec>(*ValueFromString(kEnumType)));
475   std::set<std::string> valid_enums = {"alpha", "beta"};
476 
477   {
478     const char kObjectWithRefEnumSpec[] =
479         "{"
480         "  'name': 'objWithRefEnum',"
481         "  'type': 'object',"
482         "  'properties': {"
483         "    'e': {'$ref': 'refEnum'},"
484         "    'sub': {'type': 'integer'}"
485         "  }"
486         "}";
487     ArgumentSpec spec(*ValueFromString(kObjectWithRefEnumSpec));
488     ExpectSuccess(spec, "({e: 'alpha', sub: 1})", "{'e':'alpha','sub':1}");
489     ExpectSuccess(spec, "({e: 'beta', sub: 1})", "{'e':'beta','sub':1}");
490     ExpectFailure(spec, "({e: 'gamma', sub: 1})",
491                   api_errors::PropertyError(
492                       "e", api_errors::InvalidEnumValue(valid_enums)));
493     ExpectFailure(spec, "({e: 'alpha'})", MissingRequiredProperty("sub"));
494   }
495 
496   {
497     const char kObjectWithRefObjectSpec[] =
498         "{"
499         "  'name': 'objWithRefObject',"
500         "  'type': 'object',"
501         "  'properties': {"
502         "    'o': {'$ref': 'refObj'}"
503         "  }"
504         "}";
505     ArgumentSpec spec(*ValueFromString(kObjectWithRefObjectSpec));
506     ExpectSuccess(spec, "({o: {prop1: 'foo'}})", "{'o':{'prop1':'foo'}}");
507     ExpectSuccess(spec, "({o: {prop1: 'foo', prop2: 2}})",
508                   "{'o':{'prop1':'foo','prop2':2}}");
509     ExpectFailure(
510         spec, "({o: {prop1: 1}})",
511         api_errors::PropertyError(
512             "o", api_errors::PropertyError(
513                      "prop1", InvalidType(kTypeString, kTypeInteger))));
514   }
515 
516   {
517     const char kRefEnumListSpec[] =
518         "{'type': 'array', 'items': {'$ref': 'refEnum'}}";
519     ArgumentSpec spec(*ValueFromString(kRefEnumListSpec));
520     ExpectSuccess(spec, "['alpha']", "['alpha']");
521     ExpectSuccess(spec, "['alpha', 'alpha']", "['alpha','alpha']");
522     ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']");
523     ExpectFailure(spec, "['alpha', 'beta', 'gamma']",
524                   IndexError(2u, api_errors::InvalidEnumValue(valid_enums)));
525   }
526 }
527 
TEST_F(ArgumentSpecUnitTest,TypeChoicesTest)528 TEST_F(ArgumentSpecUnitTest, TypeChoicesTest) {
529   {
530     const char kSimpleChoices[] =
531         "{'choices': [{'type': 'string'}, {'type': 'integer'}]}";
532     ArgumentSpec spec(*ValueFromString(kSimpleChoices));
533     ExpectSuccess(spec, "'alpha'", "'alpha'");
534     ExpectSuccess(spec, "42", "42");
535     const char kChoicesType[] = "[string|integer]";
536     ExpectFailure(spec, "true", InvalidType(kChoicesType, kTypeBoolean));
537   }
538 
539   {
540     const char kComplexChoices[] =
541         "{"
542         "  'choices': ["
543         "    {'type': 'array', 'items': {'type': 'string'}},"
544         "    {'type': 'object', 'properties': {'prop1': {'type': 'string'}}}"
545         "  ]"
546         "}";
547     ArgumentSpec spec(*ValueFromString(kComplexChoices));
548     ExpectSuccess(spec, "['alpha']", "['alpha']");
549     ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']");
550     ExpectSuccess(spec, "({prop1: 'alpha'})", "{'prop1':'alpha'}");
551 
552     const char kChoicesType[] = "[array|object]";
553     ExpectFailure(spec, "({prop1: 1})", api_errors::InvalidChoice());
554     ExpectFailure(spec, "'alpha'", InvalidType(kChoicesType, kTypeString));
555     ExpectFailure(spec, "42", InvalidType(kChoicesType, kTypeInteger));
556   }
557 }
558 
TEST_F(ArgumentSpecUnitTest,AdditionalPropertiesTest)559 TEST_F(ArgumentSpecUnitTest, AdditionalPropertiesTest) {
560   {
561     const char kOnlyAnyAdditionalProperties[] =
562         "{"
563         "  'type': 'object',"
564         "  'additionalProperties': {'type': 'any'}"
565         "}";
566     ArgumentSpec spec(*ValueFromString(kOnlyAnyAdditionalProperties));
567     ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})",
568                   "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}");
569     ExpectSuccess(spec, "({})", "{}");
570     // Test some crazy keys.
571     ExpectSuccess(spec,
572                   "var x = {};\n"
573                   "var y = {prop1: 'alpha'};\n"
574                   "y[42] = 'beta';\n"
575                   "y[x] = 'gamma';\n"
576                   "y[undefined] = 'delta';\n"
577                   "y;",
578                   "{'42':'beta','[object Object]':'gamma','prop1':'alpha',"
579                   "'undefined':'delta'}");
580     // We (typically*, see "Fun case" below) don't serialize properties on an
581     // object prototype.
582     ExpectSuccess(spec,
583                   "({\n"
584                   "  __proto__: {protoProp: 'proto'},\n"
585                   "  instanceProp: 'instance'\n"
586                   "})",
587                   "{'instanceProp':'instance'}");
588     // Fun case: Remove a property as a result of getting another. Currently,
589     // we don't check each property with HasOwnProperty() during iteration, so
590     // Fun case: Remove a property as a result of getting another. Currently,
591     // we don't check each property with HasOwnProperty() during iteration, so
592     // we still try to serialize it. But we don't serialize undefined, so in the
593     // case of the property not being defined on the prototype, this works as
594     // expected.
595     ExpectSuccess(spec,
596                   "var x = {};\n"
597                   "Object.defineProperty(\n"
598                   "    x, 'alpha',\n"
599                   "    {\n"
600                   "      enumerable: true,\n"
601                   "      get: () => { delete x.omega; return 'alpha'; }\n"
602                   "    });\n"
603                   "x.omega = 'omega';\n"
604                   "x;",
605                   "{'alpha':'alpha'}");
606     // Fun case continued: If an object removes the property, and the property
607     // *is* present on the prototype, then we serialize the value from the
608     // prototype. This is inconsistent, but only manifests scripts are doing
609     // crazy things (and is still safe).
610     // TODO(devlin): We *could* add a HasOwnProperty() check, in which case
611     // the result of this call should be {'alpha':'alpha'}.
612     ExpectSuccess(spec,
613                   "var x = {\n"
614                   "  __proto__: { omega: 'different omega' }\n"
615                   "};\n"
616                   "Object.defineProperty(\n"
617                   "    x, 'alpha',\n"
618                   "    {\n"
619                   "      enumerable: true,\n"
620                   "      get: () => { delete x.omega; return 'alpha'; }\n"
621                   "    });\n"
622                   "x.omega = 'omega';\n"
623                   "x;",
624                   "{'alpha':'alpha','omega':'different omega'}");
625   }
626   {
627     const char kPropertiesAndAnyAdditionalProperties[] =
628         "{"
629         "  'type': 'object',"
630         "  'properties': {"
631         "    'prop1': {'type': 'string'}"
632         "  },"
633         "  'additionalProperties': {'type': 'any'}"
634         "}";
635     ArgumentSpec spec(*ValueFromString(kPropertiesAndAnyAdditionalProperties));
636     ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})",
637                   "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}");
638     // Additional properties are optional.
639     ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
640     ExpectFailure(spec, "({prop2: 42, prop3: {foo: 'bar'}})",
641                   MissingRequiredProperty("prop1"));
642     ExpectFailure(spec, "({prop1: 42})",
643                   api_errors::PropertyError(
644                       "prop1", InvalidType(kTypeString, kTypeInteger)));
645   }
646   {
647     const char kTypedAdditionalProperties[] =
648         "{"
649         "  'type': 'object',"
650         "  'additionalProperties': {'type': 'string'}"
651         "}";
652     ArgumentSpec spec(*ValueFromString(kTypedAdditionalProperties));
653     ExpectSuccess(spec, "({prop1: 'alpha', prop2: 'beta', prop3: 'gamma'})",
654                   "{'prop1':'alpha','prop2':'beta','prop3':'gamma'}");
655     ExpectFailure(spec, "({prop1: 'alpha', prop2: 42})",
656                   api_errors::PropertyError(
657                       "prop2", InvalidType(kTypeString, kTypeInteger)));
658   }
659 }
660 
TEST_F(ArgumentSpecUnitTest,InstanceOfTest)661 TEST_F(ArgumentSpecUnitTest, InstanceOfTest) {
662   {
663     const char kInstanceOfRegExp[] =
664         "{"
665         "  'type': 'object',"
666         "  'isInstanceOf': 'RegExp'"
667         "}";
668     ArgumentSpec spec(*ValueFromString(kInstanceOfRegExp));
669     ExpectSuccess(spec, "(new RegExp())", "{}");
670     ExpectSuccess(spec, "({ __proto__: RegExp.prototype })", "{}");
671     ExpectSuccess(spec,
672                   "(function() {\n"
673                   "  function subRegExp() {}\n"
674                   "  subRegExp.prototype = { __proto__: RegExp.prototype };\n"
675                   "  return new subRegExp();\n"
676                   "})()",
677                   "{}");
678     ExpectSuccess(spec,
679                   "(function() {\n"
680                   "  function RegExp() {}\n"
681                   "  return new RegExp();\n"
682                   "})()",
683                   "{}");
684     ExpectFailure(spec, "({})", api_errors::NotAnInstance("RegExp"));
685     ExpectFailure(spec, "('')", InvalidType("RegExp", kTypeString));
686     ExpectFailure(spec, "('.*')", InvalidType("RegExp", kTypeString));
687     ExpectFailure(spec, "({ __proto__: Date.prototype })",
688                   api_errors::NotAnInstance("RegExp"));
689   }
690 
691   {
692     const char kInstanceOfCustomClass[] =
693         "{"
694         "  'type': 'object',"
695         "  'isInstanceOf': 'customClass'"
696         "}";
697     ArgumentSpec spec(*ValueFromString(kInstanceOfCustomClass));
698     ExpectSuccess(spec,
699                   "(function() {\n"
700                   "  function customClass() {}\n"
701                   "  return new customClass();\n"
702                   "})()",
703                   "{}");
704     ExpectSuccess(spec,
705                   "(function() {\n"
706                   "  function customClass() {}\n"
707                   "  function otherClass() {}\n"
708                   "  otherClass.prototype = \n"
709                   "      { __proto__: customClass.prototype };\n"
710                   "  return new otherClass();\n"
711                   "})()",
712                   "{}");
713     ExpectFailure(spec, "({})", api_errors::NotAnInstance("customClass"));
714     ExpectFailure(spec,
715                   "(function() {\n"
716                   "  function otherClass() {}\n"
717                   "  return new otherClass();\n"
718                   "})()",
719                   api_errors::NotAnInstance("customClass"));
720   }
721 }
722 
TEST_F(ArgumentSpecUnitTest,MinAndMaxLengths)723 TEST_F(ArgumentSpecUnitTest, MinAndMaxLengths) {
724   {
725     const char kMinLengthString[] = "{'type': 'string', 'minLength': 3}";
726     ArgumentSpec spec(*ValueFromString(kMinLengthString));
727     ExpectSuccess(spec, "'aaa'", "'aaa'");
728     ExpectSuccess(spec, "'aaaa'", "'aaaa'");
729     ExpectFailure(spec, "'aa'", api_errors::TooFewStringChars(3, 2));
730     ExpectFailure(spec, "''", api_errors::TooFewStringChars(3, 0));
731   }
732 
733   {
734     const char kMaxLengthString[] = "{'type': 'string', 'maxLength': 3}";
735     ArgumentSpec spec(*ValueFromString(kMaxLengthString));
736     ExpectSuccess(spec, "'aaa'", "'aaa'");
737     ExpectSuccess(spec, "'aa'", "'aa'");
738     ExpectSuccess(spec, "''", "''");
739     ExpectFailure(spec, "'aaaa'", api_errors::TooManyStringChars(3, 4));
740   }
741 
742   {
743     const char kMinLengthArray[] =
744         "{'type': 'array', 'items': {'type': 'integer'}, 'minItems': 3}";
745     ArgumentSpec spec(*ValueFromString(kMinLengthArray));
746     ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
747     ExpectSuccess(spec, "[1, 2, 3, 4]", "[1,2,3,4]");
748     ExpectFailure(spec, "[1, 2]", api_errors::TooFewArrayItems(3, 2));
749     ExpectFailure(spec, "[]", api_errors::TooFewArrayItems(3, 0));
750   }
751 
752   {
753     const char kMaxLengthArray[] =
754         "{'type': 'array', 'items': {'type': 'integer'}, 'maxItems': 3}";
755     ArgumentSpec spec(*ValueFromString(kMaxLengthArray));
756     ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
757     ExpectSuccess(spec, "[1, 2]", "[1,2]");
758     ExpectSuccess(spec, "[]", "[]");
759     ExpectFailure(spec, "[1, 2, 3, 4]", api_errors::TooManyArrayItems(3, 4));
760   }
761 }
762 
TEST_F(ArgumentSpecUnitTest,PreserveNull)763 TEST_F(ArgumentSpecUnitTest, PreserveNull) {
764   {
765     const char kObjectSpec[] =
766         "{"
767         "  'type': 'object',"
768         "  'additionalProperties': {'type': 'any'},"
769         "  'preserveNull': true"
770         "}";
771     ArgumentSpec spec(*ValueFromString(kObjectSpec));
772     ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}");
773     // Subproperties shouldn't preserve null (if not specified).
774     ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})",
775                   "{'prop':{'subprop1':'foo'}}");
776   }
777 
778   {
779     const char kObjectSpec[] =
780         "{"
781         "  'type': 'object',"
782         "  'additionalProperties': {'type': 'any', 'preserveNull': true},"
783         "  'preserveNull': true"
784         "}";
785     ArgumentSpec spec(*ValueFromString(kObjectSpec));
786     ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}");
787     // Here, subproperties should preserve null.
788     ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})",
789                   "{'prop':{'subprop1':'foo','subprop2':null}}");
790   }
791 
792   {
793     const char kObjectSpec[] =
794         "{"
795         "  'type': 'object',"
796         "  'properties': {'prop1': {'type': 'string', 'optional': true}},"
797         "  'preserveNull': true"
798         "}";
799     ArgumentSpec spec(*ValueFromString(kObjectSpec));
800     ExpectSuccess(spec, "({})", "{}");
801     ExpectSuccess(spec, "({prop1: null})", "{'prop1':null}");
802     ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
803     // Undefined should not be preserved.
804     ExpectSuccess(spec, "({prop1: undefined})", "{}");
805     // preserveNull shouldn't affect normal parsing restrictions.
806     ExpectFailure(spec, "({prop1: 1})",
807                   api_errors::PropertyError(
808                       "prop1", InvalidType(kTypeString, kTypeInteger)));
809   }
810 }
811 
TEST_F(ArgumentSpecUnitTest,NaNFun)812 TEST_F(ArgumentSpecUnitTest, NaNFun) {
813   {
814     const char kAnySpec[] = "{'type': 'any'}";
815     ArgumentSpec spec(*ValueFromString(kAnySpec));
816     ExpectFailure(spec, "NaN", api_errors::UnserializableValue());
817   }
818 
819   {
820     const char kObjectWithAnyPropertiesSpec[] =
821         "{'type': 'object', 'additionalProperties': {'type': 'any'}}";
822     ArgumentSpec spec(*ValueFromString(kObjectWithAnyPropertiesSpec));
823     ExpectSuccess(spec, "({foo: NaN, bar: 'baz'})", "{'bar':'baz'}");
824   }
825 }
826 
TEST_F(ArgumentSpecUnitTest,GetTypeName)827 TEST_F(ArgumentSpecUnitTest, GetTypeName) {
828   struct {
829     ArgumentType type;
830     const char* expected_type_name;
831   } simple_cases[] = {
832       {ArgumentType::BOOLEAN, kTypeBoolean},
833       {ArgumentType::INTEGER, kTypeInteger},
834       {ArgumentType::OBJECT, kTypeObject},
835       {ArgumentType::LIST, kTypeList},
836       {ArgumentType::BINARY, api_errors::kTypeBinary},
837       {ArgumentType::FUNCTION, kTypeFunction},
838       {ArgumentType::ANY, api_errors::kTypeAny},
839   };
840 
841   for (const auto& test_case : simple_cases) {
842     ArgumentSpec spec(test_case.type);
843     EXPECT_EQ(test_case.expected_type_name, spec.GetTypeName());
844   }
845 
846   {
847     const char kRefName[] = "someRef";
848     ArgumentSpec ref_spec(ArgumentType::REF);
849     ref_spec.set_ref(kRefName);
850     EXPECT_EQ(kRefName, ref_spec.GetTypeName());
851   }
852 
853   {
854     std::vector<std::unique_ptr<ArgumentSpec>> choices;
855     choices.push_back(std::make_unique<ArgumentSpec>(ArgumentType::INTEGER));
856     choices.push_back(std::make_unique<ArgumentSpec>(ArgumentType::STRING));
857     ArgumentSpec choices_spec(ArgumentType::CHOICES);
858     choices_spec.set_choices(std::move(choices));
859     EXPECT_EQ("[integer|string]", choices_spec.GetTypeName());
860   }
861 }
862 
TEST_F(ArgumentSpecUnitTest,V8Conversion)863 TEST_F(ArgumentSpecUnitTest, V8Conversion) {
864   {
865     // Sanity check: a simple conversion.
866     ArgumentSpec spec(ArgumentType::INTEGER);
867     ExpectSuccess(spec, "1", base::BindOnce([](v8::Local<v8::Value> value) {
868                     ASSERT_TRUE(value->IsInt32());
869                     EXPECT_EQ(1, value.As<v8::Int32>()->Value());
870                   }));
871     // The conversion should handle the -0 value (which is considered an
872     // integer but stored in v8 has a number) by converting it to a 0 integer.
873     ExpectSuccess(spec, "-0", base::BindOnce([](v8::Local<v8::Value> value) {
874                     ASSERT_TRUE(value->IsInt32());
875                     EXPECT_EQ(0, value.As<v8::Int32>()->Value());
876                   }));
877   }
878 
879   {
880     std::unique_ptr<ArgumentSpec> prop =
881         ArgumentSpecBuilder(ArgumentType::STRING, "str").Build();
882     std::unique_ptr<ArgumentSpec> spec =
883         ArgumentSpecBuilder(ArgumentType::OBJECT, "obj")
884             .AddProperty("str", std::move(prop))
885             .Build();
886     // The conversion to a v8 value should handle tricky cases like this, where
887     // a subtle getter would invalidate expectations if the value was passed
888     // directly. Since the conversion creates a new object (free of any sneaky
889     // getters), we don't need to repeatedly check for types.
890     constexpr char kTrickyInterceptors[] = R"(
891         ({
892           get str() {
893             if (this.checkedOnce)
894               return 42;
895             this.checkedOnce = true;
896             return 'a string';
897           }
898          }))";
899     ExpectSuccess(*spec, kTrickyInterceptors,
900                   base::BindOnce([](v8::Local<v8::Value> value) {
901                     ASSERT_TRUE(value->IsObject());
902                     v8::Local<v8::Object> object = value.As<v8::Object>();
903                     v8::Local<v8::Context> context = object->CreationContext();
904                     gin::Dictionary dict(context->GetIsolate(), object);
905                     std::string result;
906                     ASSERT_TRUE(dict.Get("str", &result));
907                     EXPECT_EQ("a string", result);
908                     ASSERT_TRUE(dict.Get("str", &result));
909                     // The value should remain constant (even though there's a
910                     // subtle getter).
911                     EXPECT_EQ("a string", result);
912                   }));
913   }
914 
915   {
916     std::unique_ptr<ArgumentSpec> prop =
917         ArgumentSpecBuilder(ArgumentType::STRING, "str").MakeOptional().Build();
918     std::unique_ptr<ArgumentSpec> spec =
919         ArgumentSpecBuilder(ArgumentType::OBJECT, "obj")
920             .AddProperty("str", std::move(prop))
921             .Build();
922     // The conversion to a v8 value should also protect set an undefined value
923     // on the result value for any absent optional properties. This protects
924     // against cases where an Object.prototype getter would be invoked when a
925     // handler tried to check the value of an argument.
926     constexpr const char kMessWithObjectPrototype[] =
927         R"((function() {
928              Object.defineProperty(
929                  Object.prototype, 'str',
930                  { get() { throw new Error('tricked!'); } });
931            }))";
932     v8::HandleScope handle_scope(instance_->isolate());
933     v8::Local<v8::Context> context =
934         v8::Local<v8::Context>::New(instance_->isolate(), context_);
935     v8::Local<v8::Function> mess_with_proto =
936         FunctionFromString(context, kMessWithObjectPrototype);
937     RunFunction(mess_with_proto, context, 0, nullptr);
938     ExpectSuccess(*spec, "({})", base::BindOnce([](v8::Local<v8::Value> value) {
939       ASSERT_TRUE(value->IsObject());
940       v8::Local<v8::Object> object = value.As<v8::Object>();
941       v8::Local<v8::Context> context = object->CreationContext();
942       // We expect a null prototype to ensure we avoid tricky getters/setters on
943       // the Object prototype.
944       EXPECT_TRUE(object->GetPrototype()->IsNull());
945       gin::Dictionary dict(context->GetIsolate(), object);
946       v8::Local<v8::Value> result;
947       ASSERT_TRUE(dict.Get("str", &result));
948       EXPECT_TRUE(result->IsUndefined());
949     }));
950   }
951 
952   {
953     std::unique_ptr<ArgumentSpec> prop =
954         ArgumentSpecBuilder(ArgumentType::STRING, "prop")
955             .MakeOptional()
956             .Build();
957     std::unique_ptr<ArgumentSpec> preserve_null_spec =
958         ArgumentSpecBuilder(ArgumentType::OBJECT, "spec")
959             .AddProperty("prop", std::move(prop))
960             .PreserveNull()
961             .Build();
962     ExpectSuccess(
963         *preserve_null_spec, "({prop: null})",
964         base::BindOnce([](v8::Local<v8::Value> value) {
965           ASSERT_TRUE(value->IsObject());
966           v8::Local<v8::Object> object = value.As<v8::Object>();
967           v8::Local<v8::Context> context = object->CreationContext();
968           v8::Local<v8::Value> prop =
969               object
970                   ->Get(context, gin::StringToV8(context->GetIsolate(), "prop"))
971                   .ToLocalChecked();
972           EXPECT_TRUE(prop->IsNull());
973         }));
974   }
975 }
976 
977 // Certain argument types (any, binary, and function) will be passed through
978 // directly to the v8 arguments because we won't be able to parse them properly.
TEST_F(ArgumentSpecUnitTest,TestV8ValuePassedThrough)979 TEST_F(ArgumentSpecUnitTest, TestV8ValuePassedThrough) {
980   v8::Isolate* isolate = instance_->isolate();
981   v8::HandleScope handle_scope(instance_->isolate());
982 
983   v8::Local<v8::Context> context =
984       v8::Local<v8::Context>::New(instance_->isolate(), context_);
985   v8::TryCatch try_catch(isolate);
986 
987   auto test_is_same_value = [this, context](const ArgumentSpec& spec,
988                                             const char* value_str) {
989     SCOPED_TRACE(value_str);
990     v8::Local<v8::Value> value_in = V8ValueFromScriptSource(context, value_str);
991     v8::Local<v8::Value> value_out;
992     std::string error;
993     ASSERT_TRUE(spec.ParseArgument(context, value_in, type_refs(), nullptr,
994                                    &value_out, &error));
995     ASSERT_FALSE(value_out.IsEmpty());
996     EXPECT_EQ(value_in, value_out);
997   };
998 
999   // Functions are passed directly because we reply directly to the passed-in
1000   // function.
1001   test_is_same_value(ArgumentSpec(ArgumentType::FUNCTION), "(function() {})");
1002   // 'Any' and 'Binary' arguments are passed directly because we can't know if
1003   // we can copy them safely, accurately, and efficiently.
1004   test_is_same_value(ArgumentSpec(ArgumentType::ANY), "({foo: 'bar'})");
1005   test_is_same_value(ArgumentSpec(ArgumentType::BINARY), "(new ArrayBuffer())");
1006   // See comments in ArgumentSpec::ParseArgumentToObject() for why these are
1007   // passed directly.
1008   {
1009     std::unique_ptr<ArgumentSpec> instance_of_spec =
1010         ArgumentSpecBuilder(ArgumentType::OBJECT, "instance_of")
1011             .SetInstanceOf("RegExp")
1012             .Build();
1013     test_is_same_value(*instance_of_spec, "(new RegExp('hi'))");
1014   }
1015   {
1016     std::unique_ptr<ArgumentSpec> additional_properties_spec =
1017         ArgumentSpecBuilder(ArgumentType::OBJECT, "additional props")
1018             .SetAdditionalProperties(
1019                 std::make_unique<ArgumentSpec>(ArgumentType::ANY))
1020             .Build();
1021     test_is_same_value(*additional_properties_spec, "({foo: 'bar'})");
1022   }
1023 }
1024 
1025 }  // namespace extensions
1026