Skip to content

fix(reactivity): proxy sealed and non-extensible targets (fix #14893)#14917

Open
itsybitsybootsy wants to merge 2 commits into
vuejs:mainfrom
itsybitsybootsy:fix/reactive-non-extensible-14893
Open

fix(reactivity): proxy sealed and non-extensible targets (fix #14893)#14917
itsybitsybootsy wants to merge 2 commits into
vuejs:mainfrom
itsybitsybootsy:fix/reactive-non-extensible-14893

Conversation

@itsybitsybootsy

@itsybitsybootsy itsybitsybootsy commented Jun 4, 2026

Copy link
Copy Markdown

Summary

reactive(Object.seal({...})) returned the raw target instead of a proxy, so mutations on existing keys went untracked. The guard in createReactiveObject skipped any non-extensible target, but seal and preventExtensions only lock the structure, not the values. Narrowed to Object.isFrozen. The broader guard was the fix for #1784 / #1753, back when reactive wrote a flag onto the target. That hasn't been true for a while.

markRaw can't put the SKIP flag onto a non-extensible target, so after the guard change it falls back to a side WeakSet for those.

Closes #14893.

Test Plan

pnpm vitest run packages/reactivity/ reports 434 passed. The new test exercises reactive, shallowReactive, readonly, and shallowReadonly on sealed inputs with effect tracking, and a separate test pins the markRaw WeakSet path. The freeze case is preserved as should not observe frozen objects.

Summary by CodeRabbit

  • Tests

    • Added tests covering opt-out behavior for non-extensible objects (sealed/frozen/preventExtensions), nested cases, shallow/react variants, and watch traversal skipping opt-out targets.
  • Bug Fixes

    • Improved opt-out handling so non-extensible objects are correctly ignored by reactivity where intended.
    • Clarified behavior for frozen vs. sealed/preventExtensions targets when nested, ensuring correct dependency tracking.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 50cca7cd-477e-42f4-a2bd-9b8c3b8d3a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 637360b and 5aa8967.

📒 Files selected for processing (3)
  • packages/reactivity/__tests__/reactive.spec.ts
  • packages/reactivity/src/reactive.ts
  • packages/reactivity/src/watch.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/reactivity/tests/reactive.spec.ts

📝 Walkthrough

Walkthrough

This PR adds a WeakSet-based fallback for markRaw to opt out non-extensible objects, exports isRawMarked, updates createReactiveObject to skip frozen or raw-marked targets, updates watch traversal to respect isRawMarked, and adds tests for sealed, frozen, and prevented-extension behaviors.

Changes

rawSet-based raw target tracking for non-extensible objects

Layer / File(s) Summary
rawSet, isRawMarked and markRaw integration
packages/reactivity/src/reactive.ts
Introduces internal rawSet WeakSet and exported isRawMarked. markRaw records non-extensible targets in rawSet when SKIP cannot be defined. createReactiveObject returns early for isRawMarked(target) or Object.isFrozen(target).
watch traversal update
packages/reactivity/src/watch.ts
Replaces ReactiveFlags.SKIP checks with isRawMarked(value) to stop deep traversal and updates imports accordingly.
Test coverage for markRaw and non-extensible behavior
packages/reactivity/__tests__/reactive.spec.ts
Adds tests asserting markRaw preserves opt-out for sealed objects (including nested cases), verifies watch traversal skips marked non-extensible targets, asserts frozen nested objects are not made reactive, and updates expectations for sealed/preventExtensions targets across shallow/react/readonly variants.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • vuejs/core#14860: Modifies similar createReactiveObject/markRaw early-return logic and overlaps with this PR.

Suggested labels

ready to merge

Suggested reviewers

  • edison1105

Poem

🐰 I found a sealed and quiet thing,
A WeakSet stitch to clip its string,
markRaw whispers, "stay unchanged,"
traversal skips what couldn't be arranged,
A gentle hop; the reactivity bell won't ring.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing reactivity behavior for sealed and non-extensible targets, directly addressing issue #14893.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@edison1105 edison1105 added scope: reactivity 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. labels Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/reactivity/src/reactive.ts`:
- Around line 26-27: The parity issue: non-extensible markRaw values stored in
rawSet are not being treated the same as objects flagged with ReactiveFlags.SKIP
during deep traversal/dependency collection (e.g., inside watch()). Fix by
updating the traversal / dependency-collection checks that currently test for
ReactiveFlags.SKIP to also check rawSet.has(target) (and where appropriate check
for non-extensible via Object.isExtensible) so markRaw non-extensible objects
are skipped the same way; update all places that gate behavior on
ReactiveFlags.SKIP (search for uses in watch() and traversal helpers) to include
a combined condition like “isSkipped = !!target[ReactiveFlags.SKIP] ||
rawSet.has(target)” so sealed/marked objects behave identically to skip-flagged
ones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b31663a2-de15-42b8-89cb-1dda094358a6

📥 Commits

Reviewing files that changed from the base of the PR and between 86ad076 and 637360b.

📒 Files selected for processing (2)
  • packages/reactivity/__tests__/reactive.spec.ts
  • packages/reactivity/src/reactive.ts

Comment thread packages/reactivity/src/reactive.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. scope: reactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reactive silently skips un-extensible objects, which seems undocumented and unnecessary

2 participants