-
Notifications
You must be signed in to change notification settings - Fork 20
/
mutable.js
132 lines (117 loc) · 3.75 KB
/
mutable.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Adapted from SES/Caja
// Copyright (C) 2011 Google Inc.
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
import {
defineProperty,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
getOwnPropertyNames,
getOwnPropertySymbols,
objectHasOwnProperty
} from './commons';
/**
* For a special set of properties (defined below), it ensures that the
* effect of freezing does not suppress the ability to override these
* properties on derived objects by simple assignment.
*
* Because of lack of sufficient foresight at the time, ES5 unfortunately
* specified that a simple assignment to a non-existent property must fail if
* it would override a non-writable data property of the same name. (In
* retrospect, this was a mistake, but it is now too late and we must live
* with the consequences.) As a result, simply freezing an object to make it
* tamper proof has the unfortunate side effect of breaking previously correct
* code that is considered to have followed JS best practices, if this
* previous code used assignment to override.
*
* To work around this mistake, deepFreeze(), prior to freezing, replaces
* selected configurable own data properties with accessor properties which
* simulate what we should have specified -- that assignments to derived
* objects succeed if otherwise possible.
*/
function beMutable(obj, prop, desc) {
if ('value' in desc && desc.configurable) {
const value = desc.value;
// eslint-disable-next-line no-inner-declarations
function getter() {
return value;
}
// Re-attach the data property on the object so
// it can be found by the deep-freeze traversal process.
getter.value = value;
// eslint-disable-next-line no-inner-declarations
function setter(newValue) {
if (obj === this) {
throw new TypeError(`Cannot assign to read only property '${prop}' of object '${obj}'`);
}
if (objectHasOwnProperty.call(this, prop)) {
this[prop] = newValue;
} else {
defineProperty(this, prop, {
value: newValue,
writable: true,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
}
defineProperty(obj, prop, {
get: getter,
set: setter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
}
export function beMutableProperties(obj) {
if (!obj) {
return;
}
const descs = getOwnPropertyDescriptors(obj);
if (!descs) {
return;
}
getOwnPropertyNames(obj).forEach(prop => beMutable(obj, prop, descs[prop]));
getOwnPropertySymbols(obj).forEach(prop => beMutable(obj, prop, descs[prop]));
}
export function beMutableProperty(obj, prop) {
const desc = getOwnPropertyDescriptor(obj, prop);
beMutable(obj, prop, desc);
}
/**
* These properties are subject to the override mistake
* and must be converted before freezing.
*/
export function repairDataProperties(intrinsics) {
const i = intrinsics;
[
i.ObjectPrototype,
i.ArrayPrototype,
i.BooleanPrototype,
i.DatePrototype,
i.NumberPrototype,
i.StringPrototype,
i.FunctionPrototype,
i.GeneratorPrototype,
i.AsyncFunctionPrototype,
i.AsyncGeneratorPrototype,
i.IteratorPrototype,
i.ArrayIteratorPrototype,
i.PromisePrototype,
i.DataViewPrototype,
i.TypedArray,
i.Int8ArrayPrototype,
i.Int16ArrayPrototype,
i.Int32ArrayPrototype,
i.Uint8Array,
i.Uint16Array,
i.Uint32Array,
i.ErrorPrototype,
i.EvalErrorPrototype,
i.RangeErrorPrototype,
i.ReferenceErrorPrototype,
i.SyntaxErrorPrototype,
i.TypeErrorPrototype,
i.URIErrorPrototype
].forEach(beMutableProperties);
}