Skip to content

Commit 8ab2374

Browse files
gislerrocolinhacks
andauthored
fix(util): cross realm IsPlainObject check (#4627)
* fix(util): cross realm IsPlainObject check * Tweaks and benchmark * Tweak * Tweak --------- Co-authored-by: Colin McDonnell <[email protected]>
1 parent da4f921 commit 8ab2374

File tree

6 files changed

+76
-55
lines changed

6 files changed

+76
-55
lines changed

packages/bench/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ async function run() {
66
const files = process.argv[2].split(",").map((file) => import.meta.resolve(`./${file}`).replace("file://", ""));
77

88
for (const file of files) {
9+
// await $`pnpm tsx --conditions @zod/source ${file}`;
910
await $`pnpm tsx ${file}`;
1011
}
1112
}

packages/bench/object-moltar.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as z4 from "zod/v4";
2-
import * as z4lib from "./node_modules/zod/dist/esm/v4/index.js";
2+
import * as z4lib from "./node_modules/zod4/dist/esm/v4/classic/index.js";
33
import * as z3 from "zod3";
44
import { metabench } from "./metabench.js";
55

@@ -24,7 +24,7 @@ const z4LibSchema = z4lib.object({
2424
string: z4lib.string(),
2525
longString: z4lib.string(),
2626
boolean: z4lib.boolean(),
27-
deeplyNested: z4lib.strictObject({
27+
deeplyNested: z4lib.object({
2828
foo: z4lib.string(),
2929
num: z4lib.number(),
3030
bool: z4lib.boolean(),
@@ -38,40 +38,40 @@ const z4Schema = z4.object({
3838
string: z4.string(),
3939
longString: z4.string(),
4040
boolean: z4.boolean(),
41-
deeplyNested: z4.strictObject({
41+
deeplyNested: z4.object({
4242
foo: z4.string(),
4343
num: z4.number(),
4444
bool: z4.boolean(),
4545
}),
4646
});
4747

48-
const z4SchemaStrict = z4.strictObject({
49-
number: z4.number(),
50-
negNumber: z4.number(),
51-
maxNumber: z4.number(),
52-
string: z4.string(),
53-
longString: z4.string(),
54-
boolean: z4.boolean(),
55-
deeplyNested: z4.strictObject({
56-
foo: z4.string(),
57-
num: z4.number(),
58-
bool: z4.boolean(),
59-
}),
60-
});
48+
// const z4SchemaStrict = z4.strictObject({
49+
// number: z4.number(),
50+
// negNumber: z4.number(),
51+
// maxNumber: z4.number(),
52+
// string: z4.string(),
53+
// longString: z4.string(),
54+
// boolean: z4.boolean(),
55+
// deeplyNested: z4.strictObject({
56+
// foo: z4.string(),
57+
// num: z4.number(),
58+
// bool: z4.boolean(),
59+
// }),
60+
// });
6161

62-
const z4SchemaLoose = z4.object({
63-
number: z4.number(),
64-
negNumber: z4.number(),
65-
maxNumber: z4.number(),
66-
string: z4.string(),
67-
longString: z4.string(),
68-
boolean: z4.boolean(),
69-
deeplyNested: z4.object({
70-
foo: z4.string(),
71-
num: z4.number(),
72-
bool: z4.boolean(),
73-
}),
74-
});
62+
// const z4SchemaLoose = z4.object({
63+
// number: z4.number(),
64+
// negNumber: z4.number(),
65+
// maxNumber: z4.number(),
66+
// string: z4.string(),
67+
// longString: z4.string(),
68+
// boolean: z4.boolean(),
69+
// deeplyNested: z4.object({
70+
// foo: z4.string(),
71+
// num: z4.number(),
72+
// bool: z4.boolean(),
73+
// }),
74+
// });
7575

7676
const DATA = Array.from({ length: 1000 }, () =>
7777
Object.freeze({
@@ -97,9 +97,9 @@ const bench = metabench("z.object() safeParse", {
9797
zod3() {
9898
for (const _ of DATA) z3Schema.parse(_);
9999
},
100-
// zod4lib() {
101-
// for (const _ of DATA) z4LibSchema.parse(_);
102-
// },
100+
zod4lib() {
101+
for (const _ of DATA) z4LibSchema.parse(_);
102+
},
103103
zod4(){
104104
for (const _ of DATA) z4Schema.parse(_);
105105
}

packages/bench/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"arktype": "^2.1.19",
77
"valibot": "^1.0.0",
88
"zod": "workspace:*",
9-
"zodnext": "npm:zod@^3.25.0",
10-
"zod3": "npm:zod@^3.23.7"
9+
"zod3": "npm:zod@~3.24.0",
10+
"zod4": "npm:zod@latest"
1111
},
1212
"scripts": {
1313
"bench": "tsx --conditions @zod/source index.ts"

packages/bench/tsconfig.bench.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"noEmit": true,
55
"extendedDiagnostics": true,
66
"traceResolution": true,
7-
"customConditions": [],
7+
"customConditions": ["@zod/source"],
88
"skipDefaultLibCheck": true,
99
"skipLibCheck": true,
1010
}

packages/zod/src/v4/core/util.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,27 @@ export const allowsEval: { value: boolean } = cached(() => {
328328
}
329329
});
330330

331-
export function isPlainObject(data: any): data is Record<PropertyKey, unknown> {
332-
return (
333-
typeof data === "object" &&
334-
data !== null &&
335-
(Object.getPrototypeOf(data) === Object.prototype || Object.getPrototypeOf(data) === null)
336-
);
331+
function _isObject(o: any) {
332+
return Object.prototype.toString.call(o) === "[object Object]";
333+
}
334+
335+
export function isPlainObject(o: any): o is Record<PropertyKey, unknown> {
336+
if (isObject(o) === false) return false;
337+
338+
// modified constructor
339+
const ctor = o.constructor;
340+
if (ctor === undefined) return true;
341+
342+
// modified prototype
343+
const prot = ctor.prototype;
344+
if (isObject(prot) === false) return false;
345+
346+
// ctor doesn't have static `isPrototypeOf`
347+
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
348+
return false;
349+
}
350+
351+
return true;
337352
}
338353

339354
export function numKeys(data: any): number {

pnpm-lock.yaml

Lines changed: 19 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)