Skip to content

Commit 01cac4f

Browse files
authored
ref: Create safe isInstanceOf util (getsentry#2331)
* ref: Create safe isInstanceOf util * test: Add isInstanceOf tests * misc: Changelog
1 parent 645ff53 commit 01cac4f

File tree

10 files changed

+62
-22
lines changed

10 files changed

+62
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [browser] feat: Refactor breadcrumbs integration to allow for custom handlers
88
- [utils] feat: Move insturment to utils
99
- [apm] feat: Add `@sentry/apm` package
10+
- [utils] feat: Add `isInstanceOf` util for safety reasons
1011

1112
## 5.9.1
1213

packages/apm/src/hubextensions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getMainCarrier, Hub } from '@sentry/hub';
22
import { SpanContext } from '@sentry/types';
3+
import { isInstanceOf } from '@sentry/utils';
34

45
import { Span } from './span';
56

@@ -8,7 +9,7 @@ import { Span } from './span';
89
* @param span value to check
910
*/
1011
function isSpanInstance(span: unknown): span is Span {
11-
return span instanceof Span;
12+
return isInstanceOf(span, Span);
1213
}
1314

1415
/** Returns all trace headers that are currently on the top scope. */

packages/apm/src/span.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { getCurrentHub, Hub } from '@sentry/hub';
44
import { Span as SpanInterface, SpanContext, SpanStatus } from '@sentry/types';
5-
import { logger, timestampWithMs, uuid4 } from '@sentry/utils';
5+
import { isInstanceOf, logger, timestampWithMs, uuid4 } from '@sentry/utils';
66

