Skip to content

Commit

Permalink
Account for the construct call taking a stack frame
Browse files Browse the repository at this point in the history
We do this by first searching for the first different frame, then find
the same frames and then find the first different frame again.
  • Loading branch information
sebmarkbage committed Apr 11, 2020
1 parent ac9651d commit 526ce6e
Showing 1 changed file with 28 additions and 12 deletions.
40 changes: 28 additions & 12 deletions packages/shared/ReactComponentStackFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function describeNativeComponentFrame(
}
}

const control = Error();
let control;

reentry = true;
let previousDispatcher;
Expand All @@ -90,7 +90,9 @@ export function describeNativeComponentFrame(
// This should throw.
if (construct) {
// Something should be setting the props in the constructor.
const Fake = function() {};
const Fake = function() {
return Error();
};
// $FlowFixMe
Object.defineProperty(Fake.prototype, 'props', {
set: function() {
Expand All @@ -100,16 +102,21 @@ export function describeNativeComponentFrame(
},
});
if (typeof Reflect === 'object' && Reflect.construct) {
// We construct a different control for this case to include any extra
// frames added by the construct call.
control = Reflect.construct(Fake, []);
Reflect.construct(fn, [], Fake);
} else {
control = Error();
fn.call(new Fake());
}
} else {
control = Error();
fn();
}
} catch (sample) {
// This is inlined manually because closure doesn't do it for us.
if (sample && typeof sample.stack === 'string') {
if (sample && control && typeof sample.stack === 'string') {
// This extracts the first frame from the sample that isn't also in the control.
// Skipping one frame that we assume is the frame that calls the two.
const sampleLines = sample.stack.split('\n');
Expand All @@ -127,24 +134,33 @@ export function describeNativeComponentFrame(
}
for (; s >= 1 && c >= 0; s--, c--) {
// Next we find the first one that isn't the same which should be the
// frame that called our sample function.
// frame that called our sample function and the control.
if (sampleLines[s] !== controlLines[c]) {
// In V8, the first line is describing the message but other VMs don't.
// If we're about to return the first line, and the control is also on the same
// line, that's a pretty good indicator that our sample threw at same line as
// the control. I.e. before we entered the sample frame. So we ignore this result.
// This can happen if you passed a class to function component, or non-function.
if (s !== 1 || c !== 1) {
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
const frame = '\n' + sampleLines[s - 1].replace(' at new ', ' at ');
if (__DEV__) {
if (typeof fn === 'function') {
componentFrameCache.set(fn, frame);
do {
s--;
c--;
// We may still have similar intermediate frames from the construct call.
// The next one that isn't the same should be our match though.
if (c < 0 || sampleLines[s] !== controlLines[c]) {
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
if (__DEV__) {
if (typeof fn === 'function') {
componentFrameCache.set(fn, frame);
}
}
// Return the line we found.
return frame;
}
}
// Return the line we found.
return frame;
} while (s >= 1 && c >= 0);
}
break;
}
}
}
Expand Down

0 comments on commit 526ce6e

Please sign in to comment.