content.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import {cloneElement, Component} from 'react';
  2. import styled from '@emotion/styled';
  3. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  4. import {t} from 'sentry/locale';
  5. import {Frame, Organization, PlatformType} from 'sentry/types';
  6. import {Event} from 'sentry/types/event';
  7. import {StacktraceType} from 'sentry/types/stacktrace';
  8. import withOrganization from 'sentry/utils/withOrganization';
  9. import Line from '../../frame/line';
  10. import {getImageRange, parseAddress, stackTracePlatformIcon} from '../../utils';
  11. import StacktracePlatformIcon from './platformIcon';
  12. type DefaultProps = {
  13. expandFirstFrame: boolean;
  14. includeSystemFrames: boolean;
  15. };
  16. type Props = {
  17. data: StacktraceType;
  18. event: Event;
  19. platform: PlatformType;
  20. className?: string;
  21. isHoverPreviewed?: boolean;
  22. meta?: Record<any, any>;
  23. newestFirst?: boolean;
  24. organization?: Organization;
  25. } & Partial<DefaultProps>;
  26. type State = {
  27. showCompleteFunctionName: boolean;
  28. showingAbsoluteAddresses: boolean;
  29. };
  30. class Content extends Component<Props, State> {
  31. static defaultProps: DefaultProps = {
  32. includeSystemFrames: true,
  33. expandFirstFrame: true,
  34. };
  35. state: State = {
  36. showingAbsoluteAddresses: false,
  37. showCompleteFunctionName: false,
  38. };
  39. renderOmittedFrames = (firstFrameOmitted, lastFrameOmitted) => {
  40. const props = {
  41. className: 'frame frames-omitted',
  42. key: 'omitted',
  43. };
  44. const text = t(
  45. 'Frames %d until %d were omitted and not available.',
  46. firstFrameOmitted,
  47. lastFrameOmitted
  48. );
  49. return <li {...props}>{text}</li>;
  50. };
  51. isFrameAfterLastNonApp(): boolean {
  52. const {data} = this.props;
  53. const frames = data.frames ?? [];
  54. if (!frames.length || frames.length < 2) {
  55. return false;
  56. }
  57. const lastFrame = frames[frames.length - 1];
  58. const penultimateFrame = frames[frames.length - 2];
  59. return penultimateFrame.inApp && !lastFrame.inApp;
  60. }
  61. frameIsVisible = (frame: Frame, nextFrame: Frame) => {
  62. const {includeSystemFrames} = this.props;
  63. return (
  64. includeSystemFrames ||
  65. frame.inApp ||
  66. (nextFrame && nextFrame.inApp) ||
  67. // the last non-app frame
  68. (!frame.inApp && !nextFrame)
  69. );
  70. };
  71. findImageForAddress(address: Frame['instructionAddr'], addrMode: Frame['addrMode']) {
  72. const images = this.props.event.entries.find(entry => entry.type === 'debugmeta')
  73. ?.data?.images;
  74. return images && address
  75. ? images.find((img, idx) => {
  76. if (!addrMode || addrMode === 'abs') {
  77. const [startAddress, endAddress] = getImageRange(img);
  78. return address >= (startAddress as any) && address < (endAddress as any);
  79. }
  80. return addrMode === `rel:${idx}`;
  81. })
  82. : null;
  83. }
  84. handleToggleAddresses = (event: React.MouseEvent<SVGElement>) => {
  85. event.stopPropagation(); // to prevent collapsing if collapsible
  86. this.setState(prevState => ({
  87. showingAbsoluteAddresses: !prevState.showingAbsoluteAddresses,
  88. }));
  89. };
  90. handleToggleFunctionName = (event: React.MouseEvent<SVGElement>) => {
  91. event.stopPropagation(); // to prevent collapsing if collapsible
  92. this.setState(prevState => ({
  93. showCompleteFunctionName: !prevState.showCompleteFunctionName,
  94. }));
  95. };
  96. getClassName() {
  97. const {className = '', includeSystemFrames} = this.props;
  98. if (includeSystemFrames) {
  99. return `${className} traceback full-traceback`;
  100. }
  101. return `${className} traceback in-app-traceback`;
  102. }
  103. render() {
  104. const {
  105. data,
  106. event,
  107. newestFirst,
  108. expandFirstFrame,
  109. platform,
  110. includeSystemFrames,
  111. isHoverPreviewed,
  112. meta,
  113. } = this.props;
  114. const {showingAbsoluteAddresses, showCompleteFunctionName} = this.state;
  115. let firstFrameOmitted = null;
  116. let lastFrameOmitted = null;
  117. if (data.framesOmitted) {
  118. firstFrameOmitted = data.framesOmitted[0];
  119. lastFrameOmitted = data.framesOmitted[1];
  120. }
  121. let lastFrameIdx: number | null = null;
  122. (data.frames ?? []).forEach((frame, frameIdx) => {
  123. if (frame.inApp) {
  124. lastFrameIdx = frameIdx;
  125. }
  126. });
  127. if (lastFrameIdx === null) {
  128. lastFrameIdx = (data.frames ?? []).length - 1;
  129. }
  130. const frames: React.ReactElement[] = [];
  131. let nRepeats = 0;
  132. const maxLengthOfAllRelativeAddresses = (data.frames ?? []).reduce(
  133. (maxLengthUntilThisPoint, frame) => {
  134. const correspondingImage = this.findImageForAddress(
  135. frame.instructionAddr,
  136. frame.addrMode
  137. );
  138. try {
  139. const relativeAddress = (
  140. parseAddress(frame.instructionAddr) -
  141. parseAddress(correspondingImage.image_addr)
  142. ).toString(16);
  143. return maxLengthUntilThisPoint > relativeAddress.length
  144. ? maxLengthUntilThisPoint
  145. : relativeAddress.length;
  146. } catch {
  147. return maxLengthUntilThisPoint;
  148. }
  149. },
  150. 0
  151. );
  152. const isFrameAfterLastNonApp = this.isFrameAfterLastNonApp();
  153. (data.frames ?? []).forEach((frame, frameIdx) => {
  154. const prevFrame = (data.frames ?? [])[frameIdx - 1];
  155. const nextFrame = (data.frames ?? [])[frameIdx + 1];
  156. const repeatedFrame =
  157. nextFrame &&
  158. frame.lineNo === nextFrame.lineNo &&
  159. frame.instructionAddr === nextFrame.instructionAddr &&
  160. frame.package === nextFrame.package &&
  161. frame.module === nextFrame.module &&
  162. frame.function === nextFrame.function;
  163. if (repeatedFrame) {
  164. nRepeats++;
  165. }
  166. if (this.frameIsVisible(frame, nextFrame) && !repeatedFrame) {
  167. const image = this.findImageForAddress(frame.instructionAddr, frame.addrMode);
  168. frames.push(
  169. <Line
  170. key={frameIdx}
  171. event={event}
  172. data={frame}
  173. isExpanded={expandFirstFrame && lastFrameIdx === frameIdx}
  174. emptySourceNotation={lastFrameIdx === frameIdx && frameIdx === 0}
  175. isOnlyFrame={(data.frames ?? []).length === 1}
  176. nextFrame={nextFrame}
  177. prevFrame={prevFrame}
  178. platform={platform}
  179. timesRepeated={nRepeats}
  180. showingAbsoluteAddress={showingAbsoluteAddresses}
  181. onAddressToggle={this.handleToggleAddresses}
  182. image={image}
  183. maxLengthOfRelativeAddress={maxLengthOfAllRelativeAddresses}
  184. registers={{}} // TODO: Fix registers
  185. isFrameAfterLastNonApp={isFrameAfterLastNonApp}
  186. includeSystemFrames={includeSystemFrames}
  187. onFunctionNameToggle={this.handleToggleFunctionName}
  188. showCompleteFunctionName={showCompleteFunctionName}
  189. isHoverPreviewed={isHoverPreviewed}
  190. isFirst={newestFirst ? frameIdx === lastFrameIdx : frameIdx === 0}
  191. frameMeta={meta?.frames?.[frameIdx]}
  192. registersMeta={meta?.registers}
  193. />
  194. );
  195. }
  196. if (!repeatedFrame) {
  197. nRepeats = 0;
  198. }
  199. if (frameIdx === firstFrameOmitted) {
  200. frames.push(this.renderOmittedFrames(firstFrameOmitted, lastFrameOmitted));
  201. }
  202. });
  203. if (frames.length > 0 && data.registers) {
  204. const lastFrame = frames.length - 1;
  205. frames[lastFrame] = cloneElement(frames[lastFrame], {
  206. registers: data.registers,
  207. });
  208. }
  209. if (newestFirst) {
  210. frames.reverse();
  211. }
  212. const className = this.getClassName();
  213. const platformIcon = stackTracePlatformIcon(platform, data.frames ?? []);
  214. return (
  215. <Wrapper className={className} data-test-id="stack-trace-content">
  216. <StacktracePlatformIcon platform={platformIcon} />
  217. <GuideAnchor target="stack_trace">
  218. <StyledList data-test-id="frames">{frames}</StyledList>
  219. </GuideAnchor>
  220. </Wrapper>
  221. );
  222. }
  223. }
  224. export default withOrganization(Content);
  225. const Wrapper = styled('div')`
  226. position: relative;
  227. `;
  228. const StyledList = styled('ul')`
  229. list-style: none;
  230. `;