nativeFrame.tsx 14 KB

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