Skip to content

Commit 8949a3e

Browse files
committed
fix: Fix handling of Symbol and non-enumerable properties in finalization / freeze. Fixes #1096, #1087, #1091 (#1105))
1 parent 44363f7 commit 8949a3e

File tree

4 files changed

+39
-12
lines changed

4 files changed

+39
-12
lines changed

__tests__/base.js

+16
Original file line numberDiff line numberDiff line change
@@ -2362,6 +2362,22 @@ function testObjectTypes(produce) {
23622362
[customSymbol]: 3
23632363
})
23642364
})
2365+
2366+
describe("#1096 / #1087 / proxies", () => {
2367+
const sym = Symbol()
2368+
2369+
let state = {
2370+
id: 1,
2371+
[sym]: {x: 2}
2372+
}
2373+
2374+
state = produce(state, draft => {
2375+
draft.id = 2
2376+
draft[sym]
2377+
})
2378+
2379+
expect(state[sym].x).toBe(2)
2380+
})
23652381
}
23662382

23672383
function testLiteralTypes(produce) {

__tests__/frozen.js

+1
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ function runTests(name) {
219219
state2.ref.state.x++
220220
}).not.toThrow()
221221
expect(state2.ref.state.x).toBe(2)
222+
expect(component.state.x).toBe(2)
222223
})
223224

224225
it("never freezes symbolic fields #590", () => {

src/core/finalize.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,8 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
5858
const state: ImmerState = value[DRAFT_STATE]
5959
// A plain object, might need freezing, might contain drafts
6060
if (!state) {
61-
each(
62-
value,
63-
(key, childValue) =>
64-
finalizeProperty(rootScope, state, value, key, childValue, path),
65-
true // See #590, don't recurse into non-enumerable of non drafted objects
61+
each(value, (key, childValue) =>
62+
finalizeProperty(rootScope, state, value, key, childValue, path)
6663
)
6764
return value
6865
}
@@ -148,8 +145,14 @@ function finalizeProperty(
148145
return
149146
}
150147
finalize(rootScope, childValue)
151-
// immer deep freezes plain objects, so if there is no parent state, we freeze as well
152-
if (!parentState || !parentState.scope_.parent_)
148+
// Immer deep freezes plain objects, so if there is no parent state, we freeze as well
149+
// Per #590, we never freeze symbolic properties. Just to make sure don't accidentally interfere
150+
// with other frameworks.
151+
if (
152+
(!parentState || !parentState.scope_.parent_) &&
153+
typeof prop !== "symbol" &&
154+
Object.prototype.propertyIsEnumerable.call(targetObject, prop)
155+
)
153156
maybeFreeze(rootScope, childValue)
154157
}
155158
}

src/utils/common.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,19 @@ export function original(value: Drafted<any>): any {
6060
return value[DRAFT_STATE].base_
6161
}
6262

63+
/**
64+
* Each iterates a map, set or array.
65+
* Or, if any other kind of of object all it's own properties.
66+
* Regardless whether they are enumerable or symbols
67+
*/
6368
export function each<T extends Objectish>(
6469
obj: T,
65-
iter: (key: string | number, value: any, source: T) => void,
66-
enumerableOnly?: boolean
70+
iter: (key: string | number, value: any, source: T) => void
6771
): void
6872
export function each(obj: any, iter: any) {
6973
if (getArchtype(obj) === ArchType.Object) {
70-
Object.entries(obj).forEach(([key, value]) => {
71-
iter(key, value, obj)
74+
Reflect.ownKeys(obj).forEach(key => {
75+
iter(key, obj[key], obj)
7276
})
7377
} else {
7478
obj.forEach((entry: any, index: any) => iter(index, entry, obj))
@@ -191,7 +195,10 @@ export function freeze<T>(obj: any, deep: boolean = false): T {
191195
obj.set = obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any
192196
}
193197
Object.freeze(obj)
194-
if (deep) each(obj, (_key, value) => freeze(value, true), true)
198+
if (deep)
199+
// See #590, don't recurse into non-enumerable / Symbol properties when freezing
200+
// So use Object.entries (only string-like, enumerables) instead of each()
201+
Object.entries(obj).forEach(([key, value]) => freeze(value, true))
195202
return obj
196203
}
197204

0 commit comments

Comments
 (0)