Skip to content

Commit

Permalink
Exclude Object and Promise prototype properties from shadowing for pa…
Browse files Browse the repository at this point in the history
…rams and searchParams (#70568)

params and searchParams are now promises however to facilitate migration
params and searchParams can still be referenced directly on these props.
There are a number of special properties however that conflict with this
and this change special cases more property names to not be
synchronously accessed.

We exclude common Object prototype properties, Promise prototype
properties, and properties that are commonly existence tested like
toJSON and displayName.
  • Loading branch information
gnoff authored Sep 27, 2024
1 parent af8af5e commit 58209bd
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 18 deletions.
109 changes: 97 additions & 12 deletions packages/next/src/server/request/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,32 @@ function makeAbortingExoticParams(

Object.keys(underlyingParams).forEach((prop) => {
switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'then':
case 'status': {
// We can't assign params over these properties because the VM and React use
// them to reason about the Promise.
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':
case 'status':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
break
}
default: {
Expand Down Expand Up @@ -246,11 +268,32 @@ function makeErroringExoticParams(

Object.keys(underlyingParams).forEach((prop) => {
switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'then':
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':
case 'status':
case 'value': {
// We can't assign params over these properties because the VM and React use
// them to reason about the Promise.

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
break
}
default: {
Expand Down Expand Up @@ -335,11 +378,32 @@ function makeUntrackedExoticParams(underlyingParams: Params): Promise<Params> {

Object.keys(underlyingParams).forEach((prop) => {
switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'then':
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':
case 'status': {
// These properties cannot be shadowed with a search param because they
// are necessary for ReactPromise's to work correctly with `use`
case 'status':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
break
}
default: {
Expand Down Expand Up @@ -370,11 +434,32 @@ function makeDynamicallyTrackedExoticParamsWithDevWarnings(

Object.keys(underlyingParams).forEach((prop) => {
switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'then':
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':
case 'status': {
// These properties cannot be shadowed with a search param because they
// are necessary for ReactPromise's to work correctly with `use`
case 'status':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
unproxiedProperties.push(prop)
break
}
Expand Down
107 changes: 101 additions & 6 deletions packages/next/src/server/request/search-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,33 @@ function makeAbortingExoticSearchParams(
annotateDynamicAccess(expression, prerenderStore)
return ReflectAdapter.get(target, prop, receiver)
}
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
return ReflectAdapter.get(target, prop, receiver)
}

default: {
if (typeof prop === 'string') {
const expression = describeStringPropertyAccess(
Expand Down Expand Up @@ -267,6 +294,32 @@ function makeErroringExoticSearchParams(
}

switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
return ReflectAdapter.get(target, prop, receiver)
}
case 'then': {
const expression =
'`await searchParams`, `searchParams.then`, or similar'
Expand Down Expand Up @@ -402,11 +455,32 @@ function makeUntrackedExoticSearchParams(

Object.keys(underlyingSearchParams).forEach((prop) => {
switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'then':
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':
case 'status': {
// These properties cannot be shadowed with a search param because they
// are necessary for ReactPromise's to work correctly with `use`
case 'status':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
break
}
default: {
Expand Down Expand Up @@ -503,11 +577,32 @@ function makeDynamicallyTrackedExoticSearchParamsWithDevWarnings(

Object.keys(underlyingSearchParams).forEach((prop) => {
switch (prop) {
// Object prototype
case 'hasOwnProperty':
case 'isPrototypeOf':
case 'propertyIsEnumerable':
case 'toString':
case 'valueOf':
case 'toLocaleString':

// Promise prototype
// fallthrough
case 'then':
case 'catch':
case 'finally':

// React Promise extension
// fallthrough
case 'value':
case 'status': {
// These properties cannot be shadowed with a search param because they
// are necessary for ReactPromise's to work correctly with `use`
case 'status':

// Common tested properties
// fallthrough
case 'toJSON':
case '$$typeof':
case '__esModule': {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
unproxiedProperties.push(prop)
break
}
Expand Down

0 comments on commit 58209bd

Please sign in to comment.