Skip to content

Commit c3df8ed

Browse files
committed
work with frozen intrinsics
1 parent cfdd411 commit c3df8ed

File tree

10 files changed

+124
-68
lines changed

10 files changed

+124
-68
lines changed

packages/ai/ai/src/Tool.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,33 @@ function filter(obj: any) {
14991499
return obj
15001500
}
15011501

1502+
// Utility for safely manipulating Error.stackTraceLimit in environments
1503+
// where intrinsics may be frozen (e.g., SES/hardened JavaScript)
1504+
const isStackTraceLimitWritable = (): boolean => {
1505+
const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit")
1506+
if (desc === undefined) {
1507+
return Object.isExtensible(Error)
1508+
}
1509+
return Object.prototype.hasOwnProperty.call(desc, "writable")
1510+
? desc.writable === true
1511+
: desc.set !== undefined
1512+
}
1513+
1514+
const canWriteStackTraceLimit = isStackTraceLimitWritable()
1515+
1516+
const withStackTraceLimit = <T>(limit: number, fn: () => T): T => {
1517+
if (!canWriteStackTraceLimit) {
1518+
return fn()
1519+
}
1520+
const prevLimit = Error.stackTraceLimit
1521+
try {
1522+
Error.stackTraceLimit = limit
1523+
return fn()
1524+
} finally {
1525+
Error.stackTraceLimit = prevLimit
1526+
}
1527+
}
1528+
15021529
/**
15031530
* **Unsafe**: This function will throw an error if an insecure property is
15041531
* found in the parsed JSON or if the provided JSON text is not parseable.
@@ -1508,11 +1535,5 @@ function filter(obj: any) {
15081535
*/
15091536
export const unsafeSecureJsonParse = (text: string): unknown => {
15101537
// Performance optimization, see https://github.com/fastify/secure-json-parse/pull/90
1511-
const { stackTraceLimit } = Error
1512-
Error.stackTraceLimit = 0
1513-
try {
1514-
return _parse(text)
1515-
} finally {
1516-
Error.stackTraceLimit = stackTraceLimit
1517-
}
1538+
return withStackTraceLimit(0, () => _parse(text))
15181539
}

