nativeFrame.tsx 13 KB

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