import type {MouseEvent} from 'react'; import {Fragment, useContext, useState} from 'react'; import styled from '@emotion/styled'; import scrollToElement from 'scroll-to-element'; import {Button} from 'sentry/components/button'; import ErrorBoundary from 'sentry/components/errorBoundary'; import {OpenInContextLine} from 'sentry/components/events/interfaces/frame/openInContextLine'; import {StacktraceLink} from 'sentry/components/events/interfaces/frame/stacktraceLink'; import { getLeadHint, hasAssembly, hasContextRegisters, hasContextSource, hasContextVars, isExpandable, trimPackage, } from 'sentry/components/events/interfaces/frame/utils'; import {formatAddress, parseAddress} from 'sentry/components/events/interfaces/utils'; import {AnnotatedText} from 'sentry/components/events/meta/annotatedText'; import {TraceEventDataSectionContext} from 'sentry/components/events/traceEventDataSection'; import StrictClick from 'sentry/components/strictClick'; import Tag from 'sentry/components/tag'; import {Tooltip} from 'sentry/components/tooltip'; import {SLOW_TOOLTIP_DELAY} from 'sentry/constants'; import {IconChevron} from 'sentry/icons/iconChevron'; import {IconFileBroken} from 'sentry/icons/iconFileBroken'; import {IconRefresh} from 'sentry/icons/iconRefresh'; import {IconWarning} from 'sentry/icons/iconWarning'; import {t, tn} from 'sentry/locale'; import DebugMetaStore from 'sentry/stores/debugMetaStore'; import {space} from 'sentry/styles/space'; import type { Frame, PlatformKey, SentryAppComponent, SentryAppSchemaStacktraceLink, } from 'sentry/types'; import type {Event} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import withSentryAppComponents from 'sentry/utils/withSentryAppComponents'; import type DebugImage from './debugMeta/debugImage'; import {combineStatus} from './debugMeta/utils'; import Context from './frame/context'; import {SymbolicatorStatus} from './types'; type Props = { components: SentryAppComponent[]; event: Event; frame: Frame; isUsedForGrouping: boolean; platform: PlatformKey; registers: Record; emptySourceNotation?: boolean; frameMeta?: Record; hiddenFrameCount?: number; image?: React.ComponentProps['image']; includeSystemFrames?: boolean; isExpanded?: boolean; isHoverPreviewed?: boolean; isOnlyFrame?: boolean; isShowFramesToggleExpanded?: boolean; /** * Frames that are hidden under the most recent non-InApp frame */ isSubFrame?: boolean; maxLengthOfRelativeAddress?: number; nextFrame?: Frame; onShowFramesToggle?: (event: React.MouseEvent) => void; prevFrame?: Frame; registersMeta?: Record; showStackedFrames?: boolean; }; function NativeFrame({ frame, nextFrame, prevFrame, includeSystemFrames, isUsedForGrouping, maxLengthOfRelativeAddress, image, registers, isOnlyFrame, event, components, hiddenFrameCount, isShowFramesToggleExpanded, isSubFrame, onShowFramesToggle, isExpanded, platform, registersMeta, frameMeta, emptySourceNotation = false, /** * Is the stack trace being previewed in a hovercard? */ isHoverPreviewed = false, }: Props) { const traceEventDataSectionContext = useContext(TraceEventDataSectionContext); const absolute = traceEventDataSectionContext?.display.includes('absolute-addresses'); const fullStackTrace = traceEventDataSectionContext?.fullStackTrace; const fullFunctionName = traceEventDataSectionContext?.display.includes( 'verbose-function-names' ); const absoluteFilePaths = traceEventDataSectionContext?.display.includes('absolute-file-paths'); const tooltipDelay = isHoverPreviewed ? SLOW_TOOLTIP_DELAY : undefined; const foundByStackScanning = frame.trust === 'scan' || frame.trust === 'cfi-scan'; const startingAddress = image ? image.image_addr : null; const packageClickable = !!frame.symbolicatorStatus && frame.symbolicatorStatus !== SymbolicatorStatus.UNKNOWN_IMAGE && !isHoverPreviewed; const leadsToApp = !frame.inApp && (nextFrame?.inApp || !nextFrame); const expandable = !leadsToApp || includeSystemFrames ? isExpandable({ frame, registers, platform, emptySourceNotation, isOnlyFrame, }) : false; const inlineFrame = prevFrame && platform === (prevFrame.platform || platform) && frame.instructionAddr === prevFrame.instructionAddr; const functionNameHiddenDetails = defined(frame.rawFunction) && defined(frame.function) && frame.function !== frame.rawFunction; const [expanded, setExpanded] = useState(expandable ? isExpanded ?? false : false); const [isHovering, setHovering] = useState(false); const contextLine = (frame?.context || []).find(l => l[0] === frame.lineNo); const hasStacktraceLink = frame.inApp && !!frame.filename && (isHovering || expanded); const showSentryAppStacktraceLinkInFrame = hasStacktraceLink && components.length > 0; const handleMouseEnter = () => setHovering(true); const handleMouseLeave = () => setHovering(false); function getRelativeAddress() { if (!startingAddress) { return ''; } const relativeAddress = formatAddress( parseAddress(frame.instructionAddr) - parseAddress(startingAddress), maxLengthOfRelativeAddress ); return `+${relativeAddress}`; } function getAddressTooltip() { if (inlineFrame && foundByStackScanning) { return t('Inline frame, found by stack scanning'); } if (inlineFrame) { return t('Inline frame'); } if (foundByStackScanning) { return t('Found by stack scanning'); } return undefined; } function getFunctionName() { if (functionNameHiddenDetails && fullFunctionName && frame.rawFunction) { return { value: frame.rawFunction, meta: frameMeta?.rawFunction?.[''], }; } if (frame.function) { return { value: frame.function, meta: frameMeta?.function?.[''], }; } return undefined; } // this is the status of image that belongs to this frame function getStatus() { // If a matching debug image doesn't exist, fall back to symbolicator_status if (!image) { switch (frame.symbolicatorStatus) { case SymbolicatorStatus.SYMBOLICATED: return 'success'; case SymbolicatorStatus.MISSING: case SymbolicatorStatus.MALFORMED: return 'error'; case SymbolicatorStatus.MISSING_SYMBOL: case SymbolicatorStatus.UNKNOWN_IMAGE: default: return undefined; } } const combinedStatus = combineStatus(image.debug_status, image.unwind_status); switch (combinedStatus) { case 'unused': return undefined; case 'found': return 'success'; default: return 'error'; } } function handleGoToImagesLoaded(e: MouseEvent) { e.stopPropagation(); // to prevent collapsing if collapsible if (frame.instructionAddr) { const searchTerm = !(!frame.addrMode || frame.addrMode === 'abs') && image ? `${image.debug_id}!${frame.instructionAddr}` : frame.instructionAddr; DebugMetaStore.updateFilter(searchTerm); } scrollToElement('#images-loaded'); } function handleToggleContext(e: MouseEvent) { if (!expandable) { return; } e.preventDefault(); setExpanded(!expanded); } const relativeAddress = getRelativeAddress(); const addressTooltip = getAddressTooltip(); const functionName = getFunctionName(); const status = getStatus(); return ( {status === 'error' ? ( ) : status === undefined ? ( ) : null}
{!fullStackTrace && !expanded && leadsToApp && ( {getLeadHint({event, hasNextFrame: defined(nextFrame)})} )} {frame.package ? trimPackage(frame.package) : `<${t('unknown')}>`}
{!relativeAddress || absolute ? frame.instructionAddr : relativeAddress} {functionName ? ( ) : ( `<${t('unknown')}>` )}{' '} {frame.filename && ( {'('} {absoluteFilePaths ? frame.absPath : frame.filename} {frame.lineNo && `:${frame.lineNo}`} {')'} )} {isUsedForGrouping && ( )} {hiddenFrameCount ? ( { onShowFramesToggle?.(e); }} > {isShowFramesToggleExpanded ? tn('Hide %s more frame', 'Hide %s more frames', hiddenFrameCount) : tn('Show %s more frame', 'Show %s more frames', hiddenFrameCount)} ) : null} {hasStacktraceLink && ( )} {showSentryAppStacktraceLinkInFrame && ( )} {frame.inApp ? {t('In App')} : null} {expandable && ( } /> )}
{expanded && ( )}
); } export default withSentryAppComponents(NativeFrame, {componentType: 'stacktrace-link'}); const GenericCellWrapper = styled('div')` display: flex; `; const AddressCell = styled('div')` font-family: ${p => p.theme.text.familyMono}; ${p => p.onClick && `cursor: pointer`}; ${p => p.onClick && `color:` + p.theme.linkColor}; `; const FunctionNameCell = styled('div')` word-break: break-all; @media (max-width: ${p => p.theme.breakpoints.small}) { grid-column: 2/6; } `; const GroupingCell = styled('div')` @media (max-width: ${p => p.theme.breakpoints.small}) { grid-row: 2/3; } `; const TypeCell = styled('div')` @media (max-width: ${p => p.theme.breakpoints.small}) { grid-column: 5/6; grid-row: 1/2; } `; const ExpandCell = styled('div')` @media (max-width: ${p => p.theme.breakpoints.small}) { grid-column: 6/7; grid-row: 1/2; } `; const ToggleButton = styled(Button)` width: 16px; height: 16px; `; const Registers = styled(Context)` border-bottom: 1px solid ${p => p.theme.border}; padding: 0; margin: 0; `; const PackageNote = styled('div')` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeExtraSmall}; `; const Package = styled('span')` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; padding-right: 2px; /* Needed to prevent text cropping with italic font */ `; const FileName = styled('span')` color: ${p => p.theme.subText}; border-bottom: 1px dashed ${p => p.theme.border}; `; const RowHeader = styled('span')<{ expandable: boolean; expanded: boolean; isInAppFrame: boolean; isSubFrame: boolean; }>` display: grid; grid-template-columns: repeat(2, auto) 1fr repeat(2, auto) ${space(2)}; grid-template-rows: repeat(2, auto); align-items: center; align-content: center; column-gap: ${space(1)}; background-color: ${p => !p.isInAppFrame && p.isSubFrame ? `${p.theme.surface100}` : `${p.theme.bodyBackground}`}; font-size: ${p => p.theme.fontSizeSmall}; padding: ${space(1)}; color: ${p => (!p.isInAppFrame ? p.theme.subText : '')}; font-style: ${p => (!p.isInAppFrame ? 'italic' : '')}; ${p => p.expandable && `cursor: pointer;`}; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: auto 150px 120px 4fr repeat(2, auto) ${space(2)}; padding: ${space(0.5)} ${space(1.5)}; min-height: 32px; } `; const StackTraceFrame = styled('li')` :not(:last-child) { ${RowHeader} { border-bottom: 1px solid ${p => p.theme.border}; } } &:last-child { ${RowHeader} { border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; } } &:first-child { ${RowHeader} { border-top-right-radius: 5px; } } `; const SymbolicatorIcon = styled('div')` width: ${p => p.theme.iconSizes.sm}; `; const ShowHideButton = styled(Button)` color: ${p => p.theme.subText}; font-style: italic; font-weight: normal; padding: ${space(0.25)} ${space(0.5)}; &:hover { color: ${p => p.theme.subText}; } `;