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