content.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import {cloneElement, Fragment, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  4. import {FrameSourceMapDebuggerData} from 'sentry/components/events/interfaces/sourceMapsDebuggerModal';
  5. import Panel from 'sentry/components/panels/panel';
  6. import {t} from 'sentry/locale';
  7. import {Frame, Organization, PlatformKey} from 'sentry/types';
  8. import {Event} from 'sentry/types/event';
  9. import {StackTraceMechanism, StacktraceType} from 'sentry/types/stacktrace';
  10. import {defined} from 'sentry/utils';
  11. import withOrganization from 'sentry/utils/withOrganization';
  12. import DeprecatedLine, {DeprecatedLineProps} from '../../frame/deprecatedLine';
  13. import {
  14. findImageForAddress,
  15. getHiddenFrameIndices,
  16. getLastFrameIndex,
  17. isRepeatedFrame,
  18. parseAddress,
  19. stackTracePlatformIcon,
  20. } from '../../utils';
  21. import StacktracePlatformIcon from './platformIcon';
  22. type DefaultProps = {
  23. expandFirstFrame: boolean;
  24. includeSystemFrames: boolean;
  25. };
  26. type Props = {
  27. data: StacktraceType;
  28. event: Event;
  29. platform: PlatformKey;
  30. className?: string;
  31. frameSourceMapDebuggerData?: FrameSourceMapDebuggerData[];
  32. hideIcon?: boolean;
  33. isHoverPreviewed?: boolean;
  34. lockAddress?: string;
  35. maxDepth?: number;
  36. mechanism?: StackTraceMechanism | null;
  37. meta?: Record<any, any>;
  38. newestFirst?: boolean;
  39. organization?: Organization;
  40. threadId?: number;
  41. } & Partial<DefaultProps>;
  42. function Content({
  43. data,
  44. event,
  45. className,
  46. newestFirst,
  47. expandFirstFrame = true,
  48. platform,
  49. includeSystemFrames = true,
  50. isHoverPreviewed,
  51. maxDepth,
  52. meta,
  53. hideIcon,
  54. threadId,
  55. lockAddress,
  56. organization,
  57. frameSourceMapDebuggerData,
  58. }: Props) {
  59. const [showingAbsoluteAddresses, setShowingAbsoluteAddresses] = useState(false);
  60. const [showCompleteFunctionName, setShowCompleteFunctionName] = useState(false);
  61. const [toggleFrameMap, setToggleFrameMap] = useState(setInitialFrameMap());
  62. const {frames = [], framesOmitted, registers} = data;
  63. function frameIsVisible(frame: Frame, nextFrame: Frame) {
  64. return (
  65. includeSystemFrames ||
  66. frame.inApp ||
  67. (nextFrame && nextFrame.inApp) ||
  68. // the last non-app frame
  69. (!frame.inApp && !nextFrame)
  70. );
  71. }
  72. function setInitialFrameMap(): {[frameIndex: number]: boolean} {
  73. const indexMap = {};
  74. (data.frames ?? []).forEach((frame, frameIdx) => {
  75. const nextFrame = (data.frames ?? [])[frameIdx + 1];
  76. const repeatedFrame = isRepeatedFrame(frame, nextFrame);
  77. if (frameIsVisible(frame, nextFrame) && !repeatedFrame && !frame.inApp) {
  78. indexMap[frameIdx] = false;
  79. }
  80. });
  81. return indexMap;
  82. }
  83. function getInitialFrameCounts(): {[frameIndex: number]: number} {
  84. let count = 0;
  85. const countMap = {};
  86. (data.frames ?? []).forEach((frame, frameIdx) => {
  87. const nextFrame = (data.frames ?? [])[frameIdx + 1];
  88. const repeatedFrame = isRepeatedFrame(frame, nextFrame);
  89. if (frameIsVisible(frame, nextFrame) && !repeatedFrame && !frame.inApp) {
  90. countMap[frameIdx] = count;
  91. count = 0;
  92. } else {
  93. if (!repeatedFrame && !frame.inApp) {
  94. count += 1;
  95. }
  96. }
  97. });
  98. return countMap;
  99. }
  100. function isFrameAfterLastNonApp(): boolean {
  101. if (!frames.length || frames.length < 2) {
  102. return false;
  103. }
  104. const lastFrame = frames[frames.length - 1];
  105. const penultimateFrame = frames[frames.length - 2];
  106. return penultimateFrame.inApp && !lastFrame.inApp;
  107. }
  108. function handleToggleAddresses(mouseEvent: React.MouseEvent<SVGElement>) {
  109. mouseEvent.stopPropagation(); // to prevent collapsing if collapsible
  110. setShowingAbsoluteAddresses(oldShowAbsAddresses => !oldShowAbsAddresses);
  111. }
  112. function handleToggleFunctionName(mouseEvent: React.MouseEvent<SVGElement>) {
  113. mouseEvent.stopPropagation(); // to prevent collapsing if collapsible
  114. setShowCompleteFunctionName(oldShowCompleteName => !oldShowCompleteName);
  115. }
  116. const handleToggleFrames = (
  117. mouseEvent: React.MouseEvent<HTMLElement>,
  118. frameIndex: number
  119. ) => {
  120. mouseEvent.stopPropagation(); // to prevent toggling frame context
  121. setToggleFrameMap(prevState => ({
  122. ...prevState,
  123. [frameIndex]: !prevState[frameIndex],
  124. }));
  125. };
  126. function renderOmittedFrames(firstFrameOmitted: any, lastFrameOmitted: any) {
  127. const props = {
  128. className: 'frame frames-omitted',
  129. key: 'omitted',
  130. };
  131. return (
  132. <li {...props}>
  133. {t(
  134. 'Frames %d until %d were omitted and not available.',
  135. firstFrameOmitted,
  136. lastFrameOmitted
  137. )}
  138. </li>
  139. );
  140. }
  141. const firstFrameOmitted = framesOmitted?.[0] ?? null;
  142. const lastFrameOmitted = framesOmitted?.[1] ?? null;
  143. const lastFrameIndex = getLastFrameIndex(frames);
  144. const frameCountMap = getInitialFrameCounts();
  145. const hiddenFrameIndices: number[] = getHiddenFrameIndices({
  146. data,
  147. toggleFrameMap,
  148. frameCountMap,
  149. });
  150. const mechanism =
  151. platform === 'java' && event.tags?.find(({key}) => key === 'mechanism')?.value;
  152. const isANR = mechanism === 'ANR' || mechanism === 'AppExitInfo';
  153. let nRepeats = 0;
  154. const maxLengthOfAllRelativeAddresses = frames.reduce(
  155. (maxLengthUntilThisPoint, frame) => {
  156. const correspondingImage = findImageForAddress({
  157. event,
  158. addrMode: frame.addrMode,
  159. address: frame.instructionAddr,
  160. });
  161. try {
  162. const relativeAddress = (
  163. parseAddress(frame.instructionAddr) -
  164. parseAddress(correspondingImage.image_addr)
  165. ).toString(16);
  166. return maxLengthUntilThisPoint > relativeAddress.length
  167. ? maxLengthUntilThisPoint
  168. : relativeAddress.length;
  169. } catch {
  170. return maxLengthUntilThisPoint;
  171. }
  172. },
  173. 0
  174. );
  175. let convertedFrames = frames
  176. .map((frame, frameIndex) => {
  177. const prevFrame = frames[frameIndex - 1];
  178. const nextFrame = frames[frameIndex + 1];
  179. const repeatedFrame = isRepeatedFrame(frame, nextFrame);
  180. if (repeatedFrame) {
  181. nRepeats++;
  182. }
  183. if (
  184. (frameIsVisible(frame, nextFrame) && !repeatedFrame) ||
  185. hiddenFrameIndices.includes(frameIndex)
  186. ) {
  187. const frameProps: DeprecatedLineProps = {
  188. event,
  189. data: frame,
  190. isExpanded: expandFirstFrame && lastFrameIndex === frameIndex,
  191. emptySourceNotation: lastFrameIndex === frameIndex && frameIndex === 0,
  192. isOnlyFrame: (data.frames ?? []).length === 1,
  193. nextFrame,
  194. prevFrame,
  195. platform,
  196. timesRepeated: nRepeats,
  197. showingAbsoluteAddress: showingAbsoluteAddresses,
  198. onAddressToggle: handleToggleAddresses,
  199. image: findImageForAddress({
  200. event,
  201. addrMode: frame.addrMode,
  202. address: frame.instructionAddr,
  203. }),
  204. maxLengthOfRelativeAddress: maxLengthOfAllRelativeAddresses,
  205. registers: {}, // TODO: Fix registers
  206. isFrameAfterLastNonApp: isFrameAfterLastNonApp(),
  207. includeSystemFrames,
  208. onFunctionNameToggle: handleToggleFunctionName,
  209. onShowFramesToggle: (e: React.MouseEvent<HTMLElement>) => {
  210. handleToggleFrames(e, frameIndex);
  211. },
  212. isSubFrame: hiddenFrameIndices.includes(frameIndex),
  213. isShowFramesToggleExpanded: toggleFrameMap[frameIndex],
  214. showCompleteFunctionName,
  215. isHoverPreviewed,
  216. frameMeta: meta?.frames?.[frameIndex],
  217. registersMeta: meta?.registers,
  218. isANR,
  219. threadId,
  220. lockAddress,
  221. hiddenFrameCount: frameCountMap[frameIndex],
  222. organization,
  223. frameSourceResolutionResults: frameSourceMapDebuggerData?.[frameIndex],
  224. };
  225. nRepeats = 0;
  226. if (frameIndex === firstFrameOmitted) {
  227. return (
  228. <Fragment key={frameIndex}>
  229. <DeprecatedLine {...frameProps} />
  230. {renderOmittedFrames(firstFrameOmitted, lastFrameOmitted)}
  231. </Fragment>
  232. );
  233. }
  234. return <DeprecatedLine key={frameIndex} {...frameProps} />;
  235. }
  236. if (!repeatedFrame) {
  237. nRepeats = 0;
  238. }
  239. if (frameIndex !== firstFrameOmitted) {
  240. return null;
  241. }
  242. return renderOmittedFrames(firstFrameOmitted, lastFrameOmitted);
  243. })
  244. .filter(frame => !!frame) as React.ReactElement[];
  245. if (convertedFrames.length > 0 && registers) {
  246. const lastFrame = convertedFrames.length - 1;
  247. convertedFrames[lastFrame] = cloneElement(convertedFrames[lastFrame], {
  248. registers,
  249. });
  250. }
  251. if (defined(maxDepth)) {
  252. convertedFrames = convertedFrames.slice(-maxDepth);
  253. }
  254. const wrapperClassName = `${!!className && className} traceback ${
  255. includeSystemFrames ? 'full-traceback' : 'in-app-traceback'
  256. }`;
  257. const platformIcon = stackTracePlatformIcon(platform, data.frames ?? []);
  258. return (
  259. <Wrapper className={wrapperClassName} data-test-id="stack-trace-content">
  260. {!hideIcon && <StacktracePlatformIcon platform={platformIcon} />}
  261. <GuideAnchor target="stack_trace">
  262. <StyledList data-test-id="frames">
  263. {!newestFirst ? convertedFrames : [...convertedFrames].reverse()}
  264. </StyledList>
  265. </GuideAnchor>
  266. </Wrapper>
  267. );
  268. }
  269. const Wrapper = styled(Panel)`
  270. position: relative;
  271. border-top-left-radius: 0;
  272. `;
  273. const StyledList = styled('ul')`
  274. list-style: none;
  275. `;
  276. export default withOrganization(Content);