packages/effect/src/Effect.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import * as option_ from "./internal/option.js"
4040
import * as query from "./internal/query.js"
4141
import * as runtime_ from "./internal/runtime.js"
4242
import * as schedule_ from "./internal/schedule.js"
43+
import * as StackTraceLimit from "./internal/stackTraceLimit.js"
4344
import * as internalTracer from "./internal/tracer.js"
4445
import type * as Layer from "./Layer.js"
4546
import type * as LogLevel from "./LogLevel.js"
@@ -13509,10 +13510,7 @@ export const Tag: <const Id extends string>(id: Id) => <
1350913510
: [X] extends [PromiseLike<infer A>] ? Effect<A, Cause.UnknownException, Self>
1351013511
: Effect<X, never, Self>
1351113512
} = (id) => () => {
13512-
const limit = Error.stackTraceLimit
13513-
Error.stackTraceLimit = 2
13514-
const creationError = new Error()
13515-
Error.stackTraceLimit = limit
13513+
const creationError = StackTraceLimit.withStackTraceLimit(2, () => new Error())
1351613514
function TagClass() {}
1351713515
Object.setPrototypeOf(TagClass, TagProto)
1351813516
TagClass.key = id
@@ -13668,10 +13666,7 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
1366813666
return function() {
1366913667
const [id, maker] = arguments
1367013668
const proxy = "accessors" in maker ? maker["accessors"] : false
13671-
const limit = Error.stackTraceLimit
13672-
Error.stackTraceLimit = 2
13673-
const creationError = new Error()
13674-
Error.stackTraceLimit = limit
13669+
const creationError = StackTraceLimit.withStackTraceLimit(2, () => new Error())
1367513670

1367613671
let patchState: "unchecked" | "plain" | "patched" = "unchecked"
1367713672
const TagClass: any = function(this: any, service: any) {
@@ -14628,16 +14623,10 @@ export const fn:
1462814623
name: string,
1462914624
options?: Tracer.SpanOptions
1463014625
) => fn.Gen & fn.NonGen) = function(nameOrBody: Function | string, ...pipeables: Array<any>) {
14631-
const limit = Error.stackTraceLimit
14632-
Error.stackTraceLimit = 2
14633-
const errorDef = new Error()
14634-
Error.stackTraceLimit = limit
14626+
const errorDef = StackTraceLimit.withStackTraceLimit(2, () => new Error())
1463514627
if (typeof nameOrBody !== "string") {
1463614628
return defineLength(nameOrBody.length, function(this: any, ...args: Array<any>) {
14637-
const limit = Error.stackTraceLimit
14638-
Error.stackTraceLimit = 2
14639-
const errorCall = new Error()
14640-
Error.stackTraceLimit = limit
14629+
const errorCall = StackTraceLimit.withStackTraceLimit(2, () => new Error())
1464114630
return fnApply({
1464214631
self: this,
1464314632
body: nameOrBody,
@@ -14659,10 +14648,7 @@ export const fn:
1465914648
body.length,
1466014649
({
1466114650
[name](this: any, ...args: Array<any>) {
14662-
const limit = Error.stackTraceLimit
14663-
Error.stackTraceLimit = 2
14664-
const errorCall = new Error()
14665-
Error.stackTraceLimit = limit
14651+
const errorCall = StackTraceLimit.withStackTraceLimit(2, () => new Error())
1466614652
return fnApply({
1466714653
self: this,
1466814654
body,

packages/effect/src/Micro.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { format, NodeInspectSymbol, toStringUnknown } from "./Inspectable.js"
2121
import * as InternalContext from "./internal/context.js"
2222
import * as doNotation from "./internal/doNotation.js"
2323
import { StructuralPrototype } from "./internal/effectable.js"
24+
import * as StackTraceLimit from "./internal/stackTraceLimit.js"
2425
import * as Option from "./Option.js"
2526
import type { Pipeable } from "./Pipeable.js"
2627
import { pipeArguments } from "./Pipeable.js"
@@ -2984,10 +2985,7 @@ export const withTrace: {
29842985
(name: string): <A, E, R>(self: Micro<A, E, R>) => Micro<A, E, R>
29852986
<A, E, R>(self: Micro<A, E, R>, name: string): Micro<A, E, R>
29862987
} = function() {
2987-
const prevLimit = globalThis.Error.stackTraceLimit
2988-
globalThis.Error.stackTraceLimit = 2
2989-
const error = new globalThis.Error()
2990-
globalThis.Error.stackTraceLimit = prevLimit
2988+
const error = StackTraceLimit.withStackTraceLimit(2, () => new globalThis.Error())
29912989
function generate(name: string, cause: MicroCause<any>) {
29922990
const stack = error.stack
29932991
if (!stack) {

packages/effect/src/internal/cause.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { AnySpan, Span } from "../Tracer.js"
1717
import type { NoInfer } from "../Types.js"
1818
import { getBugErrorMessage } from "./errors.js"
1919
import * as OpCodes from "./opCodes/cause.js"
20+
import * as StackTraceLimit from "./stackTraceLimit.js"
2021

2122
// -----------------------------------------------------------------------------
2223
// Models
@@ -900,8 +901,8 @@ export class PrettyError extends globalThis.Error implements Cause.PrettyError {
900901
span: undefined | Span = undefined
901902
constructor(originalError: unknown) {
902903
const originalErrorIsObject = typeof originalError === "object" && originalError !== null
903-
const prevLimit = Error.stackTraceLimit
904-
Error.stackTraceLimit = 1
904+
const prevLimit = StackTraceLimit.getStackTraceLimit()
905+
StackTraceLimit.setStackTraceLimit(1)
905906
super(
906907
prettyErrorMessage(originalError),
907908
originalErrorIsObject && "cause" in originalError && typeof originalError.cause !== "undefined"
@@ -911,7 +912,9 @@ export class PrettyError extends globalThis.Error implements Cause.PrettyError {
911912
if (this.message === "") {
912913
this.message = "An error has occurred"
913914
}
914-
Error.stackTraceLimit = prevLimit
915+
if (prevLimit !== undefined) {
916+
StackTraceLimit.setStackTraceLimit(prevLimit)
917+
}
915918
this.name = originalError instanceof Error ? originalError.name : "Error"
916919
if (originalErrorIsObject) {
917920
if (spanSymbol in originalError) {

packages/effect/src/internal/context.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type * as STM from "../STM.js"
1212
import type { NoInfer } from "../Types.js"
1313
import { EffectPrototype, effectVariance } from "./effectable.js"
1414
import * as option from "./option.js"
15+
import * as StackTraceLimit from "./stackTraceLimit.js"
1516

1617
/** @internal */
1718
export const TagTypeId: C.TagTypeId = Symbol.for("effect/Context/Tag") as C.TagTypeId
@@ -67,10 +68,7 @@ export const ReferenceProto: any = {
6768

6869
/** @internal */
6970
export const makeGenericTag = <Identifier, Service = Identifier>(key: string): C.Tag<Identifier, Service> => {
70-
const limit = Error.stackTraceLimit
71-
Error.stackTraceLimit = 2
72-
const creationError = new Error()
73-
Error.stackTraceLimit = limit
71+
const creationError = StackTraceLimit.withStackTraceLimit(2, () => new Error())
7472
const tag = Object.create(TagProto)
7573
Object.defineProperty(tag, "stack", {
7674
get() {
@@ -83,10 +81,7 @@ export const makeGenericTag = <Identifier, Service = Identifier>(key: string): C
8381

8482
/** @internal */
8583
export const Tag = <const Id extends string>(id: Id) => <Self, Shape>(): C.TagClass<Self, Id, Shape> => {
86-
const limit = Error.stackTraceLimit
87-
Error.stackTraceLimit = 2
88-
const creationError = new Error()
89-
Error.stackTraceLimit = limit
84+
const creationError = StackTraceLimit.withStackTraceLimit(2, () => new Error())
9085

9186
function TagClass() {}
9287
Object.setPrototypeOf(TagClass, TagProto)
@@ -104,10 +99,7 @@ export const Reference = <Self>() =>
10499
<const Id extends string, Service>(id: Id, options: {
105100
readonly defaultValue: () => Service
106101
}): C.ReferenceClass<Self, Id, Service> => {
107-
const limit = Error.stackTraceLimit
108-
Error.stackTraceLimit = 2
109-
const creationError = new Error()
110-
Error.stackTraceLimit = limit
102+
const creationError = StackTraceLimit.withStackTraceLimit(2, () => new Error())
111103

112104
function ReferenceClass() {}
113105
Object.setPrototypeOf(ReferenceClass, ReferenceProto)

packages/effect/src/internal/core-effect.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import * as fiberRefsPatch from "./fiberRefs/patch.js"
3838
import type { FiberRuntime } from "./fiberRuntime.js"
3939
import * as metricLabel from "./metric/label.js"
4040
import * as runtimeFlags from "./runtimeFlags.js"
41+
import * as StackTraceLimit from "./stackTraceLimit.js"
4142
import * as internalTracer from "./tracer.js"
4243

4344
/* @internal */
@@ -2241,10 +2242,7 @@ export const functionWithSpan = <Args extends Array<any>, Ret extends Effect.Eff
22412242
(function(this: any) {
22422243
let captureStackTrace: LazyArg<string | undefined> | boolean = options.captureStackTrace ?? false
22432244
if (options.captureStackTrace !== false) {
2244-
const limit = Error.stackTraceLimit
2245-
Error.stackTraceLimit = 2
2246-
const error = new Error()
2247-
Error.stackTraceLimit = limit
2245+
const error = StackTraceLimit.withStackTraceLimit(2, () => new Error())
22482246
let cache: false | string = false
22492247
captureStackTrace = () => {
22502248
if (cache !== false) {

packages/effect/src/internal/layer.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import * as OpCodes from "./opCodes/layer.js"
3333
import * as ref from "./ref.js"
3434
import * as runtime from "./runtime.js"
3535
import * as runtimeFlags from "./runtimeFlags.js"
36+
import * as StackTraceLimit from "./stackTraceLimit.js"
3637
import * as synchronized from "./synchronizedRef.js"
3738
import * as tracer from "./tracer.js"
3839

@@ -693,10 +694,10 @@ const mockImpl = <I, S extends object>(tag: Context.Tag<I, S>, service: Layer.Pa
693694
if (prop in target) {
694695
return target[prop as keyof S]
695696
}
696-
const prevLimit = Error.stackTraceLimit
697-
Error.stackTraceLimit = 2
698-
const error = new Error(`${tag.key}: Unimplemented method "${prop.toString()}"`)
699-
Error.stackTraceLimit = prevLimit
697+
const error = StackTraceLimit.withStackTraceLimit(
698+
2,
699+
() => new Error(`${tag.key}: Unimplemented method "${prop.toString()}"`)
700+
)
700701
error.name = "UnimplementedError"
701702
return makeUnimplemented(error)
702703
},

packages/effect/src/internal/runtime.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import * as executionStrategy from "./executionStrategy.js"
2323
import * as FiberRuntime from "./fiberRuntime.js"
2424
import * as fiberScope from "./fiberScope.js"
2525
import * as OpCodes from "./opCodes/effect.js"
26+
import * as StackTraceLimit from "./stackTraceLimit.js"
2627
import * as runtimeFlags from "./runtimeFlags.js"
2728
import * as supervisor_ from "./supervisor.js"
2829

@@ -179,11 +180,7 @@ class AsyncFiberExceptionImpl<A, E = never> extends Error implements Runtime.Asy
179180
}
180181

181182
const asyncFiberException = <A, E>(fiber: Fiber.RuntimeFiber<A, E>): Runtime.AsyncFiberException<A, E> => {
182-
const limit = Error.stackTraceLimit
183-
Error.stackTraceLimit = 0
184-
const error = new AsyncFiberExceptionImpl(fiber)
185-
Error.stackTraceLimit = limit
186-
return error
183+
return StackTraceLimit.withStackTraceLimit(0, () => new AsyncFiberExceptionImpl(fiber))
187184
}
188185

189186
/** @internal */
@@ -230,11 +227,7 @@ class FiberFailureImpl extends Error implements Runtime.FiberFailure {
230227

231228
/** @internal */
232229
export const fiberFailure = <E>(cause: Cause.Cause<E>): Runtime.FiberFailure => {
233-
const limit = Error.stackTraceLimit
234-
Error.stackTraceLimit = 0
235-
const error = new FiberFailureImpl(cause)
236-
Error.stackTraceLimit = limit
237-
return error
230+
return StackTraceLimit.withStackTraceLimit(0, () => new FiberFailureImpl(cause))
238231
}
239232

240233
/** @internal */
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Utility for safely manipulating Error.stackTraceLimit in environments
3+
* where intrinsics may be frozen (e.g., SES/hardened JavaScript).
4+
* @internal
5+
*/
6+
7+
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor
8+
const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty
9+
const ObjectIsExtensible = Object.isExtensible
10+
11+
/**
12+
* Check if Error.stackTraceLimit is writable.
13+
* Returns false if the property is frozen, non-writable, or Error is non-extensible.
14+
* @internal
15+
*/
16+
export const isStackTraceLimitWritable = (): boolean => {
17+
const desc = ObjectGetOwnPropertyDescriptor(Error, "stackTraceLimit")
18+
if (desc === undefined) {
19+
return ObjectIsExtensible(Error)
20+
}
21+
22+
return ObjectPrototypeHasOwnProperty.call(desc, "writable")
23+
? desc.writable === true
24+
: desc.set !== undefined
25+
}
26+
27+
// Cache the check result since it won't change during runtime
28+
const canWriteStackTraceLimit = isStackTraceLimitWritable()
29+
30+
/**
31+
* Safely set Error.stackTraceLimit if possible, otherwise no-op.
32+
* @internal
33+
*/
34+
export const setStackTraceLimit = (value: number): void => {
35+
if (canWriteStackTraceLimit) {
36+
Error.stackTraceLimit = value
37+
}
38+
}
39+
40+
/**
41+
* Get the current Error.stackTraceLimit value.
42+
* Returns undefined if the property doesn't exist.
43+
* @internal
44+
*/
45+
export const getStackTraceLimit = (): number | undefined => {
46+
return Error.stackTraceLimit
47+
}
48+
49+
/**
50+
* Execute a function with a temporarily modified Error.stackTraceLimit.
51+
* If the limit cannot be modified (frozen intrinsics), executes the function without modification.
52+
* @internal
53+
*/
54+
export const withStackTraceLimit = <T>(limit: number, fn: () => T): T => {
55+
if (!canWriteStackTraceLimit) {
56+
return fn()
57+
}
58+
59+
const prevLimit = Error.stackTraceLimit
60+
try {
61+
Error.stackTraceLimit = limit
62+
return fn()
63+
} finally {
64+
Error.stackTraceLimit = prevLimit
65+
}
66+
}

packages/effect/src/internal/tracer.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type * as Exit from "../Exit.js"
66
import { constFalse } from "../Function.js"
77
import type * as Option from "../Option.js"
88
import type * as Tracer from "../Tracer.js"
9+
import * as StackTraceLimit from "./stackTraceLimit.js"
910

1011
/** @internal */
1112
export const TracerTypeId: Tracer.TracerTypeId = Symbol.for("effect/Tracer") as Tracer.TracerTypeId
@@ -122,10 +123,7 @@ export const addSpanStackTrace = (options: Tracer.SpanOptions | undefined): Trac
122123
} else if (options?.captureStackTrace !== undefined && typeof options.captureStackTrace !== "boolean") {
123124
return options
124125
}
125-
const limit = Error.stackTraceLimit
126-
Error.stackTraceLimit = 3
127-
const traceError = new Error()
128-
Error.stackTraceLimit = limit
126+
const traceError = StackTraceLimit.withStackTraceLimit(3, () => new Error())
129127
let cache: false | string = false
130128
return {
131129
...options,

0 commit comments

Comments
 (0)