| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import re |
| |
| from analysis import callstack_detectors |
| from analysis.callstack_filters import FilterFramesAfterBlinkGeneratedCode |
| from analysis.callstack_filters import ( |
| FilterFramesBeforeAndInBetweenSignatureParts) |
| from analysis.callstack_filters import FilterJavaCallStackIfNotCrashStack |
| from analysis.callstack_filters import FilterJavaJreSdkFrames |
| from analysis.callstack_filters import FilterV8FramesForV8APIBindingCode |
| from analysis.callstack_filters import FilterV8FramesIfV8NotInTopFrames |
| from analysis.callstack_filters import KeepV8FramesIfV8GeneratedJITCrash |
| from analysis.callstack_filters import KeepTopNFrames |
| from analysis.flag_manager import ParsingFlag |
| from analysis.flag_manager import FlagManager |
| from analysis.stacktrace import CallStackBuffer |
| from analysis.stacktrace import StackFrame |
| from analysis.stacktrace import StacktraceBuffer |
| from analysis.type_enums import CallStackFormatType |
| from analysis.type_enums import CrashType |
| from analysis.type_enums import LanguageType |
| from analysis.type_enums import SanitizerType |
| |
| TOP_FRAME_HAS_NO_SYMBOLS_REGEX = re.compile( |
| r'.*#0 0x[0-9a-f]+ \(<unknown module>\).*') |
| SUMMARY_MARKER = 'SUMMARY:' |
| JAVA_FATAL_EXCEPTION_REGEX = re.compile('.*FATAL EXCEPTION.*:') |
| |
| ANDROID_JOB_TYPE_MARKER = 'android' |
| DEFAULT_TOP_N_FRAMES = 7 |
| |
| CALLSTACK_FLAG_GROUP = 'callstack_flags' |
| STACKTRACE_FLAG_GROUP = 'stacktrace_flags' |
| |
| |
| CRASH_TYPE_TO_CALLSTACK_DETECTOR_CLASS = { |
| CrashType.DIRECT_LEAK: callstack_detectors.DirectLeakDetector, |
| CrashType.INDIRECT_LEAK: callstack_detectors.IndirectLeakDetector, |
| CrashType.CHECK_FAILURE: callstack_detectors.CheckFailureDetector, |
| } |
| |
| |
| def GetCallStackDetectors(job_type, crash_type): |
| """Returns a list ``CallStackDetector`` to detect callstacks.""" |
| detectors = [ |
| callstack_detectors.AsanDetector(), |
| callstack_detectors.MsanDetector(), |
| callstack_detectors.SyzyasanDetector(), |
| callstack_detectors.UbsanDetector(), |
| callstack_detectors.LibFuzzerDetector(), |
| callstack_detectors.TsanDetector(), |
| callstack_detectors.GeneralSanitizerDetector()] |
| |
| if ANDROID_JOB_TYPE_MARKER in job_type: |
| detectors = [callstack_detectors.AndroidJobDetector()] + detectors |
| |
| if crash_type in CRASH_TYPE_TO_CALLSTACK_DETECTOR_CLASS: |
| detectors = ([CRASH_TYPE_TO_CALLSTACK_DETECTOR_CLASS[crash_type]()] + |
| detectors) |
| |
| return detectors |
| |
| |
| class ClusterfuzzParser(object): |
| |
| def __init__(self): |
| self.flag_manager = FlagManager() |
| self.flag_manager.Register(STACKTRACE_FLAG_GROUP, ParsingFlag( |
| 'java_main_stack', lambda line: # pylint: disable=W0108 |
| JAVA_FATAL_EXCEPTION_REGEX.match(line))) |
| self.flag_manager.Register(STACKTRACE_FLAG_GROUP, ParsingFlag( |
| 'after_summary_line', lambda line: # pylint: disable=W0108 |
| SUMMARY_MARKER in line)) |
| # This flag is True at the very beginning and will never be changed once it |
| # is set to False. |
| self.flag_manager.Register(STACKTRACE_FLAG_GROUP, ParsingFlag( |
| 'is_first_stack', |
| lambda line: False, value=True)) # pylint: disable=W0108 |
| self.flag_manager.Register(CALLSTACK_FLAG_GROUP, ParsingFlag( |
| 'top_frame_has_no_symbol', lambda line: # pylint: disable=W0108 |
| TOP_FRAME_HAS_NO_SYMBOLS_REGEX.match(line))) |
| |
| def UpdateMetadataWithFlags(self, stack_buffer): |
| """Updates metadata with callstack flags. Returns updated stack buffer.""" |
| for flag in self.flag_manager.GetGroupFlags(CALLSTACK_FLAG_GROUP): |
| stack_buffer.metadata[flag.name] = flag.value |
| return stack_buffer |
| |
| def Parse(self, stacktrace_string, deps, job_type, |
| crash_type, signature=None, top_n_frames=None, |
| crash_address=None, root_path=None, root_repo_url=None): |
| """Parse clusterfuzz stacktrace string into Stacktrace instance.""" |
| filters = [FilterFramesBeforeAndInBetweenSignatureParts(signature), |
| FilterJavaCallStackIfNotCrashStack(), |
| FilterJavaJreSdkFrames(), |
| KeepV8FramesIfV8GeneratedJITCrash(), |
| FilterV8FramesForV8APIBindingCode(crash_address), |
| FilterFramesAfterBlinkGeneratedCode(), |
| FilterV8FramesIfV8NotInTopFrames(), |
| KeepTopNFrames(top_n_frames or DEFAULT_TOP_N_FRAMES)] |
| stacktrace_buffer = StacktraceBuffer(filters=filters) |
| stack_detectors = GetCallStackDetectors(job_type, crash_type) |
| |
| # Initial background callstack which is not to be added into Stacktrace. |
| stack_buffer = CallStackBuffer() |
| # Reset both stacktrace and callstack flags. |
| self.flag_manager.ResetAllFlags() |
| for line in stacktrace_string.splitlines(): |
| for stack_detector in stack_detectors: |
| # Note, some flags like is_first_stack may be changed inside of stack |
| # detector. |
| start_of_callstack = stack_detector(line, flags=self.flag_manager) |
| if start_of_callstack: |
| break |
| |
| if start_of_callstack: |
| stacktrace_buffer.AddFilteredStack( |
| self.UpdateMetadataWithFlags(stack_buffer)) |
| # Create new stack and reset callstack scope flags. |
| stack_buffer = CallStackBuffer.FromStartOfCallStack(start_of_callstack) |
| self.flag_manager.ResetGroupFlags(CALLSTACK_FLAG_GROUP) |
| else: |
| frame = StackFrame.Parse(stack_buffer.language_type, |
| stack_buffer.format_type, line, deps, |
| len(stack_buffer.frames), root_path=root_path, |
| root_repo_url=root_repo_url) |
| if frame is not None: |
| stack_buffer.frames.append(frame) |
| |
| # Turn on flags if condition met. |
| self.flag_manager.ConditionallyTurnOnFlags(line) |
| |
| # Add the last stack to stacktrace. |
| stacktrace_buffer.AddFilteredStack( |
| self.UpdateMetadataWithFlags(stack_buffer)) |
| return stacktrace_buffer.ToStacktrace() |