jenkins-bot has submitted this change and it was merged.
Change subject: Ensure that embedded Maps and Sets are properly deep-frozen.
......................................................................
Ensure that embedded Maps and Sets are properly deep-frozen.
Object.freeze() doesn't actually have any effect on Map and Set, since
their mutable data is held in a hidden internal field. Stomp on the
mutator methods to ensure that any attempt to alter the map is
rejected. (You could still mutate the collection if you try
really hard, for example `Map.prototype.set.call(m, ...)`, but
we're just trying to avoid foot-guns here.)
Tweak deepFreeze to recurse correctly through included maps and sets,
freezing the keys/values as appropriate.
Change-Id: Ia16738e88aa3ee9c46fd71da50ce66b8917a07d3
---
M lib/jsutils.js
1 file changed, 55 insertions(+), 14 deletions(-)
Approvals:
Arlolra: Looks good to me, approved
jenkins-bot: Verified
diff --git a/lib/jsutils.js b/lib/jsutils.js
index 816646c..d3aba85 100644
--- a/lib/jsutils.js
+++ b/lib/jsutils.js
@@ -5,6 +5,10 @@
'use strict';
require('./core-upgrade');
+var rejectMutation = function() {
+ throw new TypeError("Mutation attempted on read-only collection.");
+};
+
var JSUtils = {
// in ES7 it should be `new Map(Object.entries(obj))`
@@ -14,29 +18,66 @@
}));
},
+ // ES6 maps/sets are still writable even when frozen, because they
+ // store data inside the object linked from an internal slot.
+ // This freezes a map by disabling the mutation methods, although
+ // its not bulletproof: you could use `Map.prototype.set.call(m, ...)`
+ // to still mutate the backing store.
+ freezeMap: function(it, freezeEntries) {
+ // Allow `it` to be an iterable, as well as a map.
+ if (!(it instanceof Map)) { it = new Map(it); }
+ it.set = it.clear = it['delete'] = rejectMutation;
+ Object.freeze(it);
+ if (freezeEntries) {
+ it.forEach(function(v, k) {
+ JSUtils.deepFreeze(v);
+ JSUtils.deepFreeze(k);
+ });
+ }
+ return it;
+ },
+
+ // This makes a set read-only.
+ freezeSet: function(it, freezeEntries) {
+ // Allow `it` to be an iterable, as well as a set.
+ if (!(it instanceof Set)) { it = new Set(it); }
+ it.add = it.clear = it['delete'] = rejectMutation;
+ Object.freeze(it);
+ if (freezeEntries) {
+ it.forEach(function(v) {
+ JSUtils.deepFreeze(v);
+ });
+ }
+ return it;
+ },
+
// Deep-freeze an object
// See
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/freeze
deepFreeze: function(o) {
- if ( o === undefined ) {
- return;
- } else if ( !(o instanceof Object) ) {
- return;
- } else if ( Object.isFrozen(o) ) {
- return;
+ if (!(o instanceof Object)) {
+ return o;
+ } else if (Object.isFrozen(o)) {
+ // Note that this might leave an unfrozen reference
somewhere in
+ // the object if there is an already frozen object
containing an
+ // unfrozen object.
+ return o;
+ } else if (o instanceof Map) {
+ return JSUtils.freezeMap(o, true);
+ } else if (o instanceof Set) {
+ return JSUtils.freezeSet(o, true);
}
- Object.freeze(o); // First freeze the object.
+ Object.freeze(o);
for (var propKey in o) {
- var prop = o[propKey];
- if (!o.hasOwnProperty(propKey) || !(prop instanceof
Object) || Object.isFrozen(prop)) {
- // If the object is on the prototype, not an
object, or is already frozen,
- // skip it. Note that this might leave an
unfrozen reference somewhere in the
- // object if there is an already frozen object
containing an unfrozen object.
+ var desc = Object.getOwnPropertyDescriptor(o, propKey);
+ if ((!desc) || desc.get || desc.set) {
+ // If the object is on the prototype or is a
getter, skip it.
continue;
}
-
- this.deepFreeze(prop); // Recursively call deepFreeze.
+ // Recursively call deepFreeze.
+ JSUtils.deepFreeze(desc.value);
}
+ return o;
},
// Convert a counter to a Base64 encoded string.
--
To view, visit https://gerrit.wikimedia.org/r/207816
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia16738e88aa3ee9c46fd71da50ce66b8917a07d3
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/services/parsoid
Gerrit-Branch: master
Gerrit-Owner: Cscott <[email protected]>
Gerrit-Reviewer: Arlolra <[email protected]>
Gerrit-Reviewer: Cscott <[email protected]>
Gerrit-Reviewer: Subramanya Sastry <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits