25

According to the ES5.1 spec, the program "use strict;" "foo".bar = 42; causes a String object to be created, assigns to a property on it, and then throws the object away, resulting in no observable effects - including any exceptions. (The absence of effect can be confirmed by trying it in an ES5-compatible JS implementation like that in Opera 12.)

In modern JS implementations, it throws a TypeError instead—try it:

"use strict"; "foo".bar = 42;

I am pretty sure the new behaviour is mandated by the ES6 spec, but despite reading the relevant sections several times I cannot see where it is specified that TypeError be thrown. In fact, the key parts appear to be unchanged:

6.2.3.2 PutValue (V, W)#

  1. ReturnIfAbrupt(V).
  2. ReturnIfAbrupt(W).
  3. If Type(V) is not Reference, throw a ReferenceError exception.
  4. Let base be GetBase(V).
  5. If IsUnresolvableReference(V) is true, then
  6. Else if IsPropertyReference(V) is true, then
    • a. If HasPrimitiveBase(V) is true, then
      • i. Assert: In this case, base will never be null or undefined.
      • ii. Set base to ToObject(base).
    • b. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
    • c. ReturnIfAbrupt(succeeded).
    • d. If succeeded is false and IsStrictReference(V) is true, throw a TypeError exception.
    • e. Return.

Where does the spec (ES6 or later) mandate throwing TypeError?

Community
  • 1
  • 1
cpcallen
  • 1,834
  • 1
  • 16
  • 27
  • I'm not seeing it in [Annex C](http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-of-ecmascript), either. – user2357112 Apr 05 '18 at 23:17
  • I'm starting to suspect this isn't actually in the spec at all. – user2357112 Apr 05 '18 at 23:26
  • 1
    Here’s where it made it into V8, with no references as usual: https://codereview.chromium.org/408183002/ Looking at others now. – Ry- Apr 05 '18 at 23:30
  • My expectation is that the `ToObject` would explicitly return a non-extensible `String`, thus `[[Set]]` would return false and step 6 would throw, but I don't actually see it defined that the string object is non-extensible. – loganfsmyth Apr 05 '18 at 23:34
  • @Paulpro: “`ToObject` is just copying the `[[Extensible]]` attribute” – are you referring to implementations here? They don’t even do a `ToObject`, so it’s not too confusing. – Ry- Apr 05 '18 at 23:39
  • 1
    @Paulpro: Creating a string object, setting it inextensible with `Object.preventExtensions`, and trying to set a property on it in strict mode produces a different error message for me, one mentioning "object is not extensible". It doesn't seem to be going through an object at all for the case in the question. – user2357112 Apr 05 '18 at 23:40

2 Answers2

16

I guess it's here:

http://www.ecma-international.org/ecma-262/7.0/#sec-ordinaryset

9.1.9.1. OrdinarySet (O, P, V, Receiver)

[...]

4.b. If Type(Receiver) is not Object, return false.

(Previously called [[Set]], in ES6 §9.1.9.)

Although PutValue promotes the base to an object, it doesn't do the same with the receiver -- GetThisValue(V) is still called on the original V (with a primitive base). So, GetThisValue returns a primitive, OrdinarySet.4b fails to assign a freshly created ownDesc and returns false, and this in turn causes PutValue.6d to throw a TypeError, provided the reference is strict.

The corresponding part of V8 seems to follow the same logic:

Maybe<bool> Object::AddDataProperty(....
  if (!it->GetReceiver()->IsJSReceiver()) {
    return CannotCreateProperty(...

https://github.com/v8/v8/blob/3b39fc4dcdb6593013c497fc9e28a1d73dbcba03/src/objects.cc#L5140

Community
  • 1
  • 1
georg
  • 211,518
  • 52
  • 313
  • 390
  • It shouldn't reach there, because `ownDesc` should be undefined. – user2357112 Apr 06 '18 at 00:06
  • So what? Step 3 doesn't return. – georg Apr 06 '18 at 00:07
  • 1
    Nice find, that does look right to me. It'll walk up the prototype and eventually hit the top level, create a data descriptor, and try to set it on the receiver, which is still the primitive. – loganfsmyth Apr 06 '18 at 00:09
  • @georg: True. I don't see where strict mode would come into things, though. – user2357112 Apr 06 '18 at 00:10
  • There is no mention of strict mode. If this would throw an error, then it should throw it without strict mode too, which is not true – ibrahim mahrir Apr 06 '18 at 00:13
  • 1
    Yes it would be good to expand this to mention `6.d`, though that was already included in the question. – loganfsmyth Apr 06 '18 at 00:14
  • It looks like [12.3.2.1](http://www.ecma-international.org/ecma-262/7.0/#sec-property-accessors-runtime-semantics-evaluation) is where the spec says the reference is a strict reference, and [6.2.3.2 6.c.](http://www.ecma-international.org/ecma-262/7.0/#sec-putvalue) throws a TypeError for a strict reference. – user2357112 Apr 06 '18 at 00:17
  • 2
    It's weird that this doesn't seem to be covered in Annex C. Annex C is only informative, but still. Weird. – user2357112 Apr 06 '18 at 00:22
5

@georg’s answer seems to be the right ES6+ interpretation, but it looks like the behaviour isn’t new, either. From ES5.1 PutValue:

  1. Else if IsPropertyReference(V), then

    a. If HasPrimitiveBase(V) is false, then let put be the [[Put]] internal method of base, otherwise let put be the special [[Put]] internal method defined below.

    b. Call the put internal method using base as its this value, and passing GetReferencedName(V) for the property name, W for the value, and IsStrictReference(V) for the Throw flag.

and in the referenced [[Put]]:

  1. Else, this is a request to create an own property on the transient object O

    a. If Throw is true, then throw a TypeError exception.

It feels like I’m probably misreading something… but what else could the rather pointed “this is a request to create an own property on the transient object O” be referring to?

Community
  • 1
  • 1
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • 1
    This is an excellent observation. I am not quite sure how I overlooked this when checking the ES5.1 spec—but it looks like the Opera 12 devs did too. – cpcallen Apr 06 '18 at 23:56