Skip to content

Commit

Permalink
Defensively install patches (#71043)
Browse files Browse the repository at this point in the history
Some builtins and node library modules may make patching impossible by
freezing objects or making propeties readonly. The patch installation is
done as early as possible before we know if we're even going to need the
patch and thus is done unconditionally whether DIO is enabled or not.
This is generally preferable to how for instance the fetch patch is
installed which is observable becasue user code can run before it.

In this change all the patch installs are for dynamicIO augmented
behavior. We can allow the process to run but will warn that
`experimental.dynamicIO` will not have expected behavior when calling
the functions that were not installed.
  • Loading branch information
gnoff authored and kdy1 committed Oct 10, 2024
1 parent d1885d5 commit 20a99ab
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 53 deletions.
10 changes: 8 additions & 2 deletions packages/next/src/server/node-environment-extensions/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,11 @@ function createDate(originalConstructor: typeof Date): typeof Date {
return newConstructor as typeof Date
}

// eslint-disable-next-line no-native-reassign
Date = createDate(Date)
try {
// eslint-disable-next-line no-native-reassign
Date = createDate(Date)
} catch {
console.error(
'Failed to install `Date` class extension. When using `experimental.dynamicIO`, APIs that read the current time will not correctly trigger dynamic behavior.'
)
}
118 changes: 86 additions & 32 deletions packages/next/src/server/node-environment-extensions/node-crypto.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,105 @@ if (process.env.NEXT_RUNTIME === 'edge') {
// crypto.getRandomValues which is extended in web-crypto.tsx

// require('node:crypto').randomUUID is not an alias for crypto.randomUUID
const _randomUUID = nodeCrypto.randomUUID
nodeCrypto.randomUUID = function randomUUID() {
io("`require('node:crypto').randomUUID()`")
return _randomUUID.apply(this, arguments as any)

const randomUUIDExpression = "`require('node:crypto').randomUUID()`"
try {
const _randomUUID = nodeCrypto.randomUUID
nodeCrypto.randomUUID = function randomUUID() {
io(randomUUIDExpression)
return _randomUUID.apply(this, arguments as any)
}
} catch {
console.error(
`Failed to install ${randomUUIDExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}

const _randomBytes = nodeCrypto.randomBytes
nodeCrypto.randomBytes = function randomBytes() {
if (typeof arguments[1] !== 'function') {
// randomBytes is sync if the second arg is undefined
io("`require('node:crypto').randomBytes(size)`")
const randomBytesExpression = "`require('node:crypto').randomBytes(size)`"
try {
const _randomBytes = nodeCrypto.randomBytes
nodeCrypto.randomBytes = function randomBytes() {
if (typeof arguments[1] !== 'function') {
// randomBytes is sync if the second arg is undefined
io(randomBytesExpression)
}
return _randomBytes.apply(this, arguments as any)
}
return _randomBytes.apply(this, arguments as any)
} catch {
console.error(
`Failed to install ${randomBytesExpression} extension. When using \`experimental.dynamicIO\` calling this function without a callback argument will not correctly trigger dynamic behavior.`
)
}

const _randomFillSync = nodeCrypto.randomFillSync
nodeCrypto.randomFillSync = function randomFillSync() {
io("`require('node:crypto').randomFillSync(...)`")
return _randomFillSync.apply(this, arguments as any)
const randomFillSyncExpression =
"`require('node:crypto').randomFillSync(...)`"
try {
const _randomFillSync = nodeCrypto.randomFillSync
nodeCrypto.randomFillSync = function randomFillSync() {
io(randomFillSyncExpression)
return _randomFillSync.apply(this, arguments as any)
}
} catch {
console.error(
`Failed to install ${randomFillSyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}

const _randomInt = nodeCrypto.randomInt
nodeCrypto.randomInt = function randomInt() {
if (typeof arguments[2] !== 'function') {
// randomInt is sync if the third arg is undefined
io("`require('node:crypto').randomInt(min, max)`")
const randomIntExpression = "`require('node:crypto').randomInt(min, max)`"
try {
const _randomInt = nodeCrypto.randomInt
nodeCrypto.randomInt = function randomInt() {
if (typeof arguments[2] !== 'function') {
// randomInt is sync if the third arg is undefined
io(randomIntExpression)
}
return _randomInt.apply(this, arguments as any)
}
return _randomInt.apply(this, arguments as any)
} catch {
console.error(
`Failed to install ${randomBytesExpression} extension. When using \`experimental.dynamicIO\` calling this function without a callback argument will not correctly trigger dynamic behavior.`
)
}

const _generatePrimeSync = nodeCrypto.generatePrimeSync
nodeCrypto.generatePrimeSync = function generatePrimeSync() {
io("`require('node:crypto').generatePrimeSync(...)`")
return _generatePrimeSync.apply(this, arguments as any)
const generatePrimeSyncExpression =
"`require('node:crypto').generatePrimeSync(...)`"
try {
const _generatePrimeSync = nodeCrypto.generatePrimeSync
nodeCrypto.generatePrimeSync = function generatePrimeSync() {
io(generatePrimeSyncExpression)
return _generatePrimeSync.apply(this, arguments as any)
}
} catch {
console.error(
`Failed to install ${generatePrimeSyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}

const _generateKeyPairSync = nodeCrypto.generateKeyPairSync
nodeCrypto.generateKeyPairSync = function generateKeyPairSync() {
io("`require('node:crypto').generateKeyPairSync(...)`")
return _generateKeyPairSync.apply(this, arguments as any)
const generateKeyPairSyncExpression =
"`require('node:crypto').generateKeyPairSync(...)`"
try {
const _generateKeyPairSync = nodeCrypto.generateKeyPairSync
nodeCrypto.generateKeyPairSync = function generateKeyPairSync() {
io(generateKeyPairSyncExpression)
return _generateKeyPairSync.apply(this, arguments as any)
}
} catch {
console.error(
`Failed to install ${generateKeyPairSyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}

const _generateKeySync = nodeCrypto.generateKeySync
nodeCrypto.generateKeySync = function generateKeySync() {
io("`require('node:crypto').generateKeySync(...)`")
return _generateKeySync.apply(this, arguments as any)
const generateKeySyncExpression =
"`require('node:crypto').generateKeySync(...)`"
try {
const _generateKeySync = nodeCrypto.generateKeySync
nodeCrypto.generateKeySync = function generateKeySync() {
io(generateKeySyncExpression)
return _generateKeySync.apply(this, arguments as any)
}
} catch {
console.error(
`Failed to install ${generateKeySyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}
}
25 changes: 15 additions & 10 deletions packages/next/src/server/node-environment-extensions/random.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@

import { io } from './utils'

const _random = Math.random
const expression = '`Math.random()`'
try {
const _random = Math.random
Math.random = function random() {
io(expression)
return _random.apply(null, arguments as any)

Math.random = function random() {
io('`Math.random()`')
return _random.apply(null, arguments as any)

// We bind here to alter the `toString` printing to match `Math.random`'s native `toString`.
// eslint-disable-next-line no-extra-bind
}.bind(null)

Object.defineProperty(Math.random, 'name', { value: 'random' })
// We bind here to alter the `toString` printing to match `Math.random`'s native `toString`.
// eslint-disable-next-line no-extra-bind
}.bind(null)
Object.defineProperty(Math.random, 'name', { value: 'random' })
} catch {
console.error(
`Failed to install ${expression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,28 @@ if (process.env.NEXT_RUNTIME === 'edge') {
}
}

const originalGetRandomValues = webCrypto.getRandomValues
webCrypto.getRandomValues = function getRandomValues() {
io('`crypto.getRandomValues()`')
return originalGetRandomValues.apply(webCrypto, arguments as any)
const getRandomValuesExpression = '`crypto.getRandomValues()`'
try {
const _getRandomValues = webCrypto.getRandomValues
webCrypto.getRandomValues = function getRandomValues() {
io(getRandomValuesExpression)
return _getRandomValues.apply(webCrypto, arguments as any)
}
} catch {
console.error(
`Failed to install ${getRandomValuesExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}

const _randomUUID = webCrypto.randomUUID
webCrypto.randomUUID = function randomUUID() {
io('`crypto.randomUUID()`')
return _randomUUID.apply(webCrypto, arguments as any)
} as typeof _randomUUID
const randomUUIDExpression = '`crypto.randomUUID()`'
try {
const _randomUUID = webCrypto.randomUUID
webCrypto.randomUUID = function randomUUID() {
io(randomUUIDExpression)
return _randomUUID.apply(webCrypto, arguments as any)
} as typeof _randomUUID
} catch {
console.error(
`Failed to install ${getRandomValuesExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
)
}

0 comments on commit 20a99ab

Please sign in to comment.