Hacker News new | past | comments | ask | show | jobs | submit login

It appears this was already reported and fixed in WebKit - https://bugs.webkit.org/show_bug.cgi?id=188794 but not specifically for the Safari browser.

  Array.prototype.reverse modifies JSImmutableButterfly
You have to wonder how an immutable butterfly can be modified?!

Almost all “immutable” types are mutatable by the runtime itself, irrespective of language or environment.

A couple of examples of why “immutable” types are actually mutable:

* string interning - a string may technically be immutable, but identical strings can be coalesced by the runtime. * immutable objects are not generally immortal: the runtime will eventually want to free the data, which means it must be mutable by the runtime.

Basically at the runtime level if you forget to check all the correct flags you can easily do the wrong thing. If you look at the bugzilla report you can see the comment from the apple engineer about needing to fix this particular foot gun.

I would be most grateful if someone could please explain to me what "butterfly" is meant to be ... a metaphor of some sort I assume.

Internal data structure of JavaScriptCore representing objects, presumably called a butterfly because it starts in the middle and spreads in both directions in memory. One side is properties of the object, the other members of its array aspect.

See e.g. http://phrack.org/papers/attacking_javascript_engines.html section 1.2

This presentation has some nice illustrations: http://www.filpizlo.com/slides/pizlo-dls2017-vmil2017-jscvm-... (under “JSC Object Model”).

An inadvertent yet apt Bradbury reference

I wonder how comparable the situation is to jq, which also does CoW / immutable data. In jq, when a datum has only one live reference, changes to that datum actually mutate the datum. A C example is probably necessary to make this clear:

  jv v = jv_array();  // ref count == 1
  v = jv_array_append(v, jv_number(0)); // Ostensibly copy
  v = jv_array_append(v, jv_number(1)); // but actually mutate
Here, in all cases there is exactly one reference to the array `v`, so `jv_array_append()` actually modifies it in place. Do note that `jv_array_append()` returns a new `jv` value though, and that this is necessary to support the copy-on-write case:

  jv v = jv_array();  // ref count == 1
  jv v2 = jv_copy(v); // ref count == 2 now!
  v = jv_array_append(v, jv_number(0)); // Copy!
  // Now `v`'s ref count == 1 again (and so does `v2`'s!)
  v = jv_array_append(v, jv_number(1)); // Ostensibly copy, actually mutate
You can imagine similar mechanics in an ECMAScript implementation.

Note that in jq itself this is mostly not something a user ever has to think about because one is always passing modified structures to an expression on the right:

  (.foo = "bar") | ... # this sees .foo == "bar"
but it can be user-visible anyways:

  ((.foo = "bar") | ...), # ... sees .foo == "bar"
   .foo                   # but here .foo is as it was before
Here the last reference to `.foo` happens outside the lexical context of the assignment to `.foo`, so the assignment is not visible. Under the covers this all is made possible by the C jv API's copy-on-write design where functions always consume a reference to their input `jv` values (except `jv_copy()`, `jv_get_kind()`, `jv_string_value()`, and `jv_number_value()`) and return a new `jv` value to replace the one that was "modified".

Thanks so much for linking to the thread; I added a comment to the bug pointing back here asking for further insight.

Hopefully this gets this (HN) thread seen by the right people and messages flying in the right directions internally :)

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact