Advertisement
Guest User

Untitled

a guest
Aug 19th, 2019
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.25 KB | None | 0 0
  1. # HG changeset patch
  2. # User hongzhidao <hongzhidao@gmail.com>
  3. # Date 1565146453 14400
  4. # Tue Aug 06 22:54:13 2019 -0400
  5. # Node ID fd41406b655c03551d8681ab07b0ac5154513ea1
  6. # Parent 05fb6e4fdb36b83c35ea66fb0355f46a38231312
  7. Added property getter support for njs_object_property().
  8.  
  9. diff --git a/src/njs_array.c b/src/njs_array.c
  10. --- a/src/njs_array.c
  11. +++ b/src/njs_array.c
  12. @@ -825,18 +825,22 @@ static njs_int_t
  13. njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
  14. njs_index_t unused)
  15. {
  16. - njs_object_prop_t *prop;
  17. + njs_int_t ret;
  18. + njs_value_t value;
  19. njs_lvlhsh_query_t lhq;
  20.  
  21. if (njs_is_object(&args[0])) {
  22. - lhq.key_hash = NJS_JOIN_HASH;
  23. - lhq.key = njs_str_value("join");
  24. -
  25. - prop = njs_object_property(vm, njs_object(&args[0]), &lhq);
  26. -
  27. - if (njs_fast_path(prop != NULL && njs_is_function(&prop->value))) {
  28. - return njs_function_apply(vm, njs_function(&prop->value), args,
  29. - nargs, &vm->retval);
  30. + njs_object_property_init(&lhq, "join", NJS_JOIN_HASH);
  31. +
  32. + ret = njs_object_property(vm, &args[0], &lhq, &value);
  33. +
  34. + if (njs_slow_path(ret == NJS_ERROR)) {
  35. + return ret;
  36. + }
  37. +
  38. + if (njs_is_function(&value)) {
  39. + return njs_function_apply(vm, njs_function(&value), args, nargs,
  40. + &vm->retval);
  41. }
  42. }
  43.  
  44. diff --git a/src/njs_date.c b/src/njs_date.c
  45. --- a/src/njs_date.c
  46. +++ b/src/njs_date.c
  47. @@ -1896,18 +1896,22 @@ static njs_int_t
  48. njs_date_prototype_to_json(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
  49. njs_index_t retval)
  50. {
  51. - njs_object_prop_t *prop;
  52. + njs_int_t ret;
  53. + njs_value_t value;
  54. njs_lvlhsh_query_t lhq;
  55.  
  56. if (njs_is_object(&args[0])) {
  57. - lhq.key_hash = NJS_TO_ISO_STRING_HASH;
  58. - lhq.key = njs_str_value("toISOString");
  59. -
  60. - prop = njs_object_property(vm, njs_object(&args[0]), &lhq);
  61. -
  62. - if (njs_fast_path(prop != NULL && njs_is_function(&prop->value))) {
  63. - return njs_function_apply(vm, njs_function(&prop->value), args,
  64. - nargs, &vm->retval);
  65. + njs_object_property_init(&lhq, "toISOString", NJS_TO_ISO_STRING_HASH);
  66. +
  67. + ret = njs_object_property(vm, &args[0], &lhq, &value);
  68. +
  69. + if (njs_slow_path(ret == NJS_ERROR)) {
  70. + return ret;
  71. + }
  72. +
  73. + if (njs_is_function(&value)) {
  74. + return njs_function_apply(vm, njs_function(&value), args, nargs,
  75. + &vm->retval);
  76. }
  77. }
  78.  
  79. diff --git a/src/njs_error.c b/src/njs_error.c
  80. --- a/src/njs_error.c
  81. +++ b/src/njs_error.c
  82. @@ -619,24 +619,29 @@ njs_error_to_string(njs_vm_t *vm, njs_va
  83. {
  84. size_t size;
  85. u_char *p;
  86. + njs_int_t ret;
  87. njs_str_t name, message;
  88. + njs_value_t value1, value2;
  89. const njs_value_t *name_value, *message_value;
  90. - njs_object_prop_t *prop;
  91. njs_lvlhsh_query_t lhq;
  92.  
  93. static const njs_value_t default_name = njs_string("Error");
  94.  
  95. - lhq.key_hash = NJS_NAME_HASH;
  96. - lhq.key = njs_str_value("name");
  97. - lhq.proto = &njs_object_hash_proto;
  98. + njs_object_property_init(&lhq, "name", NJS_NAME_HASH);
  99.  
  100. - prop = njs_object_property(vm, njs_object(error), &lhq);
  101. + ret = njs_object_property(vm, error, &lhq, &value1);
  102.  
  103. - if (prop != NULL) {
  104. - name_value = &prop->value;
  105. + switch (ret) {
  106. + case NJS_OK:
  107. + name_value = &value1;
  108. + break;
  109.  
  110. - } else {
  111. + case NJS_DECLINED:
  112. name_value = &default_name;
  113. + break;
  114. +
  115. + default:
  116. + return ret;
  117. }
  118.  
  119. njs_string_get(name_value, &name);
  120. @@ -644,13 +649,19 @@ njs_error_to_string(njs_vm_t *vm, njs_va
  121. lhq.key_hash = NJS_MESSAGE_HASH;
  122. lhq.key = njs_str_value("message");
  123.  
  124. - prop = njs_object_property(vm, njs_object(error), &lhq);
  125. + ret = njs_object_property(vm, error, &lhq, &value2);
  126.  
  127. - if (prop != NULL) {
  128. - message_value = &prop->value;
  129. + switch (ret) {
  130. + case NJS_OK:
  131. + message_value = &value2;
  132. + break;
  133.  
  134. - } else {
  135. + case NJS_DECLINED:
  136. message_value = &njs_string_empty;
  137. + break;
  138. +
  139. + default:
  140. + return ret;
  141. }
  142.  
  143. njs_string_get(message_value, &message);
  144. diff --git a/src/njs_json.c b/src/njs_json.c
  145. --- a/src/njs_json.c
  146. +++ b/src/njs_json.c
  147. @@ -1497,19 +1497,19 @@ memory_error:
  148. static njs_function_t *
  149. njs_object_to_json_function(njs_vm_t *vm, njs_value_t *value)
  150. {
  151. - njs_object_prop_t *prop;
  152. + njs_int_t ret;
  153. + njs_value_t retval;
  154. njs_lvlhsh_query_t lhq;
  155.  
  156. - lhq.key_hash = NJS_TO_JSON_HASH;
  157. - lhq.key = njs_str_value("toJSON");
  158. -
  159. - prop = njs_object_property(vm, njs_object(value), &lhq);
  160. -
  161. - if (prop != NULL && njs_is_function(&prop->value)) {
  162. - return njs_function(&prop->value);
  163. + njs_object_property_init(&lhq, "toJSON", NJS_TO_JSON_HASH);
  164. +
  165. + ret = njs_object_property(vm, value, &lhq, &retval);
  166. +
  167. + if (njs_slow_path(ret == NJS_ERROR)) {
  168. + return NULL;
  169. }
  170.  
  171. - return NULL;
  172. + return njs_is_function(&retval) ? njs_function(&retval) : NULL;
  173. }
  174.  
  175.  
  176. diff --git a/src/njs_object.h b/src/njs_object.h
  177. --- a/src/njs_object.h
  178. +++ b/src/njs_object.h
  179. @@ -20,6 +20,14 @@
  180. (!njs_is_data_descriptor(prop) && !njs_is_accessor_descriptor(prop))
  181.  
  182.  
  183. +#define njs_object_property_init(lhq, _key, hash) \
  184. + do { \
  185. + (lhq)->proto = &njs_object_hash_proto; \
  186. + (lhq)->key_hash = hash; \
  187. + (lhq)->key = njs_str_value(_key); \
  188. + } while (0)
  189. +
  190. +
  191. struct njs_object_init_s {
  192. njs_str_t name;
  193. const njs_object_prop_t *properties;
  194. @@ -56,8 +64,8 @@ njs_int_t njs_object_prototype_to_string
  195.  
  196. njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name,
  197. const njs_value_t *value, uint8_t attributes);
  198. -njs_object_prop_t *njs_object_property(njs_vm_t *vm, const njs_object_t *obj,
  199. - njs_lvlhsh_query_t *lhq);
  200. +njs_int_t njs_object_property(njs_vm_t *vm, const njs_value_t *value,
  201. + njs_lvlhsh_query_t *lhq, njs_value_t *retval);
  202. njs_int_t njs_object_prop_define(njs_vm_t *vm, njs_value_t *object,
  203. njs_value_t *name, njs_value_t *value);
  204. njs_int_t njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest,
  205. diff --git a/src/njs_object_prop.c b/src/njs_object_prop.c
  206. --- a/src/njs_object_prop.c
  207. +++ b/src/njs_object_prop.c
  208. @@ -9,7 +9,7 @@
  209.  
  210.  
  211. static njs_object_prop_t *njs_descriptor_prop(njs_vm_t *vm,
  212. - const njs_value_t *name, const njs_object_t *descriptor);
  213. + const njs_value_t *name, const njs_value_t *desc);
  214.  
  215.  
  216. njs_object_prop_t *
  217. @@ -45,32 +45,53 @@ njs_object_prop_alloc(njs_vm_t *vm, cons
  218. }
  219.  
  220.  
  221. -njs_object_prop_t *
  222. -njs_object_property(njs_vm_t *vm, const njs_object_t *object,
  223. - njs_lvlhsh_query_t *lhq)
  224. +njs_int_t
  225. +njs_object_property(njs_vm_t *vm, const njs_value_t *value,
  226. + njs_lvlhsh_query_t *lhq, njs_value_t *retval)
  227. {
  228. - njs_int_t ret;
  229. + njs_int_t ret;
  230. + njs_object_t *object;
  231. + njs_object_prop_t *prop;
  232.  
  233. - lhq->proto = &njs_object_hash_proto;
  234. + object = njs_object(value);
  235.  
  236. do {
  237. ret = njs_lvlhsh_find(&object->hash, lhq);
  238.  
  239. if (njs_fast_path(ret == NJS_OK)) {
  240. - return lhq->value;
  241. + goto found;
  242. }
  243.  
  244. ret = njs_lvlhsh_find(&object->shared_hash, lhq);
  245.  
  246. if (njs_fast_path(ret == NJS_OK)) {
  247. - return lhq->value;
  248. + goto found;
  249. }
  250.  
  251. object = object->__proto__;
  252.  
  253. } while (object != NULL);
  254.  
  255. - return NULL;
  256. + *retval = njs_value_undefined;
  257. +
  258. + return NJS_DECLINED;
  259. +
  260. +found:
  261. +
  262. + prop = lhq->value;
  263. +
  264. + if (njs_is_data_descriptor(prop)) {
  265. + *retval = prop->value;
  266. + return NJS_OK;
  267. + }
  268. +
  269. + if (njs_is_undefined(&prop->getter)) {
  270. + *retval = njs_value_undefined;
  271. + return NJS_OK;
  272. + }
  273. +
  274. + return njs_function_apply(vm, njs_function(&prop->getter), value,
  275. + 1, retval);
  276. }
  277.  
  278.  
  279. @@ -95,7 +116,7 @@ njs_object_prop_define(njs_vm_t *vm, njs
  280. return ret;
  281. }
  282.  
  283. - prop = njs_descriptor_prop(vm, name, njs_object(value));
  284. + prop = njs_descriptor_prop(vm, name, value);
  285. if (njs_slow_path(prop == NULL)) {
  286. return NJS_ERROR;
  287. }
  288. @@ -313,12 +334,13 @@ exception:
  289.  
  290. static njs_object_prop_t *
  291. njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *name,
  292. - const njs_object_t *desc)
  293. + const njs_value_t *desc)
  294. {
  295. + njs_int_t ret;
  296. njs_bool_t data, accessor;
  297. - njs_object_prop_t *prop, *pr;
  298. - const njs_value_t *setter, *getter;
  299. - njs_lvlhsh_query_t pq;
  300. + njs_value_t value;
  301. + njs_object_prop_t *prop;
  302. + njs_lvlhsh_query_t lhq;
  303.  
  304. data = 0;
  305. accessor = 0;
  306. @@ -329,72 +351,103 @@ njs_descriptor_prop(njs_vm_t *vm, const
  307. return NULL;
  308. }
  309.  
  310. - getter = &njs_value_invalid;
  311. - pq.key = njs_str_value("get");
  312. - pq.key_hash = NJS_GET_HASH;
  313. + njs_object_property_init(&lhq, "get", NJS_GET_HASH);
  314.  
  315. - pr = njs_object_property(vm, desc, &pq);
  316. - if (pr != NULL) {
  317. - if (njs_is_defined(&pr->value) && !njs_is_function(&pr->value)) {
  318. + ret = njs_object_property(vm, desc, &lhq, &value);
  319. +
  320. + if (njs_slow_path(ret == NJS_ERROR)) {
  321. + return NULL;
  322. + }
  323. +
  324. + if (ret == NJS_OK) {
  325. + if (njs_is_defined(&value) && !njs_is_function(&value)) {
  326. njs_type_error(vm, "Getter must be a function");
  327. return NULL;
  328. }
  329.  
  330. accessor = 1;
  331. - getter = &pr->value;
  332. + prop->getter = value;
  333. +
  334. + } else {
  335. + /* NJS_DECLINED */
  336. + prop->getter = njs_value_invalid;
  337. }
  338.  
  339. - prop->getter = *getter;
  340. + lhq.key = njs_str_value("set");
  341. + lhq.key_hash = NJS_SET_HASH;
  342.  
  343. - setter = &njs_value_invalid;
  344. - pq.key = njs_str_value("set");
  345. - pq.key_hash = NJS_SET_HASH;
  346. + ret = njs_object_property(vm, desc, &lhq, &value);
  347.  
  348. - pr = njs_object_property(vm, desc, &pq);
  349. - if (pr != NULL) {
  350. - if (njs_is_defined(&pr->value) && !njs_is_function(&pr->value)) {
  351. + if (njs_slow_path(ret == NJS_ERROR)) {
  352. + return NULL;
  353. + }
  354. +
  355. + if (ret == NJS_OK) {
  356. + if (njs_is_defined(&value) && !njs_is_function(&value)) {
  357. njs_type_error(vm, "Setter must be a function");
  358. return NULL;
  359. }
  360.  
  361. accessor = 1;
  362. - setter = &pr->value;
  363. + prop->setter = value;
  364. +
  365. + } else {
  366. + /* NJS_DECLINED */
  367. + prop->setter = njs_value_invalid;
  368. }
  369.  
  370. - prop->setter = *setter;
  371. + lhq.key = njs_str_value("value");
  372. + lhq.key_hash = NJS_VALUE_HASH;
  373.  
  374. - pq.key = njs_str_value("value");
  375. - pq.key_hash = NJS_VALUE_HASH;
  376. + ret = njs_object_property(vm, desc, &lhq, &value);
  377.  
  378. - pr = njs_object_property(vm, desc, &pq);
  379. - if (pr != NULL) {
  380. - data = 1;
  381. - prop->value = pr->value;
  382. + if (njs_slow_path(ret == NJS_ERROR)) {
  383. + return NULL;
  384. }
  385.  
  386. - pq.key = njs_str_value("writable");
  387. - pq.key_hash = NJS_WRITABABLE_HASH;
  388. -
  389. - pr = njs_object_property(vm, desc, &pq);
  390. - if (pr != NULL) {
  391. + if (ret == NJS_OK) {
  392. data = 1;
  393. - prop->writable = njs_is_true(&pr->value);
  394. + prop->value = value;
  395. }
  396.  
  397. - pq.key = njs_str_value("enumerable");
  398. - pq.key_hash = NJS_ENUMERABLE_HASH;
  399. + lhq.key = njs_str_value("writable");
  400. + lhq.key_hash = NJS_WRITABABLE_HASH;
  401.  
  402. - pr = njs_object_property(vm, desc, &pq);
  403. - if (pr != NULL) {
  404. - prop->enumerable = njs_is_true(&pr->value);
  405. + ret = njs_object_property(vm, desc, &lhq, &value);
  406. +
  407. + if (njs_slow_path(ret == NJS_ERROR)) {
  408. + return NULL;
  409. }
  410.  
  411. - pq.key = njs_str_value("configurable");
  412. - pq.key_hash = NJS_CONFIGURABLE_HASH;
  413. + if (ret == NJS_OK) {
  414. + data = 1;
  415. + prop->writable = njs_is_true(&value);
  416. + }
  417.  
  418. - pr = njs_object_property(vm, desc, &pq);
  419. - if (pr != NULL) {
  420. - prop->configurable = njs_is_true(&pr->value);
  421. + lhq.key = njs_str_value("enumerable");
  422. + lhq.key_hash = NJS_ENUMERABLE_HASH;
  423. +
  424. + ret = njs_object_property(vm, desc, &lhq, &value);
  425. +
  426. + if (njs_slow_path(ret == NJS_ERROR)) {
  427. + return NULL;
  428. + }
  429. +
  430. + if (ret == NJS_OK) {
  431. + prop->enumerable = njs_is_true(&value);
  432. + }
  433. +
  434. + lhq.key = njs_str_value("configurable");
  435. + lhq.key_hash = NJS_CONFIGURABLE_HASH;
  436. +
  437. + ret = njs_object_property(vm, desc, &lhq, &value);
  438. +
  439. + if (njs_slow_path(ret == NJS_ERROR)) {
  440. + return NULL;
  441. + }
  442. +
  443. + if (ret == NJS_OK) {
  444. + prop->configurable = njs_is_true(&value);
  445. }
  446.  
  447. if (accessor && data) {
  448. @@ -435,6 +488,29 @@ njs_object_prop_descriptor(njs_vm_t *vm,
  449.  
  450. switch (ret) {
  451. case NJS_OK:
  452. + prop = pq.lhq.value;
  453. +
  454. + switch (prop->type) {
  455. + case NJS_PROPERTY:
  456. + break;
  457. +
  458. + case NJS_PROPERTY_HANDLER:
  459. + pq.scratch = *prop;
  460. + prop = &pq.scratch;
  461. + ret = prop->value.data.u.prop_handler(vm, value, NULL,
  462. + &prop->value);
  463. + if (njs_slow_path(ret != NJS_OK)) {
  464. + return ret;
  465. + }
  466. +
  467. + break;
  468. +
  469. + default:
  470. + njs_type_error(vm, "unexpected property type: %s",
  471. + njs_prop_type_string(prop->type));
  472. + return NJS_ERROR;
  473. + }
  474. +
  475. break;
  476.  
  477. case NJS_DECLINED:
  478. @@ -446,28 +522,6 @@ njs_object_prop_descriptor(njs_vm_t *vm,
  479. return ret;
  480. }
  481.  
  482. - prop = pq.lhq.value;
  483. -
  484. - switch (prop->type) {
  485. - case NJS_PROPERTY:
  486. - break;
  487. -
  488. - case NJS_PROPERTY_HANDLER:
  489. - pq.scratch = *prop;
  490. - prop = &pq.scratch;
  491. - ret = prop->value.data.u.prop_handler(vm, value, NULL, &prop->value);
  492. - if (njs_slow_path(ret != NJS_OK)) {
  493. - return ret;
  494. - }
  495. -
  496. - break;
  497. -
  498. - default:
  499. - njs_type_error(vm, "unexpected property type: %s",
  500. - njs_prop_type_string(prop->type));
  501. - return NJS_ERROR;
  502. - }
  503. -
  504. desc = njs_object_alloc(vm);
  505. if (njs_slow_path(desc == NULL)) {
  506. return NJS_ERROR;
  507. diff --git a/src/njs_value.c b/src/njs_value.c
  508. --- a/src/njs_value.c
  509. +++ b/src/njs_value.c
  510. @@ -122,8 +122,7 @@ njs_value_to_primitive(njs_vm_t *vm, njs
  511. {
  512. njs_int_t ret;
  513. njs_uint_t tries;
  514. - njs_value_t retval;
  515. - njs_object_prop_t *prop;
  516. + njs_value_t method, retval;
  517. njs_lvlhsh_query_t lhq;
  518.  
  519. static const uint32_t hashes[] = {
  520. @@ -144,6 +143,7 @@ njs_value_to_primitive(njs_vm_t *vm, njs
  521. }
  522.  
  523. tries = 0;
  524. + lhq.proto = &njs_object_hash_proto;
  525.  
  526. for ( ;; ) {
  527. ret = NJS_ERROR;
  528. @@ -154,28 +154,27 @@ njs_value_to_primitive(njs_vm_t *vm, njs
  529. lhq.key_hash = hashes[hint];
  530. lhq.key = names[hint];
  531.  
  532. - prop = njs_object_property(vm, njs_object(value), &lhq);
  533. + ret = njs_object_property(vm, value, &lhq, &method);
  534.  
  535. - if (prop == NULL || !njs_is_function(&prop->value)) {
  536. - /* Try the second method. */
  537. - continue;
  538. + if (njs_slow_path(ret == NJS_ERROR)) {
  539. + return ret;
  540. }
  541.  
  542. - ret = njs_function_apply(vm, njs_function(&prop->value), value, 1,
  543. - &retval);
  544. + if (njs_is_function(&method)) {
  545. + ret = njs_function_apply(vm, njs_function(&method), value, 1,
  546. + &retval);
  547.  
  548. - if (njs_fast_path(ret == NJS_OK)) {
  549. + if (njs_slow_path(ret != NJS_OK)) {
  550. + return ret;
  551. + }
  552. +
  553. if (njs_is_primitive(&retval)) {
  554. break;
  555. - }
  556. + }
  557. + }
  558.  
  559. - /* Try the second method. */
  560. - continue;
  561. - }
  562. -
  563. - /* NJS_ERROR */
  564. -
  565. - return ret;
  566. + /* Try the second method. */
  567. + continue;
  568. }
  569.  
  570. njs_type_error(vm, "Cannot convert object to primitive value");
  571. diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
  572. --- a/src/test/njs_unit_test.c
  573. +++ b/src/test/njs_unit_test.c
  574. @@ -9811,6 +9811,43 @@ static njs_unit_test_t njs_test[] =
  575. { njs_str("var arr = [0, 1]; Object.defineProperty(arr, 'length', {value:3}); arr.length"),
  576. njs_str("3") },
  577.  
  578. + { njs_str("Object.defineProperty(Array.prototype, 'toString', { get: function() {return () => 1}});"
  579. + "'a' + []"),
  580. + njs_str("a1") },
  581. +
  582. + { njs_str("Object.defineProperty(Array.prototype, 'toJSON', { get: function() {return () => 1}});"
  583. + "JSON.stringify([])"),
  584. + njs_str("1") },
  585. +
  586. + { njs_str("Object.defineProperty(Array.prototype, 'join', { get: function() {return () => 1}});"
  587. + "([]).toString()"),
  588. + njs_str("1") },
  589. +
  590. + { njs_str("var o = {}, desc = {};"
  591. + "Object.defineProperty(desc, 'get', { get() { return () => 1 } });"
  592. + "Object.defineProperty(o, 'a', desc); o.a"),
  593. + njs_str("1") },
  594. +
  595. + { njs_str("Object.defineProperty(Error.prototype, 'message', { get() {return 'm'}});"
  596. + "Object.defineProperty(Error.prototype, 'name', { get() {return 'n'}});"
  597. + "Error()"),
  598. + njs_str("n: m") },
  599. +
  600. + { njs_str("var o = {}, desc = {};"
  601. + "Object.defineProperty(desc, 'value', { get() { return 'x'}});"
  602. + "Object.defineProperty(o, 'a', desc); o.a"),
  603. + njs_str("x") },
  604. +
  605. + { njs_str("var o = {}, desc = {};"
  606. + "Object.defineProperty(desc, 'value', { get() { return 'x'}});"
  607. + "Object.defineProperty(desc, 'enumerable', { get() { return !NaN}});"
  608. + "Object.defineProperty(desc, 'writable', { get() { return 'x'}});"
  609. + "Object.defineProperty(desc, 'configurable', { get() { return 1}});"
  610. + "Object.defineProperty(o, 'a', desc);"
  611. + "var d = Object.getOwnPropertyDescriptor(o, 'a');"
  612. + "d.enumerable && d.writable && d.configurable"),
  613. + njs_str("true") },
  614. +
  615. { njs_str("Object.defineProperties()"),
  616. njs_str("TypeError: cannot convert undefined argument to object") },
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement