GlobalObject.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. using System.Buffers;
  2. using System.Globalization;
  3. using System.Runtime.CompilerServices;
  4. using System.Text;
  5. using Jint.Extensions;
  6. using Jint.Native.Object;
  7. using Jint.Native.String;
  8. using Jint.Runtime;
  9. using Jint.Runtime.Descriptors;
  10. namespace Jint.Native.Global;
  11. public sealed partial class GlobalObject : ObjectInstance
  12. {
  13. private readonly Realm _realm;
  14. private readonly StringBuilder _stringBuilder = new();
  15. internal GlobalObject(
  16. Engine engine,
  17. Realm realm) : base(engine, ObjectClass.Object, InternalTypes.Object | InternalTypes.PlainObject)
  18. {
  19. _realm = realm;
  20. }
  21. private JsValue ToStringString(JsValue thisObject, JsCallArguments arguments)
  22. {
  23. return _realm.Intrinsics.Object.PrototypeObject.ToObjectString(thisObject, Arguments.Empty);
  24. }
  25. /// <summary>
  26. /// https://tc39.es/ecma262/#sec-parseint-string-radix
  27. /// </summary>
  28. internal static JsValue ParseInt(JsValue thisObject, JsCallArguments arguments)
  29. {
  30. var inputString = TypeConverter.ToString(arguments.At(0));
  31. var trimmed = StringPrototype.TrimEx(inputString);
  32. var s = trimmed.AsSpan();
  33. var radix = arguments.Length > 1 ? TypeConverter.ToInt32(arguments[1]) : 0;
  34. var hexStart = s.Length > 1 && trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
  35. var stripPrefix = true;
  36. if (radix == 0)
  37. {
  38. radix = hexStart ? 16 : 10;
  39. }
  40. else if (radix < 2 || radix > 36)
  41. {
  42. return JsNumber.DoubleNaN;
  43. }
  44. else if (radix != 16)
  45. {
  46. stripPrefix = false;
  47. }
  48. // check fast case
  49. if (radix == 10 && int.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out var number))
  50. {
  51. return JsNumber.Create(number);
  52. }
  53. var sign = 1;
  54. if (s.Length > 0)
  55. {
  56. var c = s[0];
  57. if (c == '-')
  58. {
  59. sign = -1;
  60. }
  61. if (c is '-' or '+')
  62. {
  63. s = s.Slice(1);
  64. }
  65. }
  66. if (stripPrefix && hexStart)
  67. {
  68. s = s.Slice(2);
  69. }
  70. if (s.Length == 0)
  71. {
  72. return double.NaN;
  73. }
  74. var hasResult = false;
  75. double result = 0;
  76. double pow = 1;
  77. for (var i = s.Length - 1; i >= 0; i--)
  78. {
  79. var digit = s[i];
  80. var index = digit switch
  81. {
  82. >= '0' and <= '9' => digit - '0',
  83. >= 'a' and <= 'z' => digit - 'a' + 10,
  84. >= 'A' and <= 'Z' => digit - 'A' + 10,
  85. _ => -1
  86. };
  87. if (index == -1 || index >= radix)
  88. {
  89. // reset
  90. hasResult = false;
  91. result = 0;
  92. pow = 1;
  93. continue;
  94. }
  95. hasResult = true;
  96. result += index * pow;
  97. pow *= radix;
  98. }
  99. return hasResult ? JsNumber.Create(sign * result) : JsNumber.DoubleNaN;
  100. }
  101. /// <summary>
  102. /// https://tc39.es/ecma262/#sec-parsefloat-string
  103. /// </summary>
  104. internal static JsValue ParseFloat(JsValue thisObject, JsCallArguments arguments)
  105. {
  106. var inputString = TypeConverter.ToString(arguments.At(0));
  107. var trimmedString = StringPrototype.TrimStartEx(inputString);
  108. if (string.IsNullOrWhiteSpace(trimmedString))
  109. {
  110. return JsNumber.DoubleNaN;
  111. }
  112. // start of string processing
  113. var i = 0;
  114. // check known string constants
  115. if (!char.IsDigit(trimmedString[0]))
  116. {
  117. if (trimmedString[0] == '-')
  118. {
  119. i++;
  120. if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity", StringComparison.Ordinal))
  121. {
  122. return JsNumber.DoubleNegativeInfinity;
  123. }
  124. }
  125. if (trimmedString[0] == '+')
  126. {
  127. i++;
  128. if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity", StringComparison.Ordinal))
  129. {
  130. return JsNumber.DoublePositiveInfinity;
  131. }
  132. }
  133. if (trimmedString.StartsWith("Infinity", StringComparison.Ordinal))
  134. {
  135. return JsNumber.DoublePositiveInfinity;
  136. }
  137. if (trimmedString.StartsWith("NaN", StringComparison.Ordinal))
  138. {
  139. return JsNumber.DoubleNaN;
  140. }
  141. }
  142. // find the starting part of string that is still acceptable JS number
  143. var dotFound = false;
  144. var exponentFound = false;
  145. while (i < trimmedString.Length)
  146. {
  147. var c = trimmedString[i];
  148. if (Character.IsDecimalDigit(c))
  149. {
  150. i++;
  151. continue;
  152. }
  153. if (c == '.')
  154. {
  155. if (dotFound)
  156. {
  157. // does not look right
  158. break;
  159. }
  160. i++;
  161. dotFound = true;
  162. continue;
  163. }
  164. if (c is 'e' or 'E')
  165. {
  166. if (exponentFound)
  167. {
  168. // does not look right
  169. break;
  170. }
  171. i++;
  172. exponentFound = true;
  173. continue;
  174. }
  175. if (c is '+' or '-' && trimmedString[i - 1] is 'e' or 'E')
  176. {
  177. // ok
  178. i++;
  179. continue;
  180. }
  181. break;
  182. }
  183. while (exponentFound && i > 0 && !Character.IsDecimalDigit(trimmedString[i - 1]))
  184. {
  185. // we are missing required exponent number part info
  186. i--;
  187. }
  188. // we should now have proper input part
  189. #if SUPPORTS_SPAN_PARSE
  190. var substring = trimmedString.AsSpan(0, i);
  191. #else
  192. var substring = trimmedString.Substring(0, i);
  193. #endif
  194. const NumberStyles Styles = NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign;
  195. if (double.TryParse(substring, Styles, CultureInfo.InvariantCulture, out var d))
  196. {
  197. return d;
  198. }
  199. return JsNumber.DoubleNaN;
  200. }
  201. /// <summary>
  202. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.4
  203. /// </summary>
  204. private static JsValue IsNaN(JsValue thisObject, JsCallArguments arguments)
  205. {
  206. var value = arguments.At(0);
  207. if (ReferenceEquals(value, JsNumber.DoubleNaN))
  208. {
  209. return true;
  210. }
  211. var x = TypeConverter.ToNumber(value);
  212. return double.IsNaN(x);
  213. }
  214. /// <summary>
  215. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.5
  216. /// </summary>
  217. private static JsValue IsFinite(JsValue thisObject, JsCallArguments arguments)
  218. {
  219. if (arguments.Length != 1)
  220. {
  221. return false;
  222. }
  223. var n = TypeConverter.ToNumber(arguments.At(0));
  224. if (double.IsNaN(n) || double.IsInfinity(n))
  225. {
  226. return false;
  227. }
  228. return true;
  229. }
  230. private const string UriReservedString = ";/?:@&=+$,";
  231. private const string UriUnescapedString = "-.!~*'()";
  232. private static readonly SearchValues<char> UriUnescaped = SearchValues.Create(Character.AsciiWordCharacters + UriUnescapedString);
  233. private static readonly SearchValues<char> UnescapedUriSet = SearchValues.Create(Character.AsciiWordCharacters + UriReservedString + UriUnescapedString + '#');
  234. private static readonly SearchValues<char> ReservedUriSet = SearchValues.Create(UriReservedString + '#');
  235. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  236. private static bool IsValidHexaChar(char c) => Uri.IsHexDigit(c);
  237. /// <summary>
  238. /// https://tc39.es/ecma262/#sec-encodeuri-uri
  239. /// </summary>
  240. private JsValue EncodeUri(JsValue thisObject, JsCallArguments arguments)
  241. {
  242. var uriString = TypeConverter.ToString(arguments.At(0));
  243. return Encode(uriString, UnescapedUriSet);
  244. }
  245. /// <summary>
  246. /// https://tc39.es/ecma262/#sec-encodeuricomponent-uricomponent
  247. /// </summary>
  248. private JsValue EncodeUriComponent(JsValue thisObject, JsCallArguments arguments)
  249. {
  250. var uriString = TypeConverter.ToString(arguments.At(0));
  251. return Encode(uriString, UriUnescaped);
  252. }
  253. [MethodImpl(512)]
  254. private JsValue Encode(string uriString, SearchValues<char> allowedCharacters)
  255. {
  256. var strLen = uriString.Length;
  257. var builder = new ValueStringBuilder(uriString.Length);
  258. Span<byte> buffer = stackalloc byte[4];
  259. for (var k = 0; k < strLen; k++)
  260. {
  261. var c = uriString[k];
  262. if (allowedCharacters.Contains(c))
  263. {
  264. builder.Append(c);
  265. }
  266. else
  267. {
  268. if (c >= 0xDC00 && c <= 0xDBFF)
  269. {
  270. goto uriError;
  271. }
  272. int v;
  273. if (c < 0xD800 || c > 0xDBFF)
  274. {
  275. v = c;
  276. }
  277. else
  278. {
  279. k++;
  280. if (k == strLen)
  281. {
  282. goto uriError;
  283. }
  284. var kChar = (int) uriString[k];
  285. if (kChar is < 0xDC00 or > 0xDFFF)
  286. {
  287. goto uriError;
  288. }
  289. v = (c - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000;
  290. }
  291. var length = 1;
  292. switch (v)
  293. {
  294. case >= 0 and <= 0x007F:
  295. // 00000000 0zzzzzzz -> 0zzzzzzz
  296. buffer[0] = (byte) v;
  297. break;
  298. case <= 0x07FF:
  299. // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz
  300. length = 2;
  301. buffer[0] = (byte) (0xC0 | (v >> 6));
  302. buffer[1] = (byte) (0x80 | (v & 0x3F));
  303. break;
  304. case <= 0xD7FF:
  305. // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz
  306. length = 3;
  307. buffer[0] = (byte) (0xE0 | (v >> 12));
  308. buffer[1] = (byte) (0x80 | ((v >> 6) & 0x3F));
  309. buffer[2] = (byte) (0x80 | (v & 0x3F));
  310. break;
  311. case <= 0xDFFF:
  312. goto uriError;
  313. case <= 0xFFFF:
  314. length = 3;
  315. buffer[0] = (byte) (0xE0 | (v >> 12));
  316. buffer[1] = (byte) (0x80 | ((v >> 6) & 0x3F));
  317. buffer[2] = (byte) (0x80 | (v & 0x3F));
  318. break;
  319. default:
  320. length = 4;
  321. buffer[0] = (byte) (0xF0 | (v >> 18));
  322. buffer[1] = (byte) (0x80 | (v >> 12 & 0x3F));
  323. buffer[2] = (byte) (0x80 | (v >> 6 & 0x3F));
  324. buffer[3] = (byte) (0x80 | (v >> 0 & 0x3F));
  325. break;
  326. }
  327. for (var i = 0; i < length; i++)
  328. {
  329. builder.Append('%');
  330. builder.AppendHex(buffer[i]);
  331. }
  332. }
  333. }
  334. return builder.ToString();
  335. uriError:
  336. _engine.SignalError(Throw.CreateUriError(_realm, "URI malformed"));
  337. return JsEmpty.Instance;
  338. }
  339. private JsValue DecodeUri(JsValue thisObject, JsCallArguments arguments)
  340. {
  341. var uriString = TypeConverter.ToString(arguments.At(0));
  342. return Decode(uriString, ReservedUriSet);
  343. }
  344. private JsValue DecodeUriComponent(JsValue thisObject, JsCallArguments arguments)
  345. {
  346. var componentString = TypeConverter.ToString(arguments.At(0));
  347. return Decode(componentString, null);
  348. }
  349. [MethodImpl(512)]
  350. private JsValue Decode(string uriString, SearchValues<char>? reservedSet)
  351. {
  352. var strLen = uriString.Length;
  353. _stringBuilder.EnsureCapacity(strLen);
  354. _stringBuilder.Clear();
  355. #if SUPPORTS_SPAN_PARSE
  356. Span<byte> octets = stackalloc byte[4];
  357. #else
  358. var octets = new byte[4];
  359. #endif
  360. for (var k = 0; k < strLen; k++)
  361. {
  362. var C = uriString[k];
  363. if (C != '%')
  364. {
  365. _stringBuilder.Append(C);
  366. }
  367. else
  368. {
  369. var start = k;
  370. if (k + 2 >= strLen)
  371. {
  372. goto uriError;
  373. }
  374. var c1 = uriString[k + 1];
  375. var c2 = uriString[k + 2];
  376. if (!IsValidHexaChar(c1) || !IsValidHexaChar(c2))
  377. {
  378. goto uriError;
  379. }
  380. var B = StringToIntBase16(uriString.AsSpan(k + 1, 2));
  381. k += 2;
  382. if ((B & 0x80) == 0)
  383. {
  384. C = (char) B;
  385. #pragma warning disable CA2249
  386. if (reservedSet == null || !reservedSet.Contains(C))
  387. #pragma warning restore CA2249
  388. {
  389. _stringBuilder.Append(C);
  390. }
  391. else
  392. {
  393. _stringBuilder.Append(uriString, start, k - start + 1);
  394. }
  395. }
  396. else
  397. {
  398. var n = 0;
  399. for (; ((B << n) & 0x80) != 0; n++)
  400. {
  401. }
  402. if (n == 1 || n > 4)
  403. {
  404. goto uriError;
  405. }
  406. octets[0] = B;
  407. if (k + (3 * (n - 1)) >= strLen)
  408. {
  409. goto uriError;
  410. }
  411. for (var j = 1; j < n; j++)
  412. {
  413. k++;
  414. if (uriString[k] != '%')
  415. {
  416. goto uriError;
  417. }
  418. c1 = uriString[k + 1];
  419. c2 = uriString[k + 2];
  420. if (!IsValidHexaChar(c1) || !IsValidHexaChar(c2))
  421. {
  422. goto uriError;
  423. }
  424. B = StringToIntBase16(uriString.AsSpan(k + 1, 2));
  425. // B & 11000000 != 10000000
  426. if ((B & 0xC0) != 0x80)
  427. {
  428. goto uriError;
  429. }
  430. k += 2;
  431. octets[j] = B;
  432. }
  433. switch (n)
  434. {
  435. case 2:
  436. {
  437. // Overlong encoding check for 2-byte sequences
  438. var x = octets[0] & 0x1F; // 0x00
  439. var y = octets[1] & 0x3F; // 0x2F
  440. var codepoint = (x << 6) | y; // 0x2F
  441. if (codepoint < 0x80) // 2-byte should be ≥ 0x80
  442. {
  443. goto uriError;
  444. }
  445. break;
  446. }
  447. case 3:
  448. {
  449. // Reserved surrogate pair (U+D800-DFFF)
  450. var x = octets[0] & 0x0F;
  451. var y = octets[1] & 0x3F;
  452. var z = octets[2] & 0x3F;
  453. var codepoint = (x << 12) | (y << 6) | z;
  454. if (codepoint is >= 0xD800 and <= 0xDFFF)
  455. {
  456. goto uriError;
  457. }
  458. break;
  459. }
  460. case 4:
  461. {
  462. var x = octets[0] & 0x07;
  463. var y = octets[1] & 0x3F;
  464. var z = octets[2] & 0x3F;
  465. var w = octets[3] & 0x3F;
  466. var codepoint = (x << 18) | (y << 12) | (z << 6) | w;
  467. if (codepoint > 0x10FFFF)
  468. {
  469. goto uriError;
  470. }
  471. break;
  472. }
  473. }
  474. #if SUPPORTS_SPAN_PARSE
  475. _stringBuilder.Append(Encoding.UTF8.GetString(octets.Slice(0, n)));
  476. #else
  477. _stringBuilder.Append(Encoding.UTF8.GetString(octets, 0, n));
  478. #endif
  479. }
  480. }
  481. }
  482. return _stringBuilder.ToString();
  483. uriError:
  484. _engine.SignalError(Throw.CreateUriError(_realm, "URI malformed"));
  485. return JsEmpty.Instance;
  486. }
  487. private static byte StringToIntBase16(ReadOnlySpan<char> s)
  488. {
  489. var i = 0;
  490. var length = s.Length;
  491. if (s[i] == '+')
  492. {
  493. i++;
  494. }
  495. if (i + 1 < length && s[i] == '0')
  496. {
  497. if (s[i + 1] == 'x' || s[i + 1] == 'X')
  498. {
  499. i += 2;
  500. }
  501. }
  502. uint result = 0;
  503. while (i < s.Length && IsDigit(s[i], 16, out var value))
  504. {
  505. result = result * 16 + (uint) value;
  506. i++;
  507. }
  508. return (byte) (int) result;
  509. }
  510. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  511. private static bool IsDigit(char c, int radix, out int result)
  512. {
  513. int tmp;
  514. if ((uint) (c - '0') <= 9)
  515. {
  516. result = tmp = c - '0';
  517. }
  518. else if ((uint) (c - 'A') <= 'Z' - 'A')
  519. {
  520. result = tmp = c - 'A' + 10;
  521. }
  522. else if ((uint) (c - 'a') <= 'z' - 'a')
  523. {
  524. result = tmp = c - 'a' + 10;
  525. }
  526. else
  527. {
  528. result = -1;
  529. return false;
  530. }
  531. return tmp < radix;
  532. }
  533. private static readonly SearchValues<char> EscapeAllowList = SearchValues.Create(Character.AsciiWordCharacters + "@*+-./");
  534. /// <summary>
  535. /// https://tc39.es/ecma262/#sec-escape-string
  536. /// </summary>
  537. private JsValue Escape(JsValue thisObject, JsCallArguments arguments)
  538. {
  539. var uriString = TypeConverter.ToString(arguments.At(0));
  540. var builder = new ValueStringBuilder(uriString.Length);
  541. foreach (var c in uriString)
  542. {
  543. if (EscapeAllowList.Contains(c))
  544. {
  545. builder.Append(c);
  546. }
  547. else if (c < 256)
  548. {
  549. builder.Append('%');
  550. builder.AppendHex((byte) c);
  551. }
  552. else
  553. {
  554. builder.Append("%u");
  555. builder.Append(((int) c).ToString("X4", CultureInfo.InvariantCulture));
  556. }
  557. }
  558. return builder.ToString();
  559. }
  560. /// <summary>
  561. /// http://www.ecma-international.org/ecma-262/5.1/#sec-B.2.2
  562. /// </summary>
  563. private JsValue Unescape(JsValue thisObject, JsCallArguments arguments)
  564. {
  565. var uriString = TypeConverter.ToString(arguments.At(0));
  566. var strLen = uriString.Length;
  567. _stringBuilder.EnsureCapacity(strLen);
  568. _stringBuilder.Clear();
  569. for (var k = 0; k < strLen; k++)
  570. {
  571. var c = uriString[k];
  572. if (c == '%')
  573. {
  574. if (k <= strLen - 6
  575. && uriString[k + 1] == 'u'
  576. && AreValidHexChars(uriString.AsSpan(k + 2, 4)))
  577. {
  578. c = ParseHexString(uriString.AsSpan(k + 2, 4));
  579. k += 5;
  580. }
  581. else if (k <= strLen - 3 && AreValidHexChars(uriString.AsSpan(k + 1, 2)))
  582. {
  583. c = ParseHexString(uriString.AsSpan(k + 1, 2));
  584. k += 2;
  585. }
  586. }
  587. _stringBuilder.Append(c);
  588. }
  589. return _stringBuilder.ToString();
  590. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  591. static bool AreValidHexChars(ReadOnlySpan<char> input)
  592. {
  593. foreach (var c in input)
  594. {
  595. if (!IsValidHexaChar(c))
  596. {
  597. return false;
  598. }
  599. }
  600. return true;
  601. }
  602. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  603. static char ParseHexString(ReadOnlySpan<char> input)
  604. {
  605. #if NET6_0_OR_GREATER
  606. return (char) int.Parse(input, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
  607. #else
  608. return (char) int.Parse(input.ToString(), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
  609. #endif
  610. }
  611. }
  612. // optimized versions with string parameter and without virtual dispatch for global environment usage
  613. internal bool HasProperty(Key property)
  614. {
  615. return GetOwnProperty(property) != PropertyDescriptor.Undefined;
  616. }
  617. private bool DefineOwnProperty(Key property, PropertyDescriptor desc)
  618. {
  619. var current = GetOwnProperty(property);
  620. if (current == desc)
  621. {
  622. return true;
  623. }
  624. // check fast path
  625. if ((current._flags & PropertyFlag.MutableBinding) != PropertyFlag.None)
  626. {
  627. current._value = desc.Value;
  628. return true;
  629. }
  630. return ValidateAndApplyPropertyDescriptor(this, new JsString(property), true, desc, current);
  631. }
  632. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  633. internal PropertyDescriptor GetOwnProperty(Key property)
  634. {
  635. Properties!.TryGetValue(property, out var descriptor);
  636. return descriptor ?? PropertyDescriptor.Undefined;
  637. }
  638. internal bool SetFromMutableBinding(Key property, JsValue value, bool strict)
  639. {
  640. // here we are called only from global environment record context
  641. // we can take some shortcuts to be faster
  642. if (!_properties!.TryGetValue(property, out var existingDescriptor))
  643. {
  644. if (strict)
  645. {
  646. Throw.ReferenceNameError(_realm, property.Name);
  647. }
  648. _properties[property] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding);
  649. return true;
  650. }
  651. if (existingDescriptor.IsDataDescriptor())
  652. {
  653. if (!existingDescriptor.Writable || existingDescriptor.IsAccessorDescriptor())
  654. {
  655. return false;
  656. }
  657. // check fast path
  658. if ((existingDescriptor._flags & PropertyFlag.MutableBinding) != PropertyFlag.None)
  659. {
  660. existingDescriptor._value = value;
  661. return true;
  662. }
  663. // slow path
  664. return DefineOwnProperty(property, new PropertyDescriptor(value, PropertyFlag.None));
  665. }
  666. if (existingDescriptor.Set is not ICallable setter)
  667. {
  668. return false;
  669. }
  670. setter.Call(this, value);
  671. return true;
  672. }
  673. }