nativeFrame.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import {Fragment, MouseEvent, useContext, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import scrollToElement from 'scroll-to-element';
  4. import {Button} from 'sentry/components/button';
  5. import {
  6. hasAssembly,
  7. hasContextRegisters,
  8. hasContextSource,
  9. hasContextVars,
  10. isDotnet,
  11. isExpandable,
  12. trimPackage,
  13. } from 'sentry/components/events/interfaces/frame/utils';
  14. import {formatAddress, parseAddress} from 'sentry/components/events/interfaces/utils';
  15. import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
  16. import {TraceEventDataSectionContext} from 'sentry/components/events/traceEventDataSection';
  17. import StrictClick from 'sentry/components/strictClick';
  18. import Tag from 'sentry/components/tag';
  19. import {Tooltip} from 'sentry/components/tooltip';
  20. import {SLOW_TOOLTIP_DELAY} from 'sentry/constants';
  21. import {IconCheckmark} from 'sentry/icons/iconCheckmark';
  22. import {IconChevron} from 'sentry/icons/iconChevron';
  23. import {IconFileBroken} from 'sentry/icons/iconFileBroken';
  24. import {IconRepeat} from 'sentry/icons/iconRepeat';
  25. import {IconWarning} from 'sentry/icons/iconWarning';
  26. import {t} from 'sentry/locale';
  27. import DebugMetaStore 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 withSentryAppComponents from 'sentry/utils/withSentryAppComponents';
  33. import DebugImage from './debugMeta/debugImage';
  34. import {combineStatus} from './debugMeta/utils';
  35. import Context from './frame/context';
  36. import {SymbolicatorStatus} from './types';
  37. type Props = {
  38. components: Array<SentryAppComponent>;
  39. event: Event;
  40. frame: Frame;
  41. isUsedForGrouping: boolean;
  42. platform: PlatformType;
  43. registers: Record<string, string>;
  44. emptySourceNotation?: boolean;
  45. frameMeta?: Record<any, any>;
  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. registersMeta?: Record<any, any>;
  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. registersMeta,
  71. frameMeta,
  72. emptySourceNotation = false,
  73. /**
  74. * Is the stack trace being previewed in a hovercard?
  75. */
  76. isHoverPreviewed = false,
  77. }: Props) {
  78. const traceEventDataSectionContext = useContext(TraceEventDataSectionContext);
  79. const absolute = traceEventDataSectionContext?.display.includes('absolute-addresses');
  80. const fullStackTrace = traceEventDataSectionContext?.fullStackTrace;
  81. const fullFunctionName = traceEventDataSectionContext?.display.includes(
  82. 'verbose-function-names'
  83. );
  84. const absoluteFilePaths =
  85. traceEventDataSectionContext?.display.includes('absolute-file-paths');
  86. const tooltipDelay = isHoverPreviewed ? SLOW_TOOLTIP_DELAY : undefined;
  87. const foundByStackScanning = frame.trust === 'scan' || frame.trust === 'cfi-scan';
  88. const startingAddress = image ? image.image_addr : null;
  89. const packageClickable =
  90. !!frame.symbolicatorStatus &&
  91. frame.symbolicatorStatus !== SymbolicatorStatus.UNKNOWN_IMAGE &&
  92. !isHoverPreviewed;
  93. const leadsToApp = !frame.inApp && ((nextFrame && nextFrame.inApp) || !nextFrame);
  94. const expandable =
  95. !leadsToApp || includeSystemFrames
  96. ? isExpandable({
  97. frame,
  98. registers,
  99. platform,
  100. emptySourceNotation,
  101. isOnlyFrame,
  102. })
  103. : false;
  104. const inlineFrame =
  105. prevFrame &&
  106. platform === (prevFrame.platform || platform) &&
  107. frame.instructionAddr === prevFrame.instructionAddr;
  108. const functionNameHiddenDetails =
  109. defined(frame.rawFunction) &&
  110. defined(frame.function) &&
  111. frame.function !== frame.rawFunction;
  112. const [expanded, setExpanded] = useState(expandable ? isExpanded ?? false : false);
  113. function getRelativeAddress() {
  114. if (!startingAddress) {
  115. return '';
  116. }
  117. const relativeAddress = formatAddress(
  118. parseAddress(frame.instructionAddr) - parseAddress(startingAddress),
  119. maxLengthOfRelativeAddress
  120. );
  121. return `+${relativeAddress}`;
  122. }
  123. function getAddressTooltip() {
  124. if (inlineFrame && foundByStackScanning) {
  125. return t('Inline frame, found by stack scanning');
  126. }
  127. if (inlineFrame) {
  128. return t('Inline frame');
  129. }
  130. if (foundByStackScanning) {
  131. return t('Found by stack scanning');
  132. }
  133. return undefined;
  134. }
  135. function getFunctionName() {
  136. if (functionNameHiddenDetails && fullFunctionName && frame.rawFunction) {
  137. return {
  138. value: frame.rawFunction,
  139. meta: frameMeta?.rawFunction?.[''],
  140. };
  141. }
  142. if (frame.function) {
  143. return {
  144. value: frame.function,
  145. meta: frameMeta?.function?.[''],
  146. };
  147. }
  148. return undefined;
  149. }
  150. function getStatus() {
  151. // this is the status of image that belongs to this frame
  152. if (!image) {
  153. return undefined;
  154. }
  155. const combinedStatus = combineStatus(image.debug_status, image.unwind_status);
  156. switch (combinedStatus) {
  157. case 'unused':
  158. return undefined;
  159. case 'found':
  160. return 'success';
  161. default:
  162. return 'error';
  163. }
  164. }
  165. function handleGoToImagesLoaded(e: MouseEvent) {
  166. e.stopPropagation(); // to prevent collapsing if collapsible
  167. if (frame.instructionAddr) {
  168. const searchTerm =
  169. !(!frame.addrMode || frame.addrMode === 'abs') && image
  170. ? `${image.debug_id}!${frame.instructionAddr}`
  171. : frame.instructionAddr;
  172. DebugMetaStore.updateFilter(searchTerm);
  173. }
  174. scrollToElement('#images-loaded');
  175. }
  176. function handleToggleContext(e: MouseEvent) {
  177. if (!expandable) {
  178. return;
  179. }
  180. e.preventDefault();
  181. setExpanded(!expanded);
  182. }
  183. const relativeAddress = getRelativeAddress();
  184. const addressTooltip = getAddressTooltip();
  185. const functionName = getFunctionName();
  186. const status = getStatus();
  187. return (
  188. <li data-test-id="stack-trace-frame">
  189. <StrictClick onClick={handleToggleContext}>
  190. <RowHeader expandable={expandable} expanded={expanded}>
  191. <div>
  192. {status === 'error' ? (
  193. <Tooltip
  194. title={t(
  195. 'This frame has missing debug files and could not be symbolicated'
  196. )}
  197. >
  198. <IconFileBroken size="sm" color="errorText" />
  199. </Tooltip>
  200. ) : status === undefined ? (
  201. <Tooltip
  202. title={t(
  203. 'This frame has an unknown problem and could not be symbolicated'
  204. )}
  205. >
  206. <IconWarning size="sm" color="warningText" />
  207. </Tooltip>
  208. ) : (
  209. <Tooltip title={t('This frame has been successfully symbolicated')}>
  210. <IconCheckmark size="sm" color="successText" />
  211. </Tooltip>
  212. )}
  213. </div>
  214. <div>
  215. {!fullStackTrace && !expanded && leadsToApp && (
  216. <Fragment>
  217. {!nextFrame ? (
  218. <PackageNote>{t('Crashed in Non-App')}</PackageNote>
  219. ) : (
  220. <PackageNote>{t('Called from')}</PackageNote>
  221. )}
  222. </Fragment>
  223. )}
  224. <Tooltip
  225. title={frame.package ?? t('Go to images loaded')}
  226. position="bottom"
  227. containerDisplayMode="inline-flex"
  228. delay={tooltipDelay}
  229. >
  230. <Package>
  231. {frame.package ? trimPackage(frame.package) : `<${t('unknown')}>`}
  232. </Package>
  233. </Tooltip>
  234. </div>
  235. <AddressCell onClick={packageClickable ? handleGoToImagesLoaded : undefined}>
  236. <Tooltip
  237. title={addressTooltip}
  238. disabled={!(foundByStackScanning || inlineFrame)}
  239. delay={tooltipDelay}
  240. >
  241. {!relativeAddress || absolute ? frame.instructionAddr : relativeAddress}
  242. </Tooltip>
  243. </AddressCell>
  244. <FunctionNameCell>
  245. {functionName ? (
  246. <AnnotatedText value={functionName.value} meta={functionName.meta} />
  247. ) : (
  248. `<${t('unknown')}>`
  249. )}
  250. {frame.filename && (
  251. <Tooltip
  252. title={frame.absPath}
  253. disabled={!(defined(frame.absPath) && frame.absPath !== frame.filename)}
  254. delay={tooltipDelay}
  255. >
  256. <FileName>
  257. {'('}
  258. {absoluteFilePaths ? frame.absPath : frame.filename}
  259. {frame.lineNo && `:${frame.lineNo}`}
  260. {')'}
  261. </FileName>
  262. </Tooltip>
  263. )}
  264. </FunctionNameCell>
  265. <GroupingCell>
  266. {isUsedForGrouping && (
  267. <Tooltip title={t('This frame is repeated in every event of this issue')}>
  268. <IconRepeat size="sm" color="textColor" />
  269. </Tooltip>
  270. )}
  271. </GroupingCell>
  272. <TypeCell>
  273. {frame.inApp ? (
  274. <Tag>{t('In App')}</Tag>
  275. ) : (
  276. <Tag type="info">{t('System')}</Tag>
  277. )}
  278. </TypeCell>
  279. <ExpandCell>
  280. {expandable && (
  281. <ToggleButton
  282. size="zero"
  283. css={isDotnet(platform) && {display: 'block !important'}} // remove important once we get rid of css files
  284. title={t('Toggle Context')}
  285. aria-label={t('Toggle Context')}
  286. tooltipProps={isHoverPreviewed ? {delay: SLOW_TOOLTIP_DELAY} : undefined}
  287. icon={
  288. <IconChevron legacySize="8px" direction={expanded ? 'up' : 'down'} />
  289. }
  290. />
  291. )}
  292. </ExpandCell>
  293. </RowHeader>
  294. </StrictClick>
  295. {expanded && (
  296. <Registers
  297. frame={frame}
  298. event={event}
  299. registers={registers}
  300. components={components}
  301. hasContextSource={hasContextSource(frame)}
  302. hasContextVars={hasContextVars(frame)}
  303. hasContextRegisters={hasContextRegisters(registers)}
  304. emptySourceNotation={emptySourceNotation}
  305. hasAssembly={hasAssembly(frame, platform)}
  306. expandable={expandable}
  307. isExpanded={expanded}
  308. registersMeta={registersMeta}
  309. frameMeta={frameMeta}
  310. />
  311. )}
  312. </li>
  313. );
  314. }
  315. export default withSentryAppComponents(NativeFrame, {componentType: 'stacktrace-link'});
  316. const AddressCell = styled('div')`
  317. ${p => p.onClick && `cursor: pointer`};
  318. ${p => p.onClick && `color:` + p.theme.linkColor};
  319. `;
  320. const FunctionNameCell = styled('div')`
  321. font-family: ${p => p.theme.text.familyMono};
  322. word-break: break-all;
  323. @media (max-width: ${p => p.theme.breakpoints.small}) {
  324. grid-column: 2/6;
  325. }
  326. `;
  327. const GroupingCell = styled('div')`
  328. @media (max-width: ${p => p.theme.breakpoints.small}) {
  329. grid-row: 2/3;
  330. }
  331. `;
  332. const TypeCell = styled('div')`
  333. @media (max-width: ${p => p.theme.breakpoints.small}) {
  334. grid-column: 5/6;
  335. grid-row: 1/2;
  336. }
  337. `;
  338. const ExpandCell = styled('div')`
  339. @media (max-width: ${p => p.theme.breakpoints.small}) {
  340. grid-column: 6/7;
  341. grid-row: 1/2;
  342. }
  343. `;
  344. const ToggleButton = styled(Button)`
  345. width: 16px;
  346. height: 16px;
  347. `;
  348. const Registers = styled(Context)`
  349. border-bottom: 1px solid ${p => p.theme.border};
  350. padding: 0;
  351. margin: 0;
  352. `;
  353. const PackageNote = styled('div')`
  354. color: ${p => p.theme.subText};
  355. font-size: ${p => p.theme.fontSizeExtraSmall};
  356. `;
  357. const Package = styled('span')`
  358. white-space: nowrap;
  359. overflow: hidden;
  360. text-overflow: ellipsis;
  361. width: 100%;
  362. `;
  363. const FileName = styled('span')`
  364. color: ${p => p.theme.subText};
  365. border-bottom: 1px dashed ${p => p.theme.border};
  366. `;
  367. const RowHeader = styled('span')<{expandable: boolean; expanded: boolean}>`
  368. display: grid;
  369. grid-template-columns: repeat(2, auto) 1fr repeat(2, auto);
  370. grid-template-rows: repeat(2, auto);
  371. align-items: center;
  372. align-content: center;
  373. column-gap: ${space(1)};
  374. border-bottom: 1px solid ${p => p.theme.border};
  375. background-color: ${p => p.theme.bodyBackground};
  376. font-size: ${p => p.theme.codeFontSize};
  377. padding: ${space(1)};
  378. ${p => p.expandable && `cursor: pointer;`};
  379. ${p =>
  380. p.expandable && `grid-template-columns: repeat(2, auto) 1fr repeat(2, auto) 16px;`};
  381. @media (min-width: ${p => p.theme.breakpoints.small}) {
  382. grid-template-columns: auto 150px 120px 4fr auto auto;
  383. ${p => p.expandable && `grid-template-columns: auto 150px 120px 4fr auto auto 16px;`};
  384. padding: ${space(0.5)} ${space(1.5)};
  385. min-height: 32px;
  386. }
  387. `;