diff --git a/packages/react-refresh/src/ReactFreshRuntime.js b/packages/react-refresh/src/ReactFreshRuntime.js index e6f0fb70ae5c6..cd5d5a6716bf8 100644 --- a/packages/react-refresh/src/ReactFreshRuntime.js +++ b/packages/react-refresh/src/ReactFreshRuntime.js @@ -178,6 +178,16 @@ function cloneSet(set: Set): Set { return clone; } +// This is a safety mechanism to protect against rogue getters and Proxies. +function getProperty(object, property) { + try { + return object[property]; + } catch (err) { + // Intentionally ignore. + return undefined; + } +} + export function performReactRefresh(): RefreshUpdate | null { if (!__DEV__) { throw new Error( @@ -322,7 +332,7 @@ export function register(type: any, id: string): void { // Visit inner types because we might not have registered them. if (typeof type === 'object' && type !== null) { - switch (type.$$typeof) { + switch (getProperty(type, '$$typeof')) { case REACT_FORWARD_REF_TYPE: register(type.render, id + '$render'); break; @@ -676,7 +686,7 @@ export function isLikelyComponentType(type: any): boolean { } case 'object': { if (type != null) { - switch (type.$$typeof) { + switch (getProperty(type, '$$typeof')) { case REACT_FORWARD_REF_TYPE: case REACT_MEMO_TYPE: // Definitely React components. diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 3b6f69b13e318..3edbd874f4501 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -3612,11 +3612,23 @@ describe('ReactFresh', () => { const useStore = () => {}; expect(ReactFreshRuntime.isLikelyComponentType(useStore)).toBe(false); expect(ReactFreshRuntime.isLikelyComponentType(useTheme)).toBe(false); + const rogueProxy = new Proxy({}, { + get(target, property) { + throw new Error(); + } + }); + expect(ReactFreshRuntime.isLikelyComponentType(rogueProxy)).toBe(false); // These seem like function components. const Button = () => {}; expect(ReactFreshRuntime.isLikelyComponentType(Button)).toBe(true); expect(ReactFreshRuntime.isLikelyComponentType(Widget)).toBe(true); + const ProxyButton = new Proxy(Button, { + get(target, property) { + return target[property]; + } + }); + expect(ReactFreshRuntime.isLikelyComponentType(ProxyButton)).toBe(true); const anon = (() => () => {})(); anon.displayName = 'Foo'; expect(ReactFreshRuntime.isLikelyComponentType(anon)).toBe(true); @@ -3624,8 +3636,14 @@ describe('ReactFresh', () => { // These seem like class components. class Btn extends React.Component {} class PureBtn extends React.PureComponent {} + const ProxyBtn = new Proxy(Btn, { + get(target, property) { + return target[property]; + } + }); expect(ReactFreshRuntime.isLikelyComponentType(Btn)).toBe(true); expect(ReactFreshRuntime.isLikelyComponentType(PureBtn)).toBe(true); + expect(ReactFreshRuntime.isLikelyComponentType(ProxyBtn)).toBe(true); expect( ReactFreshRuntime.isLikelyComponentType( createReactClass({render() {}}),