@@ -386,41 +386,165 @@ private static Object preventExtensions(
386386 return target .preventExtensions ();
387387 }
388388
389+ /*
390+ * https://tc39.es/ecma262/#sec-reflect.set
391+ * 1. If target is not an Object, throw a TypeError exception.
392+ * 2. Let key be ? ToPropertyKey(propertyKey).
393+ * 3. If receiver is not present, then
394+ * a. Set receiver to target.
395+ * 4. Return ? target.[[Set]](key, V, receiver).
396+ */
389397 private static Object set (Context cx , Scriptable scope , Scriptable thisObj , Object [] args ) {
390- ScriptableObject target = checkTarget (args );
391- if (args .length < 2 ) {
392- return true ;
398+ final ScriptableObject target = checkTarget (args );
399+ final Object propertyKey = args .length > 1 ? args [1 ] : Undefined .instance ;
400+ final Object value = args .length > 2 ? args [2 ] : Undefined .instance ;
401+ final Object receiver = args .length > 3 ? args [3 ] : target ;
402+
403+ // If target is a proxy, delegate to the proxy handler
404+ if (target instanceof NativeProxy ) {
405+ final NativeProxy proxy = (NativeProxy ) target ;
406+ final Function trap = proxy .getTrap ("set" );
407+ if (trap != null ) {
408+ final ScriptableObject proxyTarget = proxy .getTargetThrowIfRevoked ();
409+ final Object [] trapArgs = {proxyTarget , propertyKey , value , receiver };
410+ final boolean booleanTrapResult = ScriptRuntime .toBoolean (proxy .callTrap (trap , trapArgs ));
411+ if (!booleanTrapResult ) {
412+ return false ;
413+ }
414+
415+ // checks for non-configurable properties
416+ // https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver steps 10
417+ final DescriptorInfo targetDesc = proxyTarget .getOwnPropertyDescriptor (cx , propertyKey );
418+ if (targetDesc != null && targetDesc .isConfigurable (false )) {
419+ if (targetDesc .isDataDescriptor () && targetDesc .isWritable (false )) {
420+ if (!Objects .equals (value , targetDesc .value )) {
421+ throw ScriptRuntime .typeError (
422+ "proxy can't successfully set a non-writable,"
423+ + " non-configurable property '\" " + propertyKey + "\" '" );
424+ }
425+ }
426+ if (targetDesc .isAccessorDescriptor ()
427+ && (targetDesc .setter == null
428+ || targetDesc .setter == Scriptable .NOT_FOUND
429+ || Undefined .isUndefined (targetDesc .setter ))) {
430+ throw ScriptRuntime .typeError (
431+ "proxy can't successfully set a non-writable,"
432+ + " non-configurable property '\" " + propertyKey + "\" '" );
433+ }
434+ }
435+ return true ;
436+ }
393437 }
394438
395- ScriptableObject receiver =
396- args .length > 3 ? ScriptableObject .ensureScriptableObject (args [3 ]) : target ;
397- if (receiver != target ) {
398- DescriptorInfo descriptor = target .getOwnPropertyDescriptor (cx , args [1 ]);
399- if (descriptor != null ) {
400- Object setter = descriptor .setter ;
401- if (setter != null && setter != NOT_FOUND ) {
402- ((Function ) setter ).call (cx , scope , receiver , new Object [] {args [2 ]});
403- return true ;
439+ return internalSet (cx , target , propertyKey , value , receiver );
440+ }
441+
442+ /*
443+ * https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
444+ * 1. Let ownDesc be ? O.[[GetOwnProperty]](P).
445+ * 2. If ownDesc is undefined, then
446+ * a. Let parent be ? O.[[GetPrototypeOf]]().
447+ * b. If parent is not null, then
448+ * i. Return ? parent.[[Set]](P, V, Receiver).
449+ * c. Else,
450+ * i. Set ownDesc to the PropertyDescriptor
451+ * { [[Value]]: undefined, [[Writable]]: true,
452+ * [[Enumerable]]: true, [[Configurable]]: true }.
453+ * 3. If IsDataDescriptor(ownDesc) is true, then
454+ * a. If ownDesc.[[Writable]] is false, return false.
455+ * b. If Receiver is not an Object, return false.
456+ * c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
457+ * d. If existingDescriptor is not undefined, then
458+ * i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
459+ * ii. If existingDescriptor.[[Writable]] is false, return false.
460+ * iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
461+ * iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
462+ * e. Else,
463+ * i. Assert: Receiver does not currently have a property P.
464+ * ii. Return ? CreateDataProperty(Receiver, P, V).
465+ * 4. Assert: IsAccessorDescriptor(ownDesc) is true.
466+ * 5. Let setter be ownDesc.[[Set]].
467+ * 6. If setter is undefined, return false.
468+ * 7. Perform ? Call(setter, Receiver, « V »).
469+ * 8. Return true.
470+ */
471+ private static boolean internalSet (Context cx , ScriptableObject target , Object propertyKey ,
472+ Object value , Object receiver ) {
473+ try {
474+ DescriptorInfo ownDesc = target .getOwnPropertyDescriptor (cx , propertyKey );
475+ if (ownDesc == null ) {
476+ final Scriptable parent = target .getPrototype ();
477+ if (parent != null ) {
478+ return internalSet (cx , ScriptableObject .ensureScriptableObject (parent ), propertyKey , value , receiver );
479+ }
480+ ownDesc = new DescriptorInfo (true , true , true , Undefined .instance );
481+ }
482+
483+ if (ownDesc .isDataDescriptor ()) {
484+ if (ownDesc .isWritable (false )) {
485+ return false ;
486+ }
487+ if (!ScriptRuntime .isObject (receiver )) {
488+ return false ;
404489 }
405490
406- if (descriptor .isConfigurable (false )) {
491+ final ScriptableObject receiverObj = ScriptableObject .ensureScriptableObject (receiver );
492+ final DescriptorInfo existingDescriptor = receiverObj .getOwnPropertyDescriptor (cx , propertyKey );
493+ if (existingDescriptor != null ) {
494+ if (existingDescriptor .isAccessorDescriptor ()) {
495+ return false ;
496+ }
497+ if (existingDescriptor .isWritable (false )) {
498+ return false ;
499+ }
500+ } else if (!receiverObj .isExtensible ()) {
407501 return false ;
408502 }
503+
504+ // If receiver is a proxy, set property directly on the proxy's target
505+ // to avoid recursion (reflect <-> proxy)
506+ final ScriptableObject realReceiverObj = receiverObj instanceof NativeProxy
507+ ? ((NativeProxy ) receiverObj ).getTargetThrowIfRevoked ()
508+ : receiverObj ;
509+
510+ if (ScriptRuntime .isSymbol (propertyKey )) {
511+ realReceiverObj .put ((Symbol ) propertyKey , realReceiverObj , value );
512+ } else {
513+ final StringIdOrIndex s = ScriptRuntime .toStringIdOrIndex (propertyKey );
514+ if (s .stringId == null ) {
515+ realReceiverObj .put (s .index , realReceiverObj , value );
516+ } else {
517+ realReceiverObj .put (s .stringId , realReceiverObj , value );
518+ }
519+ }
520+
521+ return true ;
409522 }
410- }
411523
412- if (ScriptRuntime .isSymbol (args [1 ])) {
413- receiver .put ((Symbol ) args [1 ], receiver , args [2 ]);
414- } else {
415- StringIdOrIndex s = ScriptRuntime .toStringIdOrIndex (args [1 ]);
416- if (s .stringId == null ) {
417- receiver .put (s .index , receiver , args [2 ]);
418- } else {
419- receiver .put (s .stringId , receiver , args [2 ]);
524+ if (ownDesc .isAccessorDescriptor ()) {
525+ final Object setter = ownDesc .setter ;
526+ if (setter == null
527+ || setter == Scriptable .NOT_FOUND
528+ || Undefined .isUndefined (setter )) {
529+ return false ;
530+ }
531+ final Scriptable receiverForCall ;
532+ if (receiver == null || Undefined .isUndefined (receiver )) {
533+ receiverForCall = cx .isStrictMode ()
534+ ? null
535+ : ScriptableObject .getTopLevelScope (target );
536+ } else {
537+ receiverForCall = ScriptableObject .ensureScriptable (receiver );
538+ }
539+
540+ ((Function ) setter ).call (cx , target , receiverForCall , new Object [] {value });
420541 }
421- }
422542
423- return true ;
543+ return true ;
544+
545+ } catch (EcmaError e ) {
546+ return false ;
547+ }
424548 }
425549
426550 private static Object setPrototypeOf (
0 commit comments