nativeFrame.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import {Fragment, MouseEvent, useContext, useState} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import scrollToElement from 'scroll-to-element';
  5. import Button from 'sentry/components/button';
  6. import {
  7. hasAssembly,
  8. hasContextRegisters,
  9. hasContextSource,
  10. hasContextVars,
  11. isDotnet,
  12. isExpandable,
  13. trimPackage,
  14. } from 'sentry/components/events/interfaces/frame/utils';
  15. import {formatAddress, parseAddress} from 'sentry/components/events/interfaces/utils';
  16. import AnnotatedText from 'sentry/components/events/meta/annotatedText';
  17. import {getMeta} from 'sentry/components/events/meta/metaProxy';
  18. import {TraceEventDataSectionContext} from 'sentry/components/events/traceEventDataSection';
  19. import {DisplayOption} from 'sentry/components/events/traceEventDataSection/displayOptions';
  20. import {STACKTRACE_PREVIEW_TOOLTIP_DELAY} from 'sentry/components/stacktracePreview';
  21. import StrictClick from 'sentry/components/strictClick';
  22. import Tooltip from 'sentry/components/tooltip';
  23. import {IconChevron} from 'sentry/icons/iconChevron';
  24. import {IconInfo} from 'sentry/icons/iconInfo';
  25. import {IconQuestion} from 'sentry/icons/iconQuestion';
  26. import {IconWarning} from 'sentry/icons/iconWarning';
  27. import {t} from 'sentry/locale';
  28. import {DebugMetaActions} from 'sentry/stores/debugMetaStore';
  29. import space from 'sentry/styles/space';
  30. import {Frame, PlatformType, SentryAppComponent} from 'sentry/types';
  31. import {Event} from 'sentry/types/event';
  32. import {defined} from 'sentry/utils';
  33. import {Color} from 'sentry/utils/theme';
  34. import withSentryAppComponents from 'sentry/utils/withSentryAppComponents';
  35. import DebugImage from './debugMeta/debugImage';
  36. import {combineStatus} from './debugMeta/utils';
  37. import Context from './frame/context';
  38. import {SymbolicatorStatus} from './types';
  39. type Props = {
  40. components: Array<SentryAppComponent>;
  41. event: Event;
  42. frame: Frame;
  43. isUsedForGrouping: boolean;
  44. platform: PlatformType;
  45. registers: Record<string, string>;
  46. emptySourceNotation?: boolean;
  47. image?: React.ComponentProps<typeof DebugImage>['image'];
  48. includeSystemFrames?: boolean;
  49. isExpanded?: boolean;
  50. isHoverPreviewed?: boolean;
  51. isOnlyFrame?: boolean;
  52. maxLengthOfRelativeAddress?: number;
  53. nextFrame?: Frame;
  54. prevFrame?: Frame;
  55. };
  56. function NativeFrame({
  57. frame,
  58. nextFrame,
  59. prevFrame,
  60. includeSystemFrames,
  61. isUsedForGrouping,
  62. maxLengthOfRelativeAddress,
  63. image,
  64. registers,
  65. isOnlyFrame,
  66. event,
  67. components,
  68. isExpanded,
  69. platform,
  70. emptySourceNotation = false,
  71. /**
  72. * Is the stack trace being previewed in a hovercard?
  73. */
  74. isHoverPreviewed = false,
  75. }: Props) {
  76. const traceEventDataSectionContext = useContext(TraceEventDataSectionContext);
  77. const absolute = traceEventDataSectionContext?.activeDisplayOptions.includes(
  78. DisplayOption.ABSOLUTE_ADDRESSES
  79. );
  80. const fullStackTrace = traceEventDataSectionContext?.activeDisplayOptions.includes(
  81. DisplayOption.FULL_STACK_TRACE
  82. );
  83. const fullFunctionName = traceEventDataSectionContext?.activeDisplayOptions.includes(
  84. DisplayOption.VERBOSE_FUNCTION_NAMES
  85. );
  86. const absoluteFilePaths = traceEventDataSectionContext?.activeDisplayOptions.includes(
  87. DisplayOption.ABSOLUTE_FILE_PATHS
  88. );
  89. const tooltipDelay = isHoverPreviewed ? STACKTRACE_PREVIEW_TOOLTIP_DELAY : undefined;
  90. const foundByStackScanning = frame.trust === 'scan' || frame.trust === 'cfi-scan';
  91. const startingAddress = image ? image.image_addr : null;
  92. const packageClickable =
  93. !!frame.symbolicatorStatus &&
  94. frame.symbolicatorStatus !== SymbolicatorStatus.UNKNOWN_IMAGE &&
  95. !isHoverPreviewed;
  96. const leadsToApp = !frame.inApp && ((nextFrame && nextFrame.inApp) || !nextFrame);
  97. const expandable =
  98. !leadsToApp || includeSystemFrames
  99. ? isExpandable({
  100. frame,
  101. registers,
  102. platform,
  103. emptySourceNotation,
  104. isOnlyFrame,
  105. })
  106. : false;
  107. const inlineFrame =
  108. prevFrame &&
  109. platform === (prevFrame.platform || platform) &&
  110. frame.instructionAddr === prevFrame.instructionAddr;
  111. const functionNameHiddenDetails =
  112. defined(frame.rawFunction) &&
  113. defined(frame.function) &&
  114. frame.function !== frame.rawFunction;
  115. const [expanded, setExpanded] = useState(expandable ? isExpanded ?? false : false);
  116. function getRelativeAddress() {
  117. if (!startingAddress) {
  118. return '';
  119. }
  120. const relativeAddress = formatAddress(
  121. parseAddress(frame.instructionAddr) - parseAddress(startingAddress),
  122. maxLengthOfRelativeAddress
  123. );
  124. return `+${relativeAddress}`;
  125. }
  126. function getAddressTooltip() {
  127. if (inlineFrame && foundByStackScanning) {
  128. return t('Inline frame, found by stack scanning');
  129. }
  130. if (inlineFrame) {
  131. return t('Inline frame');
  132. }
  133. if (foundByStackScanning) {
  134. return t('Found by stack scanning');
  135. }
  136. return undefined;
  137. }
  138. function getFunctionName() {
  139. if (functionNameHiddenDetails && fullFunctionName && frame.rawFunction) {
  140. return {
  141. value: frame.rawFunction,
  142. meta: getMeta(frame, 'rawFunction'),
  143. };
  144. }
  145. if (frame.function) {
  146. return {
  147. value: frame.function,
  148. meta: getMeta(frame, 'function'),
  149. };
  150. }
  151. return undefined;
  152. }
  153. function getStatus() {
  154. // this is the status of image that belongs to this frame
  155. if (!image) {
  156. return undefined;
  157. }
  158. const combinedStatus = combineStatus(image.debug_status, image.unwind_status);
  159. switch (combinedStatus) {
  160. case 'unused':
  161. return undefined;
  162. case 'found':
  163. return 'success';
  164. default:
  165. return 'error';
  166. }
  167. }
  168. function handleGoToImagesLoaded(e: MouseEvent) {
  169. e.stopPropagation(); // to prevent collapsing if collapsible
  170. if (frame.instructionAddr) {
  171. const searchTerm =
  172. !(!frame.addrMode || frame.addrMode === 'abs') && image
  173. ? `${image.debug_id}!${frame.instructionAddr}`
  174. : frame.instructionAddr;
  175. DebugMetaActions.updateFilter(searchTerm);
  176. }
  177. scrollToElement('#images-loaded');
  178. }
  179. function handleToggleContext(e: MouseEvent) {
  180. if (!expandable) {
  181. return;
  182. }
  183. e.preventDefault();
  184. setExpanded(!expanded);
  185. }
  186. const relativeAddress = getRelativeAddress();
  187. const addressTooltip = getAddressTooltip();
  188. const functionName = getFunctionName();
  189. const status = getStatus();
  190. return (
  191. <GridRow
  192. inApp={frame.inApp}
  193. expandable={expandable}
  194. expanded={expanded}
  195. data-test-id="stack-trace-frame"
  196. >
  197. <StrictClick onClick={handleToggleContext}>
  198. <StrictClickContent>
  199. <StatusCell>
  200. {(status === 'error' || status === undefined) &&
  201. (packageClickable ? (
  202. <PackageStatusButton
  203. onClick={handleGoToImagesLoaded}
  204. title={t('Go to images loaded')}
  205. aria-label={t('Go to images loaded')}
  206. icon={
  207. status === 'error' ? (
  208. <IconQuestion size="sm" color="red300" />
  209. ) : (
  210. <IconWarning size="sm" color="red300" />
  211. )
  212. }
  213. size="zero"
  214. borderless
  215. />
  216. ) : status === 'error' ? (
  217. <IconQuestion size="sm" color="red300" />
  218. ) : (
  219. <IconWarning size="sm" color="red300" />
  220. ))}
  221. </StatusCell>
  222. <PackageCell>
  223. {!fullStackTrace && !expanded && leadsToApp && (
  224. <Fragment>
  225. {!nextFrame ? t('Crashed in non-app') : t('Called from')}
  226. {':'}&nbsp;
  227. </Fragment>
  228. )}
  229. <span>
  230. <Tooltip
  231. title={frame.package ?? t('Go to images loaded')}
  232. delay={tooltipDelay}
  233. disabled={frame.package ? false : !packageClickable}
  234. containerDisplayMode="inline-flex"
  235. >
  236. <Package
  237. color={
  238. status === undefined || status === 'error'
  239. ? 'red300'
  240. : packageClickable
  241. ? 'blue300'
  242. : undefined
  243. }
  244. onClick={packageClickable ? handleGoToImagesLoaded : undefined}
  245. >
  246. {frame.package ? trimPackage(frame.package) : `<${t('unknown')}>`}
  247. </Package>
  248. </Tooltip>
  249. </span>
  250. </PackageCell>
  251. <AddressCell>
  252. <Tooltip
  253. title={addressTooltip}
  254. disabled={!(foundByStackScanning || inlineFrame)}
  255. delay={tooltipDelay}
  256. >
  257. {!relativeAddress || absolute ? frame.instructionAddr : relativeAddress}
  258. </Tooltip>
  259. </AddressCell>
  260. <GroupingCell>
  261. {isUsedForGrouping && (
  262. <Tooltip
  263. title={t('This frame appears in all other events related to this issue')}
  264. containerDisplayMode="inline-flex"
  265. >
  266. <IconInfo size="sm" color="gray300" />
  267. </Tooltip>
  268. )}
  269. </GroupingCell>
  270. <FunctionNameCell>
  271. <FunctionName>
  272. {functionName ? (
  273. <AnnotatedText value={functionName.value} meta={functionName.meta} />
  274. ) : (
  275. `<${t('unknown')}>`
  276. )}
  277. </FunctionName>
  278. {frame.filename && (
  279. <Tooltip
  280. title={frame.absPath}
  281. disabled={!(defined(frame.absPath) && frame.absPath !== frame.filename)}
  282. delay={tooltipDelay}
  283. containerDisplayMode="inline-flex"
  284. >
  285. <FileName>
  286. {'('}
  287. {absoluteFilePaths ? frame.absPath : frame.filename}
  288. {frame.lineNo && `:${frame.lineNo}`}
  289. {')'}
  290. </FileName>
  291. </Tooltip>
  292. )}
  293. </FunctionNameCell>
  294. <ExpandCell>
  295. {expandable && (
  296. <ToggleButton
  297. size="zero"
  298. css={isDotnet(platform) && {display: 'block !important'}} // remove important once we get rid of css files
  299. title={t('Toggle Context')}
  300. aria-label={t('Toggle Context')}
  301. tooltipProps={
  302. isHoverPreviewed ? {delay: STACKTRACE_PREVIEW_TOOLTIP_DELAY} : undefined
  303. }
  304. icon={<IconChevron size="8px" direction={expanded ? 'up' : 'down'} />}
  305. />
  306. )}
  307. </ExpandCell>
  308. </StrictClickContent>
  309. </StrictClick>
  310. <RegistersCell>
  311. {expanded && (
  312. <Registers
  313. frame={frame}
  314. event={event}
  315. registers={registers}
  316. components={components}
  317. hasContextSource={hasContextSource(frame)}
  318. hasContextVars={hasContextVars(frame)}
  319. hasContextRegisters={hasContextRegisters(registers)}
  320. emptySourceNotation={emptySourceNotation}
  321. hasAssembly={hasAssembly(frame, platform)}
  322. expandable={expandable}
  323. isExpanded={expanded}
  324. />
  325. )}
  326. </RegistersCell>
  327. </GridRow>
  328. );
  329. }
  330. export default withSentryAppComponents(NativeFrame, {componentType: 'stacktrace-link'});
  331. const Cell = styled('div')`
  332. padding: ${space(0.5)};
  333. display: flex;
  334. flex-wrap: wrap;
  335. word-break: break-all;
  336. align-items: flex-start;
  337. `;
  338. const StatusCell = styled(Cell)`
  339. @media (max-width: ${p => p.theme.breakpoints.small}) {
  340. grid-column: 1/1;
  341. grid-row: 1/1;
  342. }
  343. `;
  344. const PackageCell = styled(Cell)`
  345. color: ${p => p.theme.subText};
  346. @media (max-width: ${p => p.theme.breakpoints.small}) {
  347. grid-column: 2/2;
  348. grid-row: 1/1;
  349. display: grid;
  350. grid-template-columns: 1fr;
  351. grid-template-rows: repeat(2, auto);
  352. }
  353. `;
  354. const AddressCell = styled(Cell)`
  355. font-family: ${p => p.theme.text.familyMono};
  356. @media (max-width: ${p => p.theme.breakpoints.small}) {
  357. grid-column: 3/3;
  358. grid-row: 1/1;
  359. }
  360. `;
  361. const GroupingCell = styled(Cell)`
  362. @media (max-width: ${p => p.theme.breakpoints.small}) {
  363. grid-column: 1/1;
  364. grid-row: 2/2;
  365. }
  366. `;
  367. const FunctionNameCell = styled(Cell)`
  368. color: ${p => p.theme.textColor};
  369. @media (max-width: ${p => p.theme.breakpoints.small}) {
  370. grid-column: 2/-1;
  371. grid-row: 2/2;
  372. }
  373. `;
  374. const ExpandCell = styled(Cell)``;
  375. const RegistersCell = styled('div')`
  376. grid-column: 1/-1;
  377. margin-left: -${space(0.5)};
  378. margin-right: -${space(0.5)};
  379. cursor: default;
  380. `;
  381. const Registers = styled(Context)`
  382. padding: 0;
  383. margin: 0;
  384. `;
  385. const ToggleButton = styled(Button)`
  386. width: 16px;
  387. height: 16px;
  388. `;
  389. const Package = styled('span')<{color?: Color}>`
  390. border-bottom: 1px dashed ${p => p.theme.border};
  391. ${p => p.color && `color: ${p.theme[p.color]}`};
  392. ${p => p.onClick && `cursor: pointer;`}
  393. `;
  394. const FunctionName = styled('div')`
  395. color: ${p => p.theme.headingColor};
  396. margin-right: ${space(1)};
  397. `;
  398. const FileName = styled('span')`
  399. color: ${p => p.theme.subText};
  400. border-bottom: 1px dashed ${p => p.theme.border};
  401. `;
  402. const PackageStatusButton = styled(Button)`
  403. padding: 0;
  404. border: none;
  405. `;
  406. const GridRow = styled('div')<{expandable: boolean; expanded: boolean; inApp: boolean}>`
  407. ${p => p.expandable && `cursor: pointer;`};
  408. ${p => p.inApp && `background: ${p.theme.bodyBackground};`};
  409. ${p =>
  410. !p.inApp &&
  411. css`
  412. color: ${p.theme.subText};
  413. ${FunctionName} {
  414. color: ${p.theme.subText};
  415. }
  416. ${FunctionNameCell} {
  417. color: ${p.theme.subText};
  418. }
  419. `};
  420. display: grid;
  421. align-items: flex-start;
  422. padding: ${space(0.5)};
  423. :not(:last-child) {
  424. border-bottom: 1px solid ${p => p.theme.border};
  425. }
  426. grid-template-columns: 24px 132px 138px 24px 1fr 24px;
  427. grid-template-rows: 1fr;
  428. @media (max-width: ${p => p.theme.breakpoints.small}) {
  429. grid-template-columns: 24px auto minmax(138px, 1fr) 24px;
  430. grid-template-rows: repeat(2, auto);
  431. }
  432. `;
  433. const StrictClickContent = styled('div')`
  434. display: contents;
  435. `;