77
// TODO: Should this be exported?
88
export const TRACEPARENT_REGEXP = new RegExp(
@@ -117,8 +117,8 @@ export class Span implements SpanInterface, SpanContext {
117117
public spanRecorder?: SpanRecorder;
118118

119119
public constructor(spanContext?: SpanContext, hub?: Hub) {
120-
if (hub instanceof Hub) {
121-
this._hub = hub;
120+
if (isInstanceOf(hub, Hub)) {
121+
this._hub = hub as Hub;
122122
}
123123

124124
if (!spanContext) {

packages/browser/src/integrations/linkederrors.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
22
import { Event, EventHint, Exception, ExtendedError, Integration } from '@sentry/types';
3+
import { isInstanceOf } from '@sentry/utils';
34

45
import { exceptionFromStacktrace } from '../parsers';
56
import { computeStackTrace } from '../tracekit';
@@ -54,10 +55,10 @@ export class LinkedErrors implements Integration {
5455
* @inheritDoc
5556
*/
5657
private _handler(event: Event, hint?: EventHint): Event | null {
57-
if (!event.exception || !event.exception.values || !hint || !(hint.originalException instanceof Error)) {
58+
if (!event.exception || !event.exception.values || !hint || !isInstanceOf(hint.originalException, Error)) {
5859
return event;
5960
}
60-
const linkedErrors = this._walkErrorTree(hint.originalException, this._key);
61+
const linkedErrors = this._walkErrorTree(hint.originalException as ExtendedError, this._key);
6162
event.exception.values = [...linkedErrors, ...event.exception.values];
6263
return event;
6364
}
@@ -66,7 +67,7 @@ export class LinkedErrors implements Integration {
6667
* @inheritDoc
6768
*/
6869
private _walkErrorTree(error: ExtendedError, key: string, stack: Exception[] = []): Exception[] {
69-
if (!(error[key] instanceof Error) || stack.length + 1 >= this._limit) {
70+
if (!isInstanceOf(error[key], Error) || stack.length + 1 >= this._limit) {
7071
return stack;
7172
}
7273
const stacktrace = computeStackTrace(error[key]);

packages/integrations/src/ember.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EventProcessor, Hub, Integration } from '@sentry/types';
2-
import { getGlobalObject, logger } from '@sentry/utils';
2+
import { getGlobalObject, isInstanceOf, logger } from '@sentry/utils';
33

44
/** JSDoc */
55
export class Ember implements Integration {
@@ -55,7 +55,7 @@ export class Ember implements Integration {
5555
(reason: any): void => {
5656
if (getCurrentHub().getIntegration(Ember)) {
5757
getCurrentHub().withScope(scope => {
58-
if (reason instanceof Error) {
58+
if (isInstanceOf(reason, Error)) {
5959
scope.setExtra('context', 'Unhandled Promise error detected');
6060
getCurrentHub().captureException(reason, { originalException: reason });
6161
} else {

packages/node/src/integrations/linkederrors.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
22
import { Event, EventHint, Exception, ExtendedError, Integration } from '@sentry/types';
3-
import { SyncPromise } from '@sentry/utils';
3+
import { isInstanceOf, SyncPromise } from '@sentry/utils';
44

55
import { getExceptionFromError } from '../parsers';
66

@@ -53,12 +53,12 @@ export class LinkedErrors implements Integration {
5353
* @inheritDoc
5454
*/
5555
public handler(event: Event, hint?: EventHint): PromiseLike<Event> {
56-
if (!event.exception || !event.exception.values || !hint || !(hint.originalException instanceof Error)) {
56+
if (!event.exception || !event.exception.values || !hint || !isInstanceOf(hint.originalException, Error)) {
5757
return SyncPromise.resolve(event);
5858
}
5959

6060
return new SyncPromise<Event>(resolve => {
61-
this.walkErrorTree(hint.originalException as ExtendedError, this._key)
61+
this.walkErrorTree(hint.originalException as Error, this._key)
6262
.then((linkedErrors: Exception[]) => {
6363
if (event && event.exception && event.exception.values) {
6464
event.exception.values = [...linkedErrors, ...event.exception.values];
@@ -75,7 +75,7 @@ export class LinkedErrors implements Integration {
7575
* @inheritDoc
7676
*/
7777
public walkErrorTree(error: ExtendedError, key: string, stack: Exception[] = []): PromiseLike<Exception[]> {
78-
if (!(error[key] instanceof Error) || stack.length + 1 >= this._limit) {
78+
if (!isInstanceOf(error[key], Error) || stack.length + 1 >= this._limit) {
7979
return SyncPromise.resolve(stack);
8080
}
8181
return new SyncPromise<Exception[]>((resolve, reject) => {

packages/utils/src/instrument.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { WrappedFunction } from '@sentry/types';
44

5-
import { isString } from './is';
5+
import { isInstanceOf, isString } from './is';
66
import { logger } from './logger';
77
import { getFunctionName, getGlobalObject } from './misc';
88
import { fill } from './object';
@@ -172,7 +172,7 @@ interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
172172

173173
/** Extract `method` from fetch call arguments */
174174
function getFetchMethod(fetchArgs: any[] = []): string {
175-
if ('Request' in global && fetchArgs[0] instanceof Request && fetchArgs[0].method) {
175+
if ('Request' in global && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
176176
return String(fetchArgs[0].method).toUpperCase();
177177
}
178178
if (fetchArgs[1] && fetchArgs[1].method) {
@@ -186,7 +186,7 @@ function getFetchUrl(fetchArgs: any[] = []): string {
186186
if (typeof fetchArgs[0] === 'string') {
187187
return fetchArgs[0];
188188
}
189-
if ('Request' in global && fetchArgs[0] instanceof Request) {
189+
if ('Request' in global && isInstanceOf(fetchArgs[0], Request)) {
190190
return fetchArgs[0].url;
191191
}
192192
return String(fetchArgs[0]);

packages/utils/src/is.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function isError(wat: any): boolean {
1414
case '[object DOMException]':
1515
return true;
1616
default:
17-
return wat instanceof Error;
17+
return isInstanceOf(wat, Error);
1818
}
1919
}
2020

@@ -93,7 +93,7 @@ export function isPlainObject(wat: any): boolean {
9393
*/
9494
export function isEvent(wat: any): boolean {
9595
// tslint:disable-next-line:strict-type-predicates
96-
return typeof Event !== 'undefined' && wat instanceof Event;
96+
return typeof Event !== 'undefined' && isInstanceOf(wat, Event);
9797
}
9898

9999
/**
@@ -105,7 +105,7 @@ export function isEvent(wat: any): boolean {
105105
*/
106106
export function isElement(wat: any): boolean {
107107
// tslint:disable-next-line:strict-type-predicates
108-
return typeof Element !== 'undefined' && wat instanceof Element;
108+
return typeof Element !== 'undefined' && isInstanceOf(wat, Element);
109109
}
110110

111111
/**
@@ -140,3 +140,19 @@ export function isSyntheticEvent(wat: any): boolean {
140140
// tslint:disable-next-line:no-unsafe-any
141141
return isPlainObject(wat) && 'nativeEvent' in wat && 'preventDefault' in wat && 'stopPropagation' in wat;
142142
}
143+
/**
144+
* Checks whether given value's type is an instance of provided constructor.
145+
* {@link isInstanceOf}.
146+
*
147+
* @param wat A value to be checked.
148+
* @param base A constructor to be used in a check.
149+
* @returns A boolean representing the result.
150+
*/
151+
export function isInstanceOf(wat: any, base: any): boolean {
152+
try {
153+
// tslint:disable-next-line:no-unsafe-any
154+
return wat instanceof base;
155+
} catch (_e) {
156+
return false;
157+
}
158+
}

packages/utils/src/object.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ExtendedError, WrappedFunction } from '@sentry/types';
22

3-
import { isElement, isError, isEvent, isPrimitive, isSyntheticEvent } from './is';
3+
import { isElement, isError, isEvent, isInstanceOf, isPrimitive, isSyntheticEvent } from './is';
44
import { Memo } from './memo';
55
import { getFunctionName, htmlTreeAsString } from './misc';
66
import { truncate } from './string';
@@ -135,7 +135,7 @@ function getWalkSource(
135135
}
136136

137137
// tslint:disable-next-line:strict-type-predicates
138-
if (typeof CustomEvent !== 'undefined' && value instanceof CustomEvent) {
138+
if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
139139
source.detail = event.detail;
140140
}
141141

packages/utils/test/is.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isDOMError, isDOMException, isError, isErrorEvent, isPrimitive, isThenable } from '../src/is';
1+
import { isDOMError, isDOMException, isError, isErrorEvent, isInstanceOf, isPrimitive, isThenable } from '../src/is';
22
import { supportsDOMError, supportsDOMException, supportsErrorEvent } from '../src/supports';
33
import { SyncPromise } from '../src/syncpromise';
44

@@ -89,3 +89,24 @@ describe('isThenable()', () => {
8989
expect(isThenable(new Date())).toEqual(false);
9090
});
9191
});
92+
93+
describe('isInstanceOf()', () => {
94+
test('should work as advertised', () => {
95+
function Foo(): void {
96+
/* no-empty */
97+
}
98+
expect(isInstanceOf(new Error('wat'), Error)).toEqual(true);
99+
expect(isInstanceOf(new Date(), Date)).toEqual(true);
100+
expect(isInstanceOf(new Foo(), Foo)).toEqual(true);
101+
102+
expect(isInstanceOf(new Error('wat'), Foo)).toEqual(false);
103+
expect(isInstanceOf(new Date('wat'), Error)).toEqual(false);
104+
});
105+
106+
test('should not break with incorrect input', () => {
107+
expect(isInstanceOf(new Error('wat'), 1)).toEqual(false);
108+
expect(isInstanceOf(new Error('wat'), 'wat')).toEqual(false);
109+
expect(isInstanceOf(new Error('wat'), null)).toEqual(false);
110+
expect(isInstanceOf(new Error('wat'), undefined)).toEqual(false);
111+
});
112+
});

0 commit comments

Comments
 (0)