Found by the package audit (while fixing #10434) on Perry 7661bc0 (v0.5.1589), Linux x64. Perry's Web API
objects have no Symbol.toStringTag, so Object.prototype.toString.call(x) yields [object Object] and
x[Symbol.toStringTag] is undefined. ES built-ins (Map, Promise, ArrayBuffer, DataView) produce the right
[object …] string but also report undefined for the Symbol.toStringTag property itself.
Reproduction
main.ts:
const vals: [string, any][] = [
["URL", new URL("http://x/")], ["URLSearchParams", new URLSearchParams("a=1")], ["Headers", new Headers()],
["TextEncoder", new TextEncoder()], ["TextDecoder", new TextDecoder()], ["Blob", new Blob(["a"])],
["AbortController", new AbortController()], ["AbortSignal", new AbortController().signal], ["Map", new Map()],
["Promise", Promise.resolve()], ["ArrayBuffer", new ArrayBuffer(1)], ["Uint8Array", new Uint8Array(1)],
["Request", new Request("http://x/")], ["Response", new Response("x")], ["FormData", new FormData()],
["EventTarget", new EventTarget()], ["Event", new Event("x")], ["DataView", new DataView(new ArrayBuffer(1))],
];
for (const [n, v] of vals) console.log(n, Object.prototype.toString.call(v), String(v?.[Symbol.toStringTag]));
node main.ts
PERRY_NO_AUTO_OPTIMIZE=1 perry compile main.ts -o main && ./main
Expected (Node 26.5.1)
URL [object URL] URL
URLSearchParams [object URLSearchParams] URLSearchParams
Headers [object Headers] Headers
TextEncoder [object TextEncoder] TextEncoder
TextDecoder [object TextDecoder] TextDecoder
Blob [object Blob] Blob
AbortController [object AbortController] AbortController
AbortSignal [object AbortSignal] AbortSignal
Map [object Map] Map
Promise [object Promise] Promise
ArrayBuffer [object ArrayBuffer] ArrayBuffer
Uint8Array [object Uint8Array] Uint8Array
Request [object Request] Request
Response [object Response] Response
FormData [object FormData] FormData
EventTarget [object EventTarget] EventTarget
Event [object Event] Event
DataView [object DataView] DataView
Actual (Perry)
URL [object Object] undefined
URLSearchParams [object Object] undefined
Headers [object Object] undefined
TextEncoder [object Object] undefined
TextDecoder [object Object] undefined
Blob [object Object] undefined
AbortController [object Object] undefined
AbortSignal [object Object] undefined
Map [object Map] undefined
Promise [object Promise] undefined
ArrayBuffer [object ArrayBuffer] undefined
Uint8Array [object Uint8Array] Uint8Array
Request [object Object] undefined
Response [object Object] undefined
FormData [object Object] undefined
EventTarget [object Object] undefined
Event [object Object] undefined
DataView [object DataView] undefined
Impact
Object.prototype.toString.call(x) is the standard cross-realm type check used by utility and HTTP libraries
(kindOf, isURLSearchParams, isFormData, isBlob, lodash baseGetTag). axios 1.19.0 decides how to serialize a
request body this way: a URLSearchParams body is sent as JSON instead of application/x-www-form-urlencoded.
Similar branches exist in form-data, undici, node-fetch, got, superagent and lodash.
Notes
- Fix shape: define
Symbol.toStringTag (non-enumerable, non-writable, configurable string) on each built-in's
prototype, and have the Object.prototype.toString fast path for ES built-ins also expose the property via
x[Symbol.toStringTag]. A test that sweeps every global constructor exposed by the runtime against Node's tags
would catch the rest of the population.
Found by the package audit (while fixing #10434) on Perry 7661bc0 (v0.5.1589), Linux x64. Perry's Web API
objects have no
Symbol.toStringTag, soObject.prototype.toString.call(x)yields[object Object]andx[Symbol.toStringTag]isundefined. ES built-ins (Map,Promise,ArrayBuffer,DataView) produce the right[object …]string but also reportundefinedfor theSymbol.toStringTagproperty itself.Reproduction
main.ts:node main.ts PERRY_NO_AUTO_OPTIMIZE=1 perry compile main.ts -o main && ./mainExpected (Node 26.5.1)
Actual (Perry)
Impact
Object.prototype.toString.call(x)is the standard cross-realm type check used by utility and HTTP libraries(
kindOf,isURLSearchParams,isFormData,isBlob, lodashbaseGetTag). axios 1.19.0 decides how to serialize arequest body this way: a
URLSearchParamsbody is sent as JSON instead ofapplication/x-www-form-urlencoded.Similar branches exist in form-data, undici, node-fetch, got, superagent and lodash.
Notes
Symbol.toStringTag(non-enumerable, non-writable, configurable string) on each built-in'sprototype, and have the
Object.prototype.toStringfast path for ES built-ins also expose the property viax[Symbol.toStringTag]. A test that sweeps every global constructor exposed by the runtime against Node's tagswould catch the rest of the population.