From 45f9f4c02f89e6de741a66db14330a6e3854ff12 Mon Sep 17 00:00:00 2001 From: cyx Date: Fri, 8 Sep 2023 10:25:04 +0800 Subject: [PATCH 1/4] fix(utils): Prevent iterating over VueViewModel --- packages/utils/src/is.ts | 10 +++++++++ packages/utils/src/normalize.ts | 6 +++++- packages/utils/test/is.test.ts | 17 ++++++++++++++++ packages/utils/test/normalize.test.ts | 29 +++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/is.ts b/packages/utils/src/is.ts index 350826cb567c..cbaffb5323d7 100644 --- a/packages/utils/src/is.ts +++ b/packages/utils/src/is.ts @@ -179,3 +179,13 @@ export function isInstanceOf(wat: any, base: any): boolean { return false; } } + +/** + * Checks whether given value's type is a Vue ViewModel. + * + * @param wat A value to be checked. + * @returns A boolean representing the result. + */ +export function isVueViewModel(wat: unknown): boolean { + return isPlainObject(wat) && !!(wat._isVue || wat.__isVue); +} diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts index 1f200640dbb1..7c1adaa32ccc 100644 --- a/packages/utils/src/normalize.ts +++ b/packages/utils/src/normalize.ts @@ -1,6 +1,6 @@ import type { Primitive } from '@sentry/types'; -import { isNaN, isSyntheticEvent } from './is'; +import { isNaN, isSyntheticEvent, isVueViewModel } from './is'; import type { MemoFunc } from './memo'; import { memoBuilder } from './memo'; import { convertToPlainObject } from './object'; @@ -214,6 +214,10 @@ function stringifyValue( return '[Document]'; } + if (isVueViewModel(value)) { + return '[VueViewModel]'; + } + // React's SyntheticEvent thingy if (isSyntheticEvent(value)) { return '[SyntheticEvent]'; diff --git a/packages/utils/test/is.test.ts b/packages/utils/test/is.test.ts index a8f9984e5c12..dcf1d44c2acb 100644 --- a/packages/utils/test/is.test.ts +++ b/packages/utils/test/is.test.ts @@ -7,6 +7,7 @@ import { isNaN, isPrimitive, isThenable, + isVueViewModel, } from '../src/is'; import { supportsDOMError, supportsDOMException, supportsErrorEvent } from '../src/supports'; import { resolvedSyncPromise } from '../src/syncpromise'; @@ -134,3 +135,19 @@ describe('isNaN()', () => { expect(isNaN(new Date())).toEqual(false); }); }); + +describe('isVueViewModel()', () => { + test('should work as advertised', () => { + expect(isVueViewModel({ _isVue: true })).toEqual(true); + expect(isVueViewModel({ __isVue: true })).toEqual(true); + + expect(isNaN(null)).toEqual(false); + expect(isNaN(true)).toEqual(false); + expect(isNaN('foo')).toEqual(false); + expect(isNaN(42)).toEqual(false); + expect(isNaN({})).toEqual(false); + expect(isNaN([])).toEqual(false); + expect(isNaN(new Error('foo'))).toEqual(false); + expect(isNaN(new Date())).toEqual(false); + }); +}); diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index c1c90a7ec07d..abc1ee3965e2 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -476,6 +476,17 @@ describe('normalize()', () => { foo: '[SyntheticEvent]', }); }); + + test('known classes like `VueViewModel`', () => { + const obj = { + foo: { + _isVue: true, + }, + }; + expect(normalize(obj)).toEqual({ + foo: '[VueViewModel]', + }); + }); }); describe('can limit object to depth', () => { @@ -618,6 +629,24 @@ describe('normalize()', () => { }); }); + test('normalizes value on every iteration of decycle and takes care of things like `VueViewModel`', () => { + const obj = { + foo: { + _isVue: true, + }, + baz: NaN, + qux: function qux(): void { + /* no-empty */ + }, + }; + const result = normalize(obj); + expect(result).toEqual({ + foo: '[VueViewModel]', + baz: '[NaN]', + qux: '[Function: qux]', + }); + }); + describe('skips normalizing objects marked with a non-enumerable property __sentry_skip_normalization__', () => { test('by leaving non-serializable values intact', () => { const someFun = () => undefined; From c92da7aecf573b0603730df45009df8a19a542f3 Mon Sep 17 00:00:00 2001 From: cyx Date: Tue, 12 Sep 2023 18:24:19 +0800 Subject: [PATCH 2/4] fix(utils): Prevent iterating over VueViewModel fro Vue 3 --- packages/utils/src/is.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/is.ts b/packages/utils/src/is.ts index cbaffb5323d7..61a94053a265 100644 --- a/packages/utils/src/is.ts +++ b/packages/utils/src/is.ts @@ -180,6 +180,12 @@ export function isInstanceOf(wat: any, base: any): boolean { } } +interface VueViewModel { + // Vue3 + __isVue?: boolean; + // Vue2 + _isVue?: boolean; +} /** * Checks whether given value's type is a Vue ViewModel. * @@ -187,5 +193,6 @@ export function isInstanceOf(wat: any, base: any): boolean { * @returns A boolean representing the result. */ export function isVueViewModel(wat: unknown): boolean { - return isPlainObject(wat) && !!(wat._isVue || wat.__isVue); + // Not using Object.prototype.toString because in Vue 3 it would read the instance's Symbol(Symbol.toStringTag) property. + return !!(typeof wat === 'object' && wat !== null && ((wat as VueViewModel).__isVue || (wat as VueViewModel)._isVue)); } From ada46f4e9c6a663faf8a3963998087e89a1be21c Mon Sep 17 00:00:00 2001 From: cyx Date: Tue, 12 Sep 2023 18:32:04 +0800 Subject: [PATCH 3/4] perf(utils): Optimize special handling arguments for Vue in consoleBreadcrumb 1. Preserve the original behavior of console. 2. Centralize special handling for Vue in the utils module. --- packages/browser/src/integrations/breadcrumbs.ts | 12 ------------ packages/utils/src/string.ts | 13 +++++++++++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index e41bedc8bf1c..f71361b7d96e 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -183,18 +183,6 @@ function _domBreadcrumb(dom: BreadcrumbsOptions['dom']): (handlerData: HandlerDa * Creates breadcrumbs from console API calls */ function _consoleBreadcrumb(handlerData: HandlerData & { args: unknown[]; level: string }): void { - // This is a hack to fix a Vue3-specific bug that causes an infinite loop of - // console warnings. This happens when a Vue template is rendered with - // an undeclared variable, which we try to stringify, ultimately causing - // Vue to issue another warning which repeats indefinitely. - // see: https://github.com/getsentry/sentry-javascript/pull/6010 - // see: https://github.com/getsentry/sentry-javascript/issues/5916 - for (let i = 0; i < handlerData.args.length; i++) { - if (handlerData.args[i] === 'ref=Ref<') { - handlerData.args[i + 1] = 'viewRef'; - break; - } - } const breadcrumb = { category: 'console', data: { diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 73efe8ef625e..743b25c3deef 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -1,4 +1,4 @@ -import { isRegExp, isString } from './is'; +import { isRegExp, isString, isVueViewModel } from './is'; export { escapeStringForRegex } from './vendor/escapeStringForRegex'; @@ -76,7 +76,16 @@ export function safeJoin(input: any[], delimiter?: string): string { for (let i = 0; i < input.length; i++) { const value = input[i]; try { - output.push(String(value)); + // This is a hack to fix a Vue3-specific bug that causes an infinite loop of + // console warnings. This happens when a Vue template is rendered with + // an undeclared variable, which we try to stringify, ultimately causing + // Vue to issue another warning which repeats indefinitely. + // see: https://github.com/getsentry/sentry-javascript/pull/8981 + if (isVueViewModel(value)) { + output.push('[VueViewModel]'); + } else { + output.push(String(value)); + } } catch (e) { output.push('[value cannot be serialized]'); } From bae8c65dd09c852ce412c7144a5f9feb8ac0ceb9 Mon Sep 17 00:00:00 2001 From: cyx Date: Tue, 12 Sep 2023 18:35:49 +0800 Subject: [PATCH 4/4] fix(utils): Streamline testing for isVueViewModel --- packages/utils/test/is.test.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/utils/test/is.test.ts b/packages/utils/test/is.test.ts index dcf1d44c2acb..c0482d2d4e95 100644 --- a/packages/utils/test/is.test.ts +++ b/packages/utils/test/is.test.ts @@ -141,13 +141,6 @@ describe('isVueViewModel()', () => { expect(isVueViewModel({ _isVue: true })).toEqual(true); expect(isVueViewModel({ __isVue: true })).toEqual(true); - expect(isNaN(null)).toEqual(false); - expect(isNaN(true)).toEqual(false); - expect(isNaN('foo')).toEqual(false); - expect(isNaN(42)).toEqual(false); - expect(isNaN({})).toEqual(false); - expect(isNaN([])).toEqual(false); - expect(isNaN(new Error('foo'))).toEqual(false); - expect(isNaN(new Date())).toEqual(false); + expect(isVueViewModel({ foo: true })).toEqual(false); }); });