Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions packages/core/src/integrations/functiontostring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Client } from '../client';
import { getClient } from '../currentScopes';
import { defineIntegration } from '../integration';
import type { IntegrationFn } from '../types/integration';
import type { WrappedFunction } from '../types/wrappedfunction';
import { getOriginalFunction } from '../utils/object';

const INTEGRATION_NAME = 'FunctionToString' as const;
Expand All @@ -18,24 +19,22 @@ const _functionToStringIntegration = (() => {
// intrinsics (like Function.prototype) might be immutable in some environments
// e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
try {
Function.prototype.toString = new Proxy(originalFunctionToString, {
apply(target, thisArg, args) {
const originalFunction = getOriginalFunction(thisArg);
let context = thisArg;
Function.prototype.toString = function (this: WrappedFunction, ...args: unknown[]): string {
const originalFunction = getOriginalFunction(this);
let unwrappedFunction: WrappedFunction | undefined;

try {
if (SETUP_CLIENTS.has(getClient()!) && originalFunction) {
context = originalFunction;
}
} catch {
// Reading the Sentry carrier off `getClient()` can throw a `SecurityError` when `this` (or the global
// object) is a `WindowProxy` whose browsing context was navigated cross-origin. The native
// `toString` never throws here, so fall back to it to avoid turning harmless introspection into noise.
try {
if (SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined) {
unwrappedFunction = originalFunction;
}
} catch {
// Reading the Sentry carrier off `getClient()` can throw a `SecurityError` when `this` (or the global
// object) is a `WindowProxy` whose browsing context was navigated cross-origin. The native
// `toString` never throws here, so fall back to it to avoid turning harmless introspection into noise.
}

return Reflect.apply(target, context, args);
},
});
return originalFunctionToString.apply(unwrappedFunction ?? this, args);
};
} catch {
// ignore errors here, just don't patch this
}
Expand Down
17 changes: 17 additions & 0 deletions packages/core/test/lib/integrations/functiontostring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('FunctionToString', () => {

afterEach(() => {
vi.mocked(currentScopes.getClient).mockClear();
vi.restoreAllMocks();
});

afterAll(() => {
Expand Down Expand Up @@ -67,6 +68,22 @@ describe('FunctionToString', () => {
expect(foo.bar.toString()).not.toBe(originalFunction);
});

it('does not recurse when Reflect.apply performs a function toString check', () => {
function inspectedFunction(): void {}

const fts = functionToStringIntegration();
getClient()?.addIntegration(fts);
const expected = inspectedFunction.toString();
const originalReflectApply = Reflect.apply;

vi.spyOn(Reflect, 'apply').mockImplementation((target, thisArgument, argumentsList) => {
target.toString();
return originalReflectApply(target, thisArgument, argumentsList);
});

expect(inspectedFunction.toString()).toBe(expected);
});

it('falls back to native toString and does not throw when the carrier read throws', () => {
const foo = {
bar(wat: boolean): boolean {
Expand Down
Loading