(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) (** Module for parsing stack traces and using them to guide Infer analysis *) open! IStd module F = Format module L = Logging type frame = {class_str: string; method_str: string; file_str: string; line_num: int option} type t = {exception_name: string; frames: frame list} let new_line_regexp = Str.regexp "\n" (* Pre-compute the regular expression matchers used to parse: *) (* Stack frames into (procedure, location) tuples. *) let frame_regexp = Str.regexp "\t*at \\(.*\\)(\\(.*\\))" (* procedures into class and method name. *) let procname_regexp = Str.regexp "\\(.*\\)\\.\\(.*\\)" (* locations into file and line number information. *) let file_and_line_regexp = Str.regexp "\\(.*\\):\\([0-9]+\\)" (* exception information lines into thread id and exception type *) let exception_regexp = Str.regexp "Exception in thread \"\\(.*\\)\" \\(.*\\)" let make exception_name frames = {exception_name; frames} let make_frame class_str method_str file_str line_num = {class_str; method_str; file_str; line_num} let frame_matches_location frame_obj loc = let lfname = if SourceFile.is_invalid loc.Location.file then None else Some (SourceFile.to_string loc.Location.file) in let matches_file = Option.value_map lfname ~default:false ~f:(String.is_suffix ~suffix:frame_obj.file_str) in let matches_line = match frame_obj.line_num with None -> false | Some line -> Int.equal line loc.Location.line in matches_file && matches_line let parse_stack_frame frame_str = (* separate the qualified method name and the parenthesized text/line number*) ignore (Str.string_match frame_regexp frame_str 0) ; let qualified_procname = Str.matched_group 1 frame_str in let file_and_line = Str.matched_group 2 frame_str in (* separate the class name from the method name *) ignore (Str.string_match procname_regexp qualified_procname 0) ; let class_str = Str.matched_group 1 qualified_procname in let method_str = Str.matched_group 2 qualified_procname in (* Native methods don't have debugging info *) if String.equal file_and_line "Native Method" then make_frame class_str method_str "Native Method" None else (* Separate the filename and line number. note that a few methods might not have line number information, for those, file_and_line includes only the filename. *) let is_file_line = Str.string_match file_and_line_regexp file_and_line 0 in let file_str, line_num = if is_file_line then ( Str.matched_group 1 file_and_line , Some (int_of_string (Str.matched_group 2 file_and_line)) ) else (file_and_line, None) in make_frame class_str method_str file_str line_num let parse_exception_line exception_line = ignore (Str.string_match exception_regexp exception_line 0) ; let exception_name = Str.matched_group 2 exception_line in exception_name let of_string s = let lines = Str.split new_line_regexp s in match lines with | exception_line :: trace -> let exception_name = parse_exception_line exception_line in let parsed = List.map ~f:parse_stack_frame trace in make exception_name parsed | [] -> L.(die UserError) "Empty stack trace" let of_json filename json = let exception_name_key = "exception_type" in let frames_key = "stack_trace" in let extract_json_member key = match Yojson.Basic.Util.member key json with | `Null -> L.(die UserError) "Missing key in supplied JSON data: %s (in file %s)" key filename | item -> item in let exception_name = Yojson.Basic.Util.to_string (extract_json_member exception_name_key) in let frames = Yojson.Basic.Util.to_list (extract_json_member frames_key) |> List.map ~f:Yojson.Basic.Util.to_string |> List.map ~f:String.strip |> List.filter ~f:(fun s -> s <> "") |> List.map ~f:parse_stack_frame in make exception_name frames let of_json_file filename = try of_json filename (Yojson.Basic.from_file filename) with | Sys_error msg | Yojson.Json_error msg -> L.(die UserError) "Could not read or parse the supplied JSON stacktrace file %s :@\n %s" filename msg