(* * 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} 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= 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} 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 -> L.(die InternalError) "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 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 let pdesc = proc_data.ProcData.pdesc in output_json_summary pdesc new_astate loc "call_site" get_proc_desc ) ; new_astate | None -> astate ) | 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 = ( match loaded_stacktraces with | None -> L.(die UserError) "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 -> let extras = {get_proc_desc; stacktraces} in ignore (Analyzer.exec_pdesc (ProcData.make proc_desc tenv extras) ~initial:Domain.empty) ) ; summary