Crashcontext ocaml method summary merging

Reviewed By: sblackshear

Differential Revision: D3643101

fbshipit-source-id: eaea332
master
Lázaro Clapp Jiménez Labora 8 years ago committed by Facebook Github Bot 6
parent d5848d8c94
commit 0a8b95a856

@ -30,10 +30,6 @@ csv.field_size_limit(sys.maxsize)
INFER_ANALYZE_BINARY = 'InferAnalyze'
CRASHCONTEXT_METHOD_FIELD = 'method'
CRASHCONTEXT_LOCATION_FIELD = 'location'
CRASHCONTEXT_CALLEES_FIELD = 'callees'
def get_infer_version():
try:
@ -492,54 +488,6 @@ class AnalyzerWrapper(object):
return exit_status
def crashcontext_stitch_summaries(self):
"""Take crashcontext per-method summaries and join them together to
produce the final crashcontext.json output file."""
crashcontext_dir = os.path.join(self.args.infer_out, 'crashcontext')
summaries_map_by_frame_id = {}
st_json = utils.load_json_from_path(self.args.stacktrace)
stacktrace = st_json['stack_trace']
k = 1 # Where k is the number of levels of inlined calls.
for f in os.listdir(crashcontext_dir):
if f.endswith('.json'):
path = os.path.join(crashcontext_dir, f)
method_summary = utils.load_json_from_path(path)
method_signature = method_summary[CRASHCONTEXT_METHOD_FIELD]
method_name = method_signature.split('(')[0]
src_path = method_summary[CRASHCONTEXT_LOCATION_FIELD]['file']
line = method_summary[CRASHCONTEXT_LOCATION_FIELD]['line']
frame_id = "{0}({1}:{2})".format(
method_name,
os.path.basename(src_path),
line)
summaries_map_by_frame_id[frame_id] = method_summary
def expand(summary, k):
if k == 0:
# Make sure leaf nodes have an empty 'callees' field.
leaf_nodes = summary[CRASHCONTEXT_CALLEES_FIELD]
for leaf_node in leaf_nodes:
if CRASHCONTEXT_CALLEES_FIELD not in leaf_node:
leaf_node[CRASHCONTEXT_CALLEES_FIELD] = []
return summary
else:
NotImplementedError() # TODO
json_frames = []
for frame in stacktrace:
frame_id = frame.strip()
if not frame_id:
continue
assert frame_id.startswith('at ')
frame_id = frame_id[3:]
assert frame_id in summaries_map_by_frame_id
summary = summaries_map_by_frame_id[frame_id]
json_frames.append(expand(summary, k - 1))
out_json = {}
out_json['stack'] = json_frames
out_file = os.path.join(crashcontext_dir, 'crashcontext.json')
utils.dump_json_to_path(out_json, out_file)
def read_proc_stats(self):
proc_stats_path = os.path.join(
@ -573,8 +521,6 @@ class AnalyzerWrapper(object):
if self.args.analyzer not in [config.ANALYZER_COMPILE,
config.ANALYZER_CAPTURE]:
if self.analyze() == os.EX_OK:
if self.args.analyzer == config.ANALYZER_CRASHCONTEXT:
self.crashcontext_stitch_summaries()
reporting_start_time = time.time()
report_status = self.create_report()
elapsed = utils.elapsed_time(reporting_start_time)

@ -0,0 +1,74 @@
(*
* 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! Utils
module F = Format
module L = Logging
let frame_id_of_stackframe frame =
F.sprintf
"%s.%s(%s:%d)"
frame.Stacktrace.class_str
frame.Stacktrace.method_str
frame.Stacktrace.file_str
frame.Stacktrace.line_num
let frame_id_of_summary stacktree =
let short_name = IList.hd
(Str.split (Str.regexp "(") stacktree.Stacktree_j.method_name) in
match stacktree.Stacktree_j.location with
| None ->
failwith "Attempted to take signature of a frame without location \
information. This is undefined."
| Some loc ->
F.sprintf "%s(%s:%d)" short_name (Filename.basename loc.file) loc.line
let stracktree_of_frame frame =
{ Stacktree_j.method_name = F.sprintf
"%s.%s"
frame.Stacktrace.class_str
frame.Stacktrace.method_str;
location = Some { Stacktree_j.location_type = "call_site";
file = frame.Stacktrace.file_str;
line = frame.Stacktrace.line_num };
callees = [];
}
(** k = 1 implementation, where k is the number of levels of calls inlined *)
let stitch_summaries stacktrace_file summary_files out_file =
let stacktrace = Stacktrace.of_json_file stacktrace_file in
let summaries = IList.map
(Ag_util.Json.from_file Stacktree_j.read_stacktree)
summary_files in
let summary_map = IList.fold_left
(fun acc stacktree ->
StringMap.add (frame_id_of_summary stacktree) stacktree acc)
StringMap.empty
summaries in
let expand_stack_frame frame =
(** TODO: Implement k > 1 case *)
let frame_id = frame_id_of_stackframe frame in
if StringMap.exists (fun key _ -> key = frame_id) summary_map then
StringMap.find frame_id summary_map
else
stracktree_of_frame frame in
let expanded_frames = IList.map expand_stack_frame stacktrace.frames in
let crashcontext = { Stacktree_j.stack = expanded_frames} in
Ag_util.Json.to_file Stacktree_j.write_crashcontext_t out_file crashcontext
let collect_all_summaries root_out_dir stacktrace_file =
let out_dir = Filename.concat root_out_dir "crashcontext" in
DB.create_dir out_dir;
let out_file = Filename.concat out_dir "crashcontext.json" in
let path_regexp = Str.regexp ".*crashcontext/.*\\..*\\.json" in
let path_matcher path = Str.string_match path_regexp path 0 in
let method_summaries =
DB.paths_matching root_out_dir path_matcher in
stitch_summaries stacktrace_file method_summaries out_file;

@ -94,6 +94,37 @@ let () =
) in
let pid = Unix.create_process args_py.(0) args_py Unix.stdin Unix.stdout Unix.stderr in
let _, status = Unix.waitpid [] pid in
(** Collect crashcontext summaries *)
let analysis_is_crashcontext = match Config.analyzer with
| Some Crashcontext -> true
| _ -> false in
if analysis_is_crashcontext then
(** Check whether this is the top-level infer process *)
let top_level_infer =
(** if the '--buck' option was passed, then this is the top level process
iff the build command starts with 'buck' *)
if Config.buck then buck
(** otherwise, we assume javac as the build command and thus only
one process *)
else true in
if top_level_infer then
(** If we are the top-level process, then find the output directory and
collect all crashcontext summaries under it in a single
crashcontext.json file *)
let root_out_dir = if buck then begin
let project_root = match Config.project_root with
| Some root -> root
| None -> Filename.dirname Config.results_dir in
let buck_out = match Config.buck_out with
| Some dir -> dir
| None -> "buck-out" in
Filename.concat project_root buck_out
end
else Config.results_dir in
match Config.stacktrace with
| None -> failwith "Detected -a crashcontext without --stacktrace, \
this should have been checked earlier."
| Some s -> Crashcontext.collect_all_summaries root_out_dir s;
if status <> Unix.WEXITED 0 then (
prerr_endline ("Failed to execute: " ^ (String.concat " " (Array.to_list args_py))) ;
exit 1

@ -27,25 +27,22 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
module Domain = Domain
type extras = Stacktrace.t
let json_of_summary caller astate loc loc_type =
let stacktree_of_summary caller astate loc location_type =
let procs = Domain.elements astate in
let json = `Assoc [
("method", `String (Procname.to_unique_id caller));
("location", `Assoc [
("type", `String loc_type);
("file", `String (DB.source_file_to_string loc.Location.file));
("line", `Int loc.Location.line);
]);
("callees", `List (IList.map
(fun pn -> `Assoc [
("method", `String (Procname.to_unique_id pn))
])
procs))
] in
json
let method_name = Procname.to_unique_id caller in
let file = DB.source_file_to_string loc.Location.file in
let line = loc.Location.line in
let location = Some { Stacktree_j.location_type ; file ; line } in
let callees = IList.map
(fun pn ->
{ Stacktree_j.method_name = Procname.to_unique_id pn;
location = None;
callees = [] } )
procs in
{ Stacktree_j.method_name; location; callees }
let output_summary caller astate loc loc_type =
let json = json_of_summary caller astate loc loc_type in
let stacktree = stacktree_of_summary caller astate loc loc_type in
let dir = Filename.concat Config.results_dir "crashcontext" in
let suffix = F.sprintf "%s_%d" loc_type loc.Location.line in
let fname = F.sprintf "%s.%s.json"
@ -53,14 +50,26 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
suffix in
let fpath = Filename.concat dir fname in
DB.create_dir dir;
Utils.write_json_to_file fpath json
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, _) ->
(** TODO: Match class. *)
let caller = Cfg.Procdesc.get_proc_name proc_data.ProcData.pdesc in
let matches_proc frame =
frame.Stacktrace.method_str = (Procname.get_method caller) in
let matches_class pname = match pname with
| Procname.Java java_proc ->
string_equal
frame.Stacktrace.class_str
(Procname.java_get_class_name java_proc)
| Procname.ObjC_Cpp objc_cpp_prod ->
string_equal
frame.Stacktrace.class_str
(Procname.objc_cpp_get_class_name objc_cpp_prod)
| Procname.C _ -> true (** Needed for test code. *)
| Procname.Block _ ->
failwith "Proc type not supported by crashcontext: block" in
frame.Stacktrace.method_str = (Procname.get_method caller) &&
matches_class caller in
let proc_in_trace = IList.exists
matches_proc
proc_data.ProcData.extras.Stacktrace.frames in

@ -37,12 +37,12 @@ public class CrashContextResults {
public boolean hasStackFrame(String methodSignature, int pos) {
return methodSignature.equals(
json.path("stack").get(pos).path("method").asText());
json.path("stack").get(pos).path("method_name").asText());
}
public boolean hasStackFrame(String methodSignature) {
for (JsonNode frame : json.path("stack")) {
if(methodSignature.equals(frame.path("method").asText())) {
if (methodSignature.equals(frame.path("method_name").asText())) {
return true;
}
}
@ -52,10 +52,10 @@ public class CrashContextResults {
private List<JsonNode> findNodesForMethod(JsonNode node,
String methodSignature,
List<JsonNode> accumulator) {
if(methodSignature.equals(node.path("method").asText())) {
if (methodSignature.equals(node.path("method_name").asText())) {
accumulator.add(node);
}
for(JsonNode callee : node.path("callees")) {
for (JsonNode callee : node.path("callees")) {
findNodesForMethod(callee, methodSignature, accumulator);
}
return accumulator;
@ -78,8 +78,8 @@ public class CrashContextResults {
}
public boolean hasPath(String methodFrom, String methodTo) {
for(JsonNode from : findNodesForMethod(methodFrom)) {
if(!findNodesForMethod(from, methodTo, new ArrayList()).isEmpty()) {
for (JsonNode from : findNodesForMethod(methodFrom)) {
if (!findNodesForMethod(from, methodTo, new ArrayList()).isEmpty()) {
return true;
}
}

Loading…
Cancel
Save