Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,234 @@ describe('Native StackTrace', () => {
expect(screen.getByText('non-in-app-frame')).toBeInTheDocument();
});

describe('Non-native frames in mixed stacktraces', () => {
it('hides package and instruction address for Java frames', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {
...dataFrames[0]!,
platform: 'java',
package: 'com.example.MyClass',
instructionAddr: '0x00007fff5bf3d000',
symbolAddr: '0x00007fff5bf3d000',
function: 'myJavaMethod',
filename: 'MyClass.java',
lineNo: 42,
inApp: true,
};

const newData = {
...data,
frames: dataFrames,
};

render(
<NativeContent
data={newData}
event={event}
newestFirst
includeSystemFrames
platform="java-android"
/>,
{organization}
);

// Java frame should show function name but not package or instruction address
expect(screen.getByText('myJavaMethod')).toBeInTheDocument();
expect(screen.queryByText('com.example.MyClass')).not.toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d000')).not.toBeInTheDocument();
});

it('hides package and instruction address for Kotlin frames', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {
...dataFrames[0]!,
platform: 'kotlin',
package: 'com.example.MyClass',
instructionAddr: '0x00007fff5bf3d000',
symbolAddr: '0x00007fff5bf3d000',
function: 'myKotlinMethod',
filename: 'MyClass.kt',
lineNo: 42,
inApp: true,
};

const newData = {
...data,
frames: dataFrames,
};

render(
<NativeContent
data={newData}
event={event}
newestFirst
includeSystemFrames
platform="java-android"
/>,
{organization}
);

// Kotlin frame should show function name but not package or instruction address
expect(screen.getByText('myKotlinMethod')).toBeInTheDocument();
expect(screen.queryByText('com.example.MyClass')).not.toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d000')).not.toBeInTheDocument();
});

it('hides package and instruction address for JavaScript frames', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {
...dataFrames[0]!,
platform: 'javascript',
package: 'webpack://myapp',
instructionAddr: '0x00007fff5bf3d000',
symbolAddr: '0x00007fff5bf3d000',
function: 'myJsFunction',
filename: 'app.js',
lineNo: 42,
inApp: true,
};

const newData = {
...data,
frames: dataFrames,
};

render(
<NativeContent
data={newData}
event={event}
newestFirst
includeSystemFrames
platform="javascript"
/>,
{organization}
);

// JavaScript frame should show function name but not package or instruction address
expect(screen.getByText('myJsFunction')).toBeInTheDocument();
expect(screen.queryByText('webpack://myapp')).not.toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d000')).not.toBeInTheDocument();
});

it('hides package and address even when package is null', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {
...dataFrames[0]!,
platform: 'java',
package: null,
instructionAddr: '0x00007fff5bf3d000',
function: 'javaMethod',
filename: 'MyClass.java',
lineNo: 10,
inApp: true,
};

const newData = {
...data,
frames: dataFrames,
};

render(
<NativeContent
data={newData}
event={event}
newestFirst
includeSystemFrames
platform="java-android"
/>,
{organization}
);

// Non-native frame should show function name but not address
expect(screen.getByText('javaMethod')).toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d000')).not.toBeInTheDocument();
});

it('hides package and address for multiple non-native frames', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {
...dataFrames[0]!,
platform: 'java',
package: 'com.example.ClassA',
instructionAddr: '0x00007fff5bf3d001',
function: 'methodA',
filename: 'ClassA.java',
lineNo: 10,
inApp: true,
};
dataFrames[1] = {
...dataFrames[1]!,
platform: 'kotlin',
package: 'com.example.ClassB',
instructionAddr: '0x00007fff5bf3d002',
function: 'methodB',
filename: 'ClassB.kt',
lineNo: 20,
inApp: true,
};

const newData = {
...data,
frames: dataFrames,
};

render(
<NativeContent
data={newData}
event={event}
newestFirst
includeSystemFrames
platform="java-android"
/>,
{organization}
);

// Both non-native frames should show function names but not packages or addresses
expect(screen.getByText('methodA')).toBeInTheDocument();
expect(screen.getByText('methodB')).toBeInTheDocument();
expect(screen.queryByText('com.example.ClassA')).not.toBeInTheDocument();
expect(screen.queryByText('com.example.ClassB')).not.toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d001')).not.toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d002')).not.toBeInTheDocument();
});

it('frame.platform overrides event platform', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {
...dataFrames[0]!,
platform: 'java', // Frame is Java
package: 'com.example.MyClass',
instructionAddr: '0x00007fff5bf3d000',
function: 'javaMethod',
filename: 'MyClass.java',
lineNo: 42,
inApp: true,
};

const newData = {
...data,
frames: dataFrames,
};

render(
<NativeContent
data={newData}
event={event}
newestFirst
includeSystemFrames
platform="native" // Event platform is native, but frame overrides
/>,
{organization}
);

// Frame platform (Java) should override event platform (native)
// So package/address should be hidden
expect(screen.getByText('javaMethod')).toBeInTheDocument();
expect(screen.queryByText('com.example.MyClass')).not.toBeInTheDocument();
expect(screen.queryByText('0x00007fff5bf3d000')).not.toBeInTheDocument();
});
});

