Reviewed By: jeremydubreil, mbouaziz, jvillard Differential Revision: D13861427 fbshipit-source-id: 85e340bb5master
parent
e6d2872a4e
commit
374538a02f
@ -1,28 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
type line_range_t = {
|
||||
start_line : int;
|
||||
end_line : int;
|
||||
}
|
||||
|
||||
type location_t = {
|
||||
location_type : string;
|
||||
file : string;
|
||||
?line : int option;
|
||||
blame_range : line_range_t list;
|
||||
}
|
||||
|
||||
type stacktree = {
|
||||
method_name : string;
|
||||
?location : location_t option;
|
||||
callees : stacktree list;
|
||||
}
|
||||
|
||||
type crashcontext_t = {
|
||||
stack : stacktree list;
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
module F = Format
|
||||
module L = Logging
|
||||
|
||||
let frame_id_of_stackframe frame =
|
||||
let loc_str =
|
||||
match frame.Stacktrace.line_num with
|
||||
| None ->
|
||||
frame.Stacktrace.file_str
|
||||
| Some line ->
|
||||
F.sprintf "%s:%d" frame.Stacktrace.file_str line
|
||||
in
|
||||
F.sprintf "%s.%s(%s)" frame.Stacktrace.class_str frame.Stacktrace.method_str loc_str
|
||||
|
||||
|
||||
let frame_id_of_summary stacktree =
|
||||
let short_name = List.hd_exn (Str.split (Str.regexp "(") stacktree.Stacktree_j.method_name) in
|
||||
match stacktree.Stacktree_j.location with
|
||||
| None ->
|
||||
L.(die InternalError)
|
||||
"Attempted to take signature of a frame without location information. This is undefined."
|
||||
| Some {line= Some line_num; file} ->
|
||||
F.sprintf "%s(%s:%d)" short_name (Filename.basename file) line_num
|
||||
| Some {file} ->
|
||||
F.sprintf "%s(%s)" short_name (Filename.basename file)
|
||||
|
||||
|
||||
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
|
||||
; blame_range= [] }
|
||||
; 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 =
|
||||
List.map ~f:(Atdgen_runtime.Util.Json.from_file Stacktree_j.read_stacktree) summary_files
|
||||
in
|
||||
let summary_map =
|
||||
List.fold
|
||||
~f:(fun acc stacktree ->
|
||||
String.Map.set ~key:(frame_id_of_summary stacktree) ~data:stacktree acc )
|
||||
~init:String.Map.empty summaries
|
||||
in
|
||||
let expand_stack_frame frame =
|
||||
(* TODO: Implement k > 1 case *)
|
||||
let frame_id = frame_id_of_stackframe frame in
|
||||
if String.Map.existsi ~f:(fun ~key ~data:_ -> String.equal key frame_id) summary_map then
|
||||
String.Map.find_exn summary_map frame_id
|
||||
else stracktree_of_frame frame
|
||||
in
|
||||
let expanded_frames = List.map ~f:expand_stack_frame stacktrace.frames in
|
||||
let crashcontext = {Stacktree_j.stack= expanded_frames} in
|
||||
Atdgen_runtime.Util.Json.to_file Stacktree_j.write_crashcontext_t out_file crashcontext
|
||||
|
||||
|
||||
let collect_all_summaries root_summaries_dir stacktrace_file stacktraces_dir =
|
||||
let method_summaries =
|
||||
Utils.directory_fold
|
||||
(fun summaries path ->
|
||||
(* check if the file is a JSON file under the crashcontext dir *)
|
||||
if
|
||||
Sys.is_directory path <> `Yes
|
||||
&& Filename.check_suffix path "json"
|
||||
&& String.is_suffix ~suffix:"crashcontext" (Filename.dirname path)
|
||||
then path :: summaries
|
||||
else summaries )
|
||||
[] root_summaries_dir
|
||||
in
|
||||
let pair_for_stacktrace_file =
|
||||
match stacktrace_file with
|
||||
| None ->
|
||||
None
|
||||
| Some file ->
|
||||
let crashcontext_dir = Config.results_dir ^/ "crashcontext" in
|
||||
Utils.create_dir crashcontext_dir ;
|
||||
Some (file, crashcontext_dir ^/ "crashcontext.json")
|
||||
in
|
||||
let trace_file_regexp = Str.regexp "\\(.*\\)\\.json" in
|
||||
let pairs_for_stactrace_dir =
|
||||
match stacktraces_dir with
|
||||
| None ->
|
||||
[]
|
||||
| Some s -> (
|
||||
let dir = DB.filename_from_string s in
|
||||
let trace_file_matcher path =
|
||||
let path_str = DB.filename_to_string path in
|
||||
Str.string_match trace_file_regexp path_str 0
|
||||
in
|
||||
let trace_fold stacktrace_file acc =
|
||||
let stacktrace_file_str = DB.filename_to_string stacktrace_file in
|
||||
let out_file = Str.matched_group 1 stacktrace_file_str ^ ".crashcontext.json" in
|
||||
(stacktrace_file_str, out_file) :: acc
|
||||
in
|
||||
try DB.fold_paths_matching ~dir ~p:trace_file_matcher ~init:[] ~f:trace_fold
|
||||
with
|
||||
(* trace_fold runs immediately after trace_file_matcher in the
|
||||
DB.fold_paths_matching statement below, so we don't need to
|
||||
call Str.string_match again. *)
|
||||
| Caml.Not_found
|
||||
-> assert false )
|
||||
in
|
||||
let input_output_file_pairs =
|
||||
match pair_for_stacktrace_file with
|
||||
| None ->
|
||||
pairs_for_stactrace_dir
|
||||
| Some pair ->
|
||||
pair :: pairs_for_stactrace_dir
|
||||
in
|
||||
let process_stacktrace (stacktrace_file, out_file) =
|
||||
stitch_summaries stacktrace_file method_summaries out_file
|
||||
in
|
||||
List.iter ~f:process_stacktrace input_output_file_pairs
|
||||
|
||||
|
||||
let crashcontext_epilogue ~in_buck_mode =
|
||||
(* 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.
|
||||
Important: Note that when running under buck, this is not the final
|
||||
infer-out/ directory, but instead it is buck-out/, which contains the
|
||||
infer output directories for every buck target. *)
|
||||
let root_summaries_dir =
|
||||
if in_buck_mode then
|
||||
let buck_out = match Config.buck_out with Some dir -> dir | None -> "buck-out" in
|
||||
Config.project_root ^/ buck_out
|
||||
else Config.results_dir
|
||||
in
|
||||
collect_all_summaries root_summaries_dir Config.stacktrace Config.stacktraces_dir
|
||||
|
||||
|
||||
let pp_stacktree fmt st = Format.pp_print_string fmt (Stacktree_j.string_of_stacktree st)
|
@ -1,44 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
(**
|
||||
CRASHCONTEXT Intro [experimental]:
|
||||
|
||||
Crashcontext is an experimental analysis (in the future: a family of
|
||||
analyses). It takes one or more stacktraces representing crashes
|
||||
corresponding to the codebase being analyzed and expands them into
|
||||
crashcontext.json files. These files incorporate further information about
|
||||
the code that might have executed before the crash.
|
||||
|
||||
This analysis is run with '-a crashcontext' and must take either of the
|
||||
following extra arguments:
|
||||
|
||||
--stacktrace stacktrace.json (a single stacktrace, output defaults to
|
||||
crashcontext/crashcontext.json)
|
||||
--stacktraces-dir dir (will expand every json stacktace in dir, output
|
||||
crashcontext files will be saved to dir as well)
|
||||
|
||||
For further information, take a look at tests under:
|
||||
|
||||
infer/tests/codetoanalyze/java/crashcontext/ and
|
||||
infer/tests/endtoend/java/crashcontext/
|
||||
*)
|
||||
|
||||
val crashcontext_epilogue : in_buck_mode:bool -> unit
|
||||
(**
|
||||
Runs crashcontext epilogue code, which takes the per-method summaries
|
||||
produced by crashcontext related analysis (future: analyses) and stitches
|
||||
them together into a final crashcontext.json output file.
|
||||
This code should run after all checkers when running with '--crashcontext'.
|
||||
When running with buck, summaries are stitched across multiple buck targets,
|
||||
so this runs at the end of the parent buck infer process only.
|
||||
TODO: Similar integration with build systems other than buck.
|
||||
*)
|
||||
|
||||
val pp_stacktree : Format.formatter -> Stacktree_t.stacktree -> unit
|
@ -1,181 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
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 SpecPayload = SummaryPayload.Make (struct
|
||||
type t = Stacktree_j.stacktree
|
||||
|
||||
let update_payloads frame (payloads : Payloads.t) = {payloads with crashcontext_frame= Some frame}
|
||||
|
||||
let of_payloads (payloads : Payloads.t) = payloads.crashcontext_frame
|
||||
end)
|
||||
|
||||
type extras_t = {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 pdesc ~init:start_line ~f:(fun acc _ instr ->
|
||||
let new_loc = Sil.instr_get_loc instr in
|
||||
max acc new_loc.Location.line )
|
||||
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
|
||||
; 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 =
|
||||
let procs = Domain.elements astate in
|
||||
let callees =
|
||||
List.map
|
||||
~f:(fun pn ->
|
||||
match SpecPayload.read pdesc pn with
|
||||
| None -> (
|
||||
match Ondemand.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 (SpecPayload) 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 =
|
||||
let caller = Procdesc.get_proc_name pdesc in
|
||||
let stacktree = stacktree_of_astate pdesc astate loc location_type 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 ;
|
||||
Atdgen_runtime.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 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
|
||||
| Typ.Procname.WithBlockParameters _ ->
|
||||
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" ) ;
|
||||
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 _ | ExitScope _ | Abstract _ | Nullify _ ->
|
||||
astate
|
||||
|
||||
|
||||
let pp_session_name _node fmt = F.pp_print_string fmt "crashcontext"
|
||||
end
|
||||
|
||||
module Analyzer = AbstractInterpreter.MakeRPO (TransferFunctions (ProcCfg.Exceptional))
|
||||
|
||||
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; summary} : Summary.t =
|
||||
( 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 = {stacktraces} in
|
||||
ignore (Analyzer.exec_pdesc (ProcData.make proc_desc tenv extras) ~initial:Domain.empty) ) ;
|
||||
summary
|
@ -1,118 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
(** Module for parsing stack traces and using them to guide Infer analysis *)
|
||||
|
||||
open! IStd
|
||||
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
|
@ -1,24 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
(** Module for parsing stack traces and using them to guide Infer analysis *)
|
||||
|
||||
type frame = {class_str: string; method_str: string; file_str: string; line_num: int option}
|
||||
|
||||
type t = {exception_name: string; frames: frame list}
|
||||
|
||||
val make : string -> frame list -> t
|
||||
|
||||
val make_frame : string -> string -> string -> int option -> frame
|
||||
|
||||
val frame_matches_location : frame -> Location.t -> bool
|
||||
|
||||
val of_string : string -> t
|
||||
|
||||
val of_json_file : string -> t
|
@ -1,84 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
module TestInterpreter =
|
||||
AnalyzerTester.Make (BoundedCallTree.TransferFunctions (ProcCfg.Exceptional))
|
||||
|
||||
let tests =
|
||||
let open OUnit2 in
|
||||
let open AnalyzerTester.StructuredSil in
|
||||
let initial = BoundedCallTree.Domain.empty in
|
||||
let f_proc_name = Typ.Procname.from_string_c_fun "f" in
|
||||
let g_proc_name = Typ.Procname.from_string_c_fun "g" in
|
||||
let g_args = [(Exp.Const (Const.Cint IntLit.one), Typ.mk (Tint IInt))] in
|
||||
let g_return = (ident_of_str "r", Typ.mk (Tint IInt)) in
|
||||
let class_name = "com.example.SomeClass" in
|
||||
let file_name = "SomeClass.java" in
|
||||
let trace =
|
||||
Stacktrace.make "java.lang.NullPointerException"
|
||||
[ Stacktrace.make_frame class_name "foo" file_name (Some 16)
|
||||
; Stacktrace.make_frame class_name "bar" file_name (Some 20) ]
|
||||
in
|
||||
let extras = {BoundedCallTree.stacktraces= [trace]} in
|
||||
let multi_trace_1 =
|
||||
Stacktrace.make "java.lang.NullPointerException"
|
||||
[Stacktrace.make_frame class_name "foo" file_name (Some 16)]
|
||||
in
|
||||
let multi_trace_2 =
|
||||
Stacktrace.make "java.lang.NullPointerException"
|
||||
[Stacktrace.make_frame class_name "bar" file_name (Some 20)]
|
||||
in
|
||||
let multi_trace_extras = {BoundedCallTree.stacktraces= [multi_trace_1; multi_trace_2]} in
|
||||
let caller_foo_name = Typ.Procname.from_string_c_fun "foo" in
|
||||
let caller_bar_name = Typ.Procname.from_string_c_fun "bar" in
|
||||
let caller_baz_name = Typ.Procname.from_string_c_fun "baz" in
|
||||
let test_list_from_foo =
|
||||
[ ( "on_call_add_proc_name"
|
||||
, [make_call ~procname:f_proc_name []; (* means f() *) invariant "{ f }"] )
|
||||
; ( "on_call_add_proc_name_w_args"
|
||||
, [ make_call ~procname:g_proc_name ~return:g_return g_args
|
||||
; (* means r = a.g(1) *)
|
||||
invariant "{ g }" ] )
|
||||
; ( "handle_two_proc_calls"
|
||||
, [ make_call ~procname:f_proc_name []
|
||||
; invariant "{ f }"
|
||||
; make_call ~procname:g_proc_name ~return:g_return g_args
|
||||
; invariant "{ f, g }" ] )
|
||||
; ( "dont_record_procs_twice"
|
||||
, [ make_call ~procname:f_proc_name []
|
||||
; invariant "{ f }"
|
||||
; make_call ~procname:f_proc_name []
|
||||
; invariant "{ f }" ] ) ]
|
||||
|> TestInterpreter.create_tests ~test_pname:caller_foo_name
|
||||
~initial:BoundedCallTree.Domain.empty extras
|
||||
in
|
||||
let test_list_from_bar =
|
||||
[ ( "on_call_anywhere_on_stack_add_proc_name"
|
||||
, [make_call ~procname:f_proc_name []; (* means f() *) invariant "{ f }"] ) ]
|
||||
|> TestInterpreter.create_tests ~test_pname:caller_bar_name extras ~initial
|
||||
in
|
||||
let test_list_from_baz =
|
||||
[ ( "ignore_procs_unrelated_to_trace"
|
||||
, [make_call ~procname:f_proc_name []; (* means f() *) invariant "{ }"] ) ]
|
||||
|> TestInterpreter.create_tests ~test_pname:caller_baz_name extras ~initial
|
||||
in
|
||||
let test_list_multiple_traces_from_foo =
|
||||
[ ( "on_call_add_proc_name_in_any_stack_1"
|
||||
, [make_call ~procname:f_proc_name []; (* means f() *) invariant "{ f }"] ) ]
|
||||
|> TestInterpreter.create_tests ~test_pname:caller_foo_name multi_trace_extras ~initial
|
||||
in
|
||||
let test_list_multiple_traces_from_bar =
|
||||
[ ( "on_call_add_proc_name_in_any_stack_2"
|
||||
, [make_call ~procname:f_proc_name []; (* means f() *) invariant "{ f }"] ) ]
|
||||
|> TestInterpreter.create_tests ~test_pname:caller_bar_name multi_trace_extras ~initial
|
||||
in
|
||||
let test_list =
|
||||
test_list_from_foo @ test_list_from_bar @ test_list_from_baz
|
||||
@ test_list_multiple_traces_from_foo @ test_list_multiple_traces_from_bar
|
||||
in
|
||||
"bounded_calltree_test_suite" >::: test_list
|
@ -1,81 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
let tests =
|
||||
let open OUnit2 in
|
||||
let empty_string_test =
|
||||
let empty_string_test_ _ =
|
||||
assert_raises (Logging.InferUserError "Empty stack trace") (fun () -> Stacktrace.of_string "")
|
||||
in
|
||||
"empty_string" >:: empty_string_test_
|
||||
in
|
||||
let empty_trace_test =
|
||||
let empty_stack_trace_s = "Exception in thread \"main\" java.lang.NullPointerException" in
|
||||
let trace = Stacktrace.of_string empty_stack_trace_s in
|
||||
let empty_trace_test_ _ = assert_equal trace.frames [] in
|
||||
"empty_trace" >:: empty_trace_test_
|
||||
in
|
||||
let one_frame_trace_test =
|
||||
let one_frame_trace_test_s =
|
||||
"Exception in thread \"main\" java.lang.NullPointerException\n"
|
||||
^ "\tat endtoend.java.checkers.crashcontext.MinimalCrashTest.main"
|
||||
^ "(MinimalCrashTest.java:16)"
|
||||
in
|
||||
let trace = Stacktrace.of_string one_frame_trace_test_s in
|
||||
let expected =
|
||||
Stacktrace.make "java.lang.NullPointerException"
|
||||
[ Stacktrace.make_frame "endtoend.java.checkers.crashcontext.MinimalCrashTest" "main"
|
||||
"MinimalCrashTest.java" (Some 16) ]
|
||||
in
|
||||
let one_frame_trace_test_ _ = assert_equal trace expected in
|
||||
"one_frame_trace" >:: one_frame_trace_test_
|
||||
in
|
||||
let multi_frame_trace_test =
|
||||
let multi_frame_trace_test_s =
|
||||
"Exception in thread \"main\" java.lang.NullPointerException\n\t"
|
||||
^ "at endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.bar"
|
||||
^ "(MultiStackFrameCrashTest.java:16)\n"
|
||||
^ "\tat endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.foo"
|
||||
^ "(MultiStackFrameCrashTest.java:20)\n"
|
||||
^ "\tat endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.main"
|
||||
^ "(MultiStackFrameCrashTest.java:24)"
|
||||
in
|
||||
let trace = Stacktrace.of_string multi_frame_trace_test_s in
|
||||
let class_name = "endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest" in
|
||||
let file_name = "MultiStackFrameCrashTest.java" in
|
||||
let expected =
|
||||
Stacktrace.make "java.lang.NullPointerException"
|
||||
[ Stacktrace.make_frame class_name "bar" file_name (Some 16)
|
||||
; Stacktrace.make_frame class_name "foo" file_name (Some 20)
|
||||
; Stacktrace.make_frame class_name "main" file_name (Some 24) ]
|
||||
in
|
||||
let multi_frame_trace_test_ _ = assert_equal trace expected in
|
||||
"multi_frame_trace_test" >:: multi_frame_trace_test_
|
||||
in
|
||||
let missing_line_info_test =
|
||||
let missing_line_info_test_s =
|
||||
"Exception in thread \"main\" java.lang.NullPointerException\n"
|
||||
^ "\tat endtoend.java.checkers.crashcontext.MinimalCrashTest.main"
|
||||
^ "(MinimalCrashTest.java)"
|
||||
in
|
||||
let trace = Stacktrace.of_string missing_line_info_test_s in
|
||||
let expected =
|
||||
Stacktrace.make "java.lang.NullPointerException"
|
||||
[ Stacktrace.make_frame "endtoend.java.checkers.crashcontext.MinimalCrashTest" "main"
|
||||
"MinimalCrashTest.java" None ]
|
||||
in
|
||||
let missing_line_info_test_ _ = assert_equal trace expected in
|
||||
"missing_line_info_test" >:: missing_line_info_test_
|
||||
in
|
||||
"all_tests_suite"
|
||||
>::: [ empty_string_test
|
||||
; empty_trace_test
|
||||
; one_frame_trace_test
|
||||
; multi_frame_trace_test
|
||||
; missing_line_info_test ]
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package codetoanalyze.java.crashcontext;
|
||||
|
||||
public class BranchingCallsExample {
|
||||
|
||||
public static void pre_bar() {
|
||||
System.out.println("This runs before the crash.");
|
||||
}
|
||||
|
||||
public static void post_bar() {
|
||||
System.out.println("This doesn't.");
|
||||
}
|
||||
|
||||
public static void bar() {
|
||||
String s = null;
|
||||
s.toString();
|
||||
}
|
||||
|
||||
public static void foo() {
|
||||
pre_bar();
|
||||
bar();
|
||||
post_bar();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
foo();
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"exception_type": "java.lang.NullPointerException", "stack_trace": ["at codetoanalyze.java.crashcontext.BranchingCallsExample.bar(BranchingCallsExample.java:24)","at codetoanalyze.java.crashcontext.BranchingCallsExample.foo(BranchingCallsExample.java:29)","at codetoanalyze.java.crashcontext.BranchingCallsExample.main(BranchingCallsExample.java:34)",""], "exception_message": "", "normvector_stack": ["codetoanalyze.java.crashcontext.BranchingCallsExample.bar","codetoanalyze.java.crashcontext.BranchingCallsExample.foo","endtoend.java.crashcontext.BranchingCallsExample.main"]}
|
@ -1,52 +0,0 @@
|
||||
# Copyright (c) 2016-present, Facebook, Inc.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
TESTS_DIR = ../../..
|
||||
include $(TESTS_DIR)/java.make
|
||||
include $(TESTS_DIR)/base.make
|
||||
|
||||
SOURCES = $(wildcard *.java)
|
||||
OBJECTS = $(patsubst %.java,%.class,$(SOURCES))
|
||||
EXP_TESTs = $(sort $(patsubst %.java,issues-%.exp.test,$(SOURCES)))
|
||||
INFER_OUTs = $(patsubst %.java,infer-out-%,$(SOURCES))
|
||||
|
||||
$(OBJECTS): $(SOURCES)
|
||||
$(JAVAC) -cp $(CLASSPATH) $(SOURCES)
|
||||
|
||||
# analyze a single source file and generate the test results for it
|
||||
issues-%.exp.test: $(JAVA_DEPS) %.stacktrace.json %.java
|
||||
$(QUIET)$(call silent_on_success,Testing crashcontext: $*,\
|
||||
$(INFER_BIN) --crashcontext-only -o infer-out-$* --stacktrace $*.stacktrace.json \
|
||||
-- $(JAVAC) -cp $(CLASSPATH) $*.java)
|
||||
# add a newline at the end of the json when creating the exp.test
|
||||
$(QUIET)$(COPY) infer-out-$*/crashcontext/crashcontext.json $@ && echo >> $@
|
||||
|
||||
# combine the test results for all the source files
|
||||
issues.exp.test: $(EXP_TESTs)
|
||||
$(QUIET)cat $^ > $@
|
||||
|
||||
default: compile
|
||||
|
||||
.PHONY: compile
|
||||
compile: $(OBJECTS)
|
||||
|
||||
.PHONY: analyze
|
||||
analyze: $(EXP_TESTs)
|
||||
|
||||
.PHONY: print
|
||||
print: issues.exp.test
|
||||
|
||||
.PHONY: test
|
||||
test: issues.exp.test
|
||||
$(QUIET)cd $(TESTS_DIR) && \
|
||||
$(call check_no_diff,$(TEST_REL_DIR)/issues.exp,$(TEST_REL_DIR)/issues.exp.test)
|
||||
|
||||
.PHONY: replace
|
||||
replace: issues.exp.test
|
||||
cp $< issues.exp
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(REMOVE_DIR) $(INFER_OUTs) $(OBJECTS) $(EXP_TESTs)
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package codetoanalyze.java.crashcontext;
|
||||
|
||||
public class MethodNameClashExample {
|
||||
|
||||
public static class A {
|
||||
|
||||
public static void foo() {
|
||||
String s = null;
|
||||
s.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class B {
|
||||
|
||||
public static void foo() {
|
||||
A.foo();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
B.foo();
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"exception_type": "java.lang.NullPointerException", "stack_trace": ["at codetoanalyze.java.crashcontext.MethodNameClashExample$A.foo(MethodNameClashExample.java:18)","at codetoanalyze.java.crashcontext.MethodNameClashExample$B.foo(MethodNameClashExample.java:26)","at codetoanalyze.java.crashcontext.MethodNameClashExample.main(MethodNameClashExample.java:32)",""], "exception_message": "", "normvector_stack": ["codetoanalyze.java.crashcontext.MethodNameClashExample$A.foo","codetoanalyze.java.crashcontext.MethodNameClashExample$B.foo","codetoanalyze.java.crashcontext.MethodNameClashExample.main"]}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package codetoanalyze.java.crashcontext;
|
||||
|
||||
public class MinimalCrashExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
String s = null;
|
||||
s.toString();
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"exception_type": "java.lang.NullPointerException", "stack_trace": ["at codetoanalyze.java.crashcontext.MinimalCrashExample.main(MinimalCrashExample.java:16)",""], "exception_message": "", "normvector_stack": ["codetoanalyze.java.crashcontext.MinimalCrashExample.main"]}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package codetoanalyze.java.crashcontext;
|
||||
|
||||
public class MultiStackFrameCrashExample {
|
||||
|
||||
public static void bar() {
|
||||
String s = null;
|
||||
s.toString();
|
||||
}
|
||||
|
||||
public static void foo() {
|
||||
bar();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
foo();
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"exception_type": "java.lang.NullPointerException", "stack_trace": ["at codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.bar(MultiStackFrameCrashExample.java:16)","at codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.foo(MultiStackFrameCrashExample.java:20)","at codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.main(MultiStackFrameCrashExample.java:24)",""], "exception_message": "", "normvector_stack": ["codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.bar","codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.foo","endtoend.java.crashcontext.MultiStackFrameCrashExample.main"]}
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package codetoanalyze.java.crashcontext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class NativeMethodExample {
|
||||
|
||||
public static void foo() {
|
||||
String s = null;
|
||||
s.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// Calling method.invoke is a reliable way of getting a native method
|
||||
// in the stack (from the implementation of reflection) between this
|
||||
// method and the target of the reflective invocation.
|
||||
Method method = NativeMethodExample.class.getDeclaredMethod("foo");
|
||||
Object o = method.invoke(new Object[] {});
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"exception_type": "java.lang.NullPointerException", "stack_trace": ["at codetoanalyze.java.crashcontext.NativeMethodExample.foo(NativeMethodExample.java:18)","at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)","at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)","at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)","at java.lang.reflect.Method.invoke(Method.java:497)","at codetoanalyze.java.crashcontext.NativeMethodExample.main(NativeMethodExample.java:27)",""], "exception_message": "", "normvector_stack": ["codetoanalyze.java.crashcontext.NativeMethodExample.foo","sun.reflect.NativeMethodAccessorImpl.invoke0","sun.reflect.NativeMethodAccessorImpl.invoke","at sun.reflect.DelegatingMethodAccessorImpl.invoke","java.lang.reflect.Method.invoke","codetoanalyze.java.crashcontext.NativeMethodExample.main",""]}
|
@ -1,5 +0,0 @@
|
||||
{"stack":[{"method_name":"codetoanalyze.java.crashcontext.BranchingCallsExample.bar","location":{"location_type":"call_site","file":"BranchingCallsExample.java","line":24,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.BranchingCallsExample.foo","location":{"location_type":"call_site","file":"BranchingCallsExample.java","line":29,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.BranchingCallsExample.main","location":{"location_type":"call_site","file":"BranchingCallsExample.java","line":34,"blame_range":[]},"callees":[]}]}
|
||||
{"stack":[{"method_name":"codetoanalyze.java.crashcontext.MethodNameClashExample$A.foo","location":{"location_type":"call_site","file":"MethodNameClashExample.java","line":18,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.MethodNameClashExample$B.foo","location":{"location_type":"call_site","file":"MethodNameClashExample.java","line":26,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.MethodNameClashExample.main","location":{"location_type":"call_site","file":"MethodNameClashExample.java","line":32,"blame_range":[]},"callees":[]}]}
|
||||
{"stack":[{"method_name":"codetoanalyze.java.crashcontext.MinimalCrashExample.main","location":{"location_type":"call_site","file":"MinimalCrashExample.java","line":16,"blame_range":[]},"callees":[]}]}
|
||||
{"stack":[{"method_name":"codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.bar","location":{"location_type":"call_site","file":"MultiStackFrameCrashExample.java","line":16,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.foo","location":{"location_type":"call_site","file":"MultiStackFrameCrashExample.java","line":20,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.MultiStackFrameCrashExample.main","location":{"location_type":"call_site","file":"MultiStackFrameCrashExample.java","line":24,"blame_range":[]},"callees":[]}]}
|
||||
{"stack":[{"method_name":"codetoanalyze.java.crashcontext.NativeMethodExample.foo","location":{"location_type":"call_site","file":"NativeMethodExample.java","line":18,"blame_range":[]},"callees":[]},{"method_name":"sun.reflect.NativeMethodAccessorImpl.invoke0","location":{"location_type":"call_site","file":"Native Method","blame_range":[]},"callees":[]},{"method_name":"sun.reflect.NativeMethodAccessorImpl.invoke","location":{"location_type":"call_site","file":"NativeMethodAccessorImpl.java","line":62,"blame_range":[]},"callees":[]},{"method_name":"sun.reflect.DelegatingMethodAccessorImpl.invoke","location":{"location_type":"call_site","file":"DelegatingMethodAccessorImpl.java","line":43,"blame_range":[]},"callees":[]},{"method_name":"java.lang.reflect.Method.invoke","location":{"location_type":"call_site","file":"Method.java","line":497,"blame_range":[]},"callees":[]},{"method_name":"codetoanalyze.java.crashcontext.NativeMethodExample.main(java.lang.String[]):void","location":{"location_type":"call_site","file":"NativeMethodExample.java","line":27,"blame_range":[{"start_line":19,"end_line":27}]},"callees":[{"method_name":"java.lang.Error.<init>(java.lang.Throwable)","callees":[]},{"method_name":"java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]):java.lang.reflect.Method","callees":[]},{"method_name":"java.lang.reflect.Method.invoke(java.lang.Object,java.lang.Object[]):java.lang.Object","callees":[]},{"method_name":"__instanceof","callees":[]},{"method_name":"__new","callees":[]},{"method_name":"__new_array","callees":[]},{"method_name":"__unwrap_exception","callees":[]}]}]}
|
Loading…
Reference in new issue