(* * 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. *) open! IStd module F = Format module L = Logging (** find transitive procedure calls for each procedure *) module Domain = AbstractDomain.FiniteSet(Typ.Procname) (* Store a single stacktree frame per method. That is, callees is always []. Instead, the expanded per-method summaries are directly stored in the output directory as JSON files and *only* for those methods that will be part of the final crashcontext.json. *) module SpecSummary = Summary.Make (struct type payload = Stacktree_j.stacktree let update_payload frame (summary : Specs.summary) = let payload = { summary.payload with Specs.crashcontext_frame = Some frame } in { summary with payload = payload } let read_payload (summary : Specs.summary) = summary.payload.crashcontext_frame end) type extras_t = { get_proc_desc : Typ.Procname.t -> Procdesc.t option; stacktraces : Stacktrace.t list; } let line_range_of_pdesc pdesc = let ploc = Procdesc.get_loc pdesc in let start_line = ploc.Location.line in let end_line = Procdesc.fold_instrs (fun acc _ instr -> let new_loc = Sil.instr_get_loc instr in max acc new_loc.Location.line) start_line pdesc in { Stacktree_j.start_line; end_line } let stacktree_of_pdesc pdesc ?(loc=Procdesc.get_loc pdesc) ?(callees=[]) location_type = let procname = Procdesc.get_proc_name pdesc in let frame_loc = Some { Stacktree_j.location_type = location_type; file = SourceFile.to_string loc.Location.file; line = Some loc.Location.line; blame_range = [line_range_of_pdesc pdesc] } in { Stacktree_j.method_name = Typ.Procname.to_unique_id procname; location = frame_loc; callees = callees } let stacktree_stub_of_procname procname = { Stacktree_j.method_name = Typ.Procname.to_unique_id procname; location = None; callees = [] } module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain type extras = extras_t let stacktree_of_astate pdesc astate loc location_type get_proc_desc = let procs = Domain.elements astate in let callees = List.map ~f:(fun pn -> match SpecSummary.read_summary pdesc pn with | None -> (match get_proc_desc pn with | None -> stacktree_stub_of_procname pn (* This can happen when the callee is in the same cluster/ buck target, but it hasn't been checked yet. So we need both the inter-target lookup (SpecSummary) and the intra-target lookup (using get_proc_desc). *) | Some callee_pdesc -> stacktree_of_pdesc callee_pdesc "proc_start") | Some stracktree -> stracktree ) procs in stacktree_of_pdesc pdesc ~loc ~callees location_type let output_json_summary pdesc astate loc location_type get_proc_desc = let caller = Procdesc.get_proc_name pdesc in let stacktree = stacktree_of_astate pdesc astate loc location_type get_proc_desc in let dir = Filename.concat Config.results_dir "crashcontext" in let suffix = F.sprintf "%s_%d" location_type loc.Location.line in let fname = F.sprintf "%s.%s.json" (Typ.Procname.to_filename caller) suffix in let fpath = Filename.concat dir fname in Utils.create_dir dir; Ag_util.Json.to_file Stacktree_j.write_stacktree fpath stacktree let exec_instr astate proc_data _ = function | Sil.Call (_, Const (Const.Cfun pn), _, loc, _) -> let get_proc_desc = proc_data.ProcData.extras.get_proc_desc in let traces = proc_data.ProcData.extras.stacktraces in let caller = Procdesc.get_proc_name proc_data.ProcData.pdesc in let matches_proc frame = let matches_class pname = match pname with | Typ.Procname.Java java_proc -> String.equal frame.Stacktrace.class_str (Typ.Procname.java_get_class_name java_proc) | Typ.Procname.ObjC_Cpp objc_cpp_prod -> String.equal frame.Stacktrace.class_str (Typ.Procname.objc_cpp_get_class_name objc_cpp_prod) | Typ.Procname.C _ -> true (* Needed for test code. *) | Typ.Procname.Block _ | Typ.Procname.Linters_dummy_method -> failwith "Proc type not supported by crashcontext: block" in String.equal frame.Stacktrace.method_str (Typ.Procname.get_method caller) && matches_class caller in let all_frames = List.concat (List.map ~f:(fun trace -> trace.Stacktrace.frames) traces) in begin match List.find ~f:matches_proc all_frames with | Some frame -> let new_astate = Domain.add pn astate in if Stacktrace.frame_matches_location frame loc then begin let pdesc = proc_data.ProcData.pdesc in output_json_summary pdesc new_astate loc "call_site" get_proc_desc end; new_astate | None -> astate end | Sil.Call _ -> (* We currently ignore calls through function pointers in C and other potential special kinds of procedure calls to be added later, e.g. Java reflection. *) astate | Sil.Load _ | Store _ | Prune _ | Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ -> astate end module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions) let loaded_stacktraces = (* Load all stacktraces defined in either Config.stacktrace or Config.stacktraces_dir. *) let json_files_in_dir dir = let stacktrace_path_regexp = Str.regexp ".*\\.json" in let path_matcher path = Str.string_match stacktrace_path_regexp path 0 in DB.paths_matching dir path_matcher in let filenames = match Config.stacktrace, Config.stacktraces_dir with | None, None -> None | Some fname, None -> Some [fname] | None, Some dir -> Some (json_files_in_dir dir) | Some fname, Some dir -> Some (fname :: (json_files_in_dir dir)) in match filenames with | None -> None | Some files -> Some (List.map ~f:Stacktrace.of_json_file files) let checker { Callbacks.proc_desc; tenv; get_proc_desc; summary } : Specs.summary = begin match loaded_stacktraces with | None -> failwith "Missing command line option. Either \ '--stacktrace stack.json' or '--stacktrace-dir ./dir' \ must be used when running '-a crashcontext'. This \ options expects a JSON formated stack trace or a \ directory containing multiple such traces, \ respectively. See \ tests/codetoanalyze/java/crashcontext/*.json for \ examples of the expected format." | Some stacktraces -> begin let extras = { get_proc_desc; stacktraces; } in ignore (Analyzer.exec_pdesc (ProcData.make proc_desc tenv extras) ~initial:Domain.empty) end end; summary