it('does not display a toggle button when there is only one non-inapp frame', () => {
const dataFrames = [...data.frames];
dataFrames[0] = {...dataFrames[0]!, inApp: true};
Expand Down
80 changes: 47 additions & 33 deletions static/app/components/events/interfaces/nativeFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {OpenInContextLine} from 'sentry/components/events/interfaces/frame/openI
import {StacktraceLink} from 'sentry/components/events/interfaces/frame/stacktraceLink';
import {
getLeadHint,
getPlatform,
hasAssembly,
hasContextRegisters,
hasContextSource,
Expand Down Expand Up @@ -42,6 +43,7 @@ import type {
import type {PlatformKey} from 'sentry/types/project';
import {StackView, type StacktraceType} from 'sentry/types/stacktrace';
import {defined} from 'sentry/utils';
import {isNativePlatform} from 'sentry/utils/platform';
import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState';
import withSentryAppComponents from 'sentry/utils/withSentryAppComponents';
import {SectionKey, useIssueDetails} from 'sentry/views/issueDetails/streamline/context';
Expand Down Expand Up @@ -278,6 +280,8 @@ function NativeFrame({
const addressTooltip = getAddressTooltip();
const functionName = getFunctionName();
const status = getStatus();
const frameOrEventPlatform = getPlatform(frame.platform, platform);
const showNativeInfo = isNativePlatform(frameOrEventPlatform);

return (
<StackTraceFrame data-test-id="stack-trace-frame">
Expand Down Expand Up @@ -317,47 +321,57 @@ function NativeFrame({
</Tooltip>
) : null}
</SymbolicatorIcon>

<div>
{!fullStackTrace && !expanded && leadsToApp && (
{showNativeInfo && (
<Fragment>
<PackageNote>
{getLeadHint({event, hasNextFrame: defined(nextFrame)})}
</PackageNote>
{!fullStackTrace && !expanded && leadsToApp && (
<Fragment>
<PackageNote>
{getLeadHint({event, hasNextFrame: defined(nextFrame)})}
</PackageNote>
</Fragment>
)}
<Tooltip
title={
frame.package ??
(isDartAsyncSuspensionFrame
? t('Dart async operation')
: t('Go to images loaded'))
}
containerDisplayMode="inline-flex"
delay={tooltipDelay}
maxWidth={FRAME_TOOLTIP_MAX_WIDTH}
position="auto-start"
>
<Package>
{frame.package
? trimPackage(frame.package)
: isDartAsyncSuspensionFrame
? t('Dart async')
: `<${t('unknown')}>`}
</Package>
</Tooltip>
</Fragment>
)}
<Tooltip
title={
frame.package ??
(isDartAsyncSuspensionFrame
? t('Dart async operation')
: t('Go to images loaded'))
}
containerDisplayMode="inline-flex"
delay={tooltipDelay}
maxWidth={FRAME_TOOLTIP_MAX_WIDTH}
position="auto-start"
>
<Package>
{frame.package
? trimPackage(frame.package)
: isDartAsyncSuspensionFrame
? t('Dart async')
: `<${t('unknown')}>`}
</Package>
</Tooltip>
</div>
<Flex>
<AddressCell onClick={packageClickable ? handleGoToImagesLoaded : undefined}>
<Tooltip
title={addressTooltip}
disabled={!(foundByStackScanning || inlineFrame)}
delay={tooltipDelay}
maxWidth={FRAME_TOOLTIP_MAX_WIDTH}
{showNativeInfo && (
<AddressCell
onClick={packageClickable ? handleGoToImagesLoaded : undefined}
>
{!relativeAddress || absolute ? frame.instructionAddr : relativeAddress}
</Tooltip>
</AddressCell>
<Tooltip
title={addressTooltip}
disabled={!(foundByStackScanning || inlineFrame)}
delay={tooltipDelay}
maxWidth={FRAME_TOOLTIP_MAX_WIDTH}
>
{!relativeAddress || absolute ? frame.instructionAddr : relativeAddress}
</Tooltip>
</AddressCell>
)}
</Flex>

<FunctionNameCell>
{functionName ? (
<Tooltip title={frame?.rawFunction ?? frame?.symbol} delay={tooltipDelay}>
Expand Down
34 changes: 34 additions & 0 deletions static/app/components/events/interfaces/utils.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import {
getCurlCommand,
getCurrentThread,
getStacktracePlatform,
getThreadById,
stringifyQueryList,
userContextToActor,
Expand Down Expand Up @@ -348,4 +349,37 @@
expect(thread?.name).toBe('puma 002');
});
});

it('should return event platform when stacktrace is undefined', () => {
const event = EventFixture({platform: 'python'});
expect(getStacktracePlatform(event)).toBe('python');
});

it('should return "other" when event has no platform and stacktrace is null', () => {
const event = EventFixture({platform: undefined});
expect(getStacktracePlatform(event, null)).toBe('other');
});

it('should return event platform when any frame platform matches event platform', () => {
const event = EventFixture({platform: 'javascript'});
const stacktrace = {
frames: [
{platform: 'native', filename: 'native.c'},
{platform: 'javascript', filename: 'app.js'},
{platform: 'native', filename: 'framework.c'},
],
};
expect(getStacktracePlatform(event, stacktrace)).toBe('javascript');

Check failure on line 372 in static/app/components/events/interfaces/utils.spec.tsx

View workflow job for this annotation

GitHub Actions / typescript

Argument of type '{ frames: { platform: string; filename: string; }[]; }' is not assignable to parameter of type 'StacktraceType'.
});

it('should return overridden platform when all frames have different platform', () => {
const event = EventFixture({platform: 'javascript'});
const stacktrace = {
frames: [
{platform: 'native', filename: 'native.c'},
{platform: 'native', filename: 'framework.c'},
],
};
expect(getStacktracePlatform(event, stacktrace)).toBe('native');

Check failure on line 383 in static/app/components/events/interfaces/utils.spec.tsx

View workflow job for this annotation

GitHub Actions / typescript

Argument of type '{ frames: { platform: string; filename: string; }[]; }' is not assignable to parameter of type 'StacktraceType'.
});
});
Loading
Loading