Infer# integration (#1361)
Summary: Dear Infer team, To contribute to Infer community, I would like to integrate infer#'s language agnostic layer into Infer. Please help to review, discuss and consider to merge this feature. Thanks, Xiaoyu Pull Request resolved: https://github.com/facebook/infer/pull/1361 Reviewed By: skcho Differential Revision: D25928458 Pulled By: jvillard fbshipit-source-id: 7726150b8master
parent
e0f0022fa1
commit
285ddb4a98
@ -0,0 +1,62 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(** invariant: if [namespace = Some str] then [not (String.equal str "")]. [classname] appears first
|
||||||
|
so that the comparator fails earlier *)
|
||||||
|
type t = {classname: string; namespace: string option} [@@deriving compare, equal, yojson_of]
|
||||||
|
|
||||||
|
let make ~namespace ~classname =
|
||||||
|
match namespace with Some "" -> {namespace= None; classname} | _ -> {namespace; classname}
|
||||||
|
|
||||||
|
|
||||||
|
let from_string str =
|
||||||
|
match String.rsplit2 str ~on:'.' with
|
||||||
|
| None ->
|
||||||
|
{classname= str; namespace= None}
|
||||||
|
| Some ("", _) ->
|
||||||
|
L.die InternalError "Empty namespace path in CSharp qualified classname.@."
|
||||||
|
| Some (pkg, classname) ->
|
||||||
|
{classname; namespace= Some pkg}
|
||||||
|
|
||||||
|
|
||||||
|
let to_string = function
|
||||||
|
| {classname; namespace= None} ->
|
||||||
|
classname
|
||||||
|
| {classname; namespace= Some pkg} ->
|
||||||
|
String.concat ~sep:"." [pkg; classname]
|
||||||
|
|
||||||
|
|
||||||
|
let pp fmt = function
|
||||||
|
| {classname; namespace= None} ->
|
||||||
|
F.pp_print_string fmt classname
|
||||||
|
| {classname; namespace= Some pkg} ->
|
||||||
|
F.fprintf fmt "%s.%s" pkg classname
|
||||||
|
|
||||||
|
|
||||||
|
let classname {classname} = classname
|
||||||
|
|
||||||
|
let pp_with_verbosity ~verbose fmt t =
|
||||||
|
if verbose then pp fmt t else F.pp_print_string fmt (classname t)
|
||||||
|
|
||||||
|
|
||||||
|
module Normalizer = HashNormalizer.Make (struct
|
||||||
|
type nonrec t = t [@@deriving equal]
|
||||||
|
|
||||||
|
let hash = Hashtbl.hash
|
||||||
|
|
||||||
|
let normalize t =
|
||||||
|
let classname = HashNormalizer.StringNormalizer.normalize t.classname in
|
||||||
|
let namespace =
|
||||||
|
IOption.map_changed t.namespace ~equal:phys_equal ~f:HashNormalizer.StringNormalizer.normalize
|
||||||
|
in
|
||||||
|
if phys_equal classname t.classname && phys_equal namespace t.namespace then t
|
||||||
|
else {classname; namespace}
|
||||||
|
end)
|
@ -0,0 +1,27 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
|
||||||
|
type t [@@deriving compare, equal, yojson_of]
|
||||||
|
|
||||||
|
val make : namespace:string option -> classname:string -> t
|
||||||
|
|
||||||
|
val from_string : string -> t
|
||||||
|
|
||||||
|
val to_string : t -> string
|
||||||
|
(** [to_string (from_string "X.Y.Z") = "X.Y.Z"] *)
|
||||||
|
|
||||||
|
val pp : Format.formatter -> t -> unit
|
||||||
|
(** [pp] includes namespace if any *)
|
||||||
|
|
||||||
|
val pp_with_verbosity : verbose:bool -> Format.formatter -> t -> unit
|
||||||
|
(** if [verbose] then print namespace if present, otherwise only print class *)
|
||||||
|
|
||||||
|
val classname : t -> string
|
||||||
|
|
||||||
|
module Normalizer : HashNormalizer.S with type t = t
|
@ -0,0 +1,693 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** Main module for the analyzejson analysis after the capture phase *)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
open Yojson
|
||||||
|
open Yojson.Safe.Util
|
||||||
|
|
||||||
|
(* We use Yojson.Safe to parse json so it handles long integers, which cannot be handled by OCaml's basic integers *)
|
||||||
|
module L = Logging
|
||||||
|
module Hashtbl = Caml.Hashtbl
|
||||||
|
|
||||||
|
module IntHash = struct
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let equal i j = Int.equal i j
|
||||||
|
|
||||||
|
let hash i = i land Int.max_value
|
||||||
|
end
|
||||||
|
|
||||||
|
module IntTbl = Hashtbl.Make (IntHash)
|
||||||
|
|
||||||
|
exception JsonParse_Error of string
|
||||||
|
|
||||||
|
let typename_of_classname cn = Typ.Name.CSharp.from_string cn
|
||||||
|
|
||||||
|
let parse_list (eleparse : Safe.t -> 'a) (json : Safe.t) = List.map ~f:eleparse (to_list json)
|
||||||
|
|
||||||
|
let parse_parameter (str : string) : Annot.parameter = Annot.{name= Some str; value= Annot.Str str}
|
||||||
|
|
||||||
|
let parse_list_parameters (eleparse : Safe.t -> 'a) (json : Safe.t) =
|
||||||
|
List.map ~f:eleparse (to_list json) |> List.map ~f:parse_parameter
|
||||||
|
|
||||||
|
|
||||||
|
let parse_cil_type_name (str : string) : Typ.t =
|
||||||
|
let r = Str.regexp "\\." in
|
||||||
|
try
|
||||||
|
let n = Str.search_backward r str (String.length str) in
|
||||||
|
let _namespace = Str.string_before str n in
|
||||||
|
let _name = Str.string_after str (n + 1) in
|
||||||
|
Typ.(
|
||||||
|
mk_ptr
|
||||||
|
(mk_struct
|
||||||
|
(CSharpClass (CSharpClassName.make ~namespace:(Some _namespace) ~classname:_name))))
|
||||||
|
with _ ->
|
||||||
|
Typ.(mk_ptr (mk_struct (CSharpClass (CSharpClassName.make ~namespace:None ~classname:str))))
|
||||||
|
|
||||||
|
|
||||||
|
let parse_cil_procname (json : Safe.t) : Procname.t =
|
||||||
|
let method_name = to_string (member "method_name" json) in
|
||||||
|
match method_name with
|
||||||
|
| "__new" ->
|
||||||
|
BuiltinDecl.__new
|
||||||
|
| _ ->
|
||||||
|
let return_type =
|
||||||
|
if String.equal Procname.CSharp.constructor_method_name method_name then None
|
||||||
|
else Some (to_string (member "return_type" json) |> parse_cil_type_name)
|
||||||
|
in
|
||||||
|
let class_name = to_string (member "class_name" json) |> Typ.Name.CSharp.from_string in
|
||||||
|
let param_types = parse_list to_string (member "parameters" json) in
|
||||||
|
let params = List.map ~f:parse_cil_type_name param_types in
|
||||||
|
let is_static = to_bool (member "is_static" json) in
|
||||||
|
let method_kind = if is_static then Procname.CSharp.Static else Procname.CSharp.Non_Static in
|
||||||
|
let proc_name_cs =
|
||||||
|
Procname.(
|
||||||
|
make_csharp ~class_name ~return_type ~method_name ~parameters:params ~kind:method_kind)
|
||||||
|
in
|
||||||
|
proc_name_cs ()
|
||||||
|
|
||||||
|
|
||||||
|
let parse_ikind (json : Safe.t) =
|
||||||
|
let ikind_map =
|
||||||
|
[ ("IChar", Typ.IChar)
|
||||||
|
; ("ISChar", Typ.ISChar)
|
||||||
|
; ("IUChar", Typ.IUChar)
|
||||||
|
; ("IBool", Typ.IBool)
|
||||||
|
; ("IInt", Typ.IInt)
|
||||||
|
; ("IUInt", Typ.IUInt)
|
||||||
|
; ("IShort", Typ.IShort)
|
||||||
|
; ("IUShort", Typ.IUShort)
|
||||||
|
; ("ILong", Typ.ILong)
|
||||||
|
; ("IULong", Typ.IULong)
|
||||||
|
; ("ILongLong", Typ.ILongLong)
|
||||||
|
; ("IULongLong", Typ.IULongLong)
|
||||||
|
; ("I128", Typ.I128)
|
||||||
|
; ("IU128", Typ.IU128) ]
|
||||||
|
in
|
||||||
|
List.Assoc.find_exn ~equal:String.equal ikind_map (to_string json)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_fkind (json : Safe.t) =
|
||||||
|
let fkind_map =
|
||||||
|
[("FFloat", Typ.FFloat); ("FDouble", Typ.FDouble); ("FLongDouble", Typ.FLongDouble)]
|
||||||
|
in
|
||||||
|
List.Assoc.find_exn ~equal:String.equal fkind_map (to_string json)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_ptr_kind (json : Safe.t) =
|
||||||
|
let ptr_kind_map =
|
||||||
|
[ ("Pk_pointer", Typ.Pk_pointer)
|
||||||
|
; ("Pk_reference", Typ.Pk_reference)
|
||||||
|
; ("Pk_objc_weak", Typ.Pk_objc_weak)
|
||||||
|
; ("Pk_objc_unsafe_unretained", Typ.Pk_objc_unsafe_unretained)
|
||||||
|
; ("Pk_objc_autoreleasing", Typ.Pk_objc_autoreleasing) ]
|
||||||
|
in
|
||||||
|
List.Assoc.find_exn ~equal:String.equal ptr_kind_map (to_string json)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_if_kind (json : Safe.t) =
|
||||||
|
let ifkind_map =
|
||||||
|
[ ("Ik_bexp", Sil.Ik_bexp)
|
||||||
|
; ("Ik_dowhile", Sil.Ik_dowhile)
|
||||||
|
; ("Ik_for", Sil.Ik_for)
|
||||||
|
; ("Ik_if", Sil.Ik_if)
|
||||||
|
; ("Ik_land_lor", Sil.Ik_land_lor)
|
||||||
|
; ("Ik_while", Sil.Ik_while)
|
||||||
|
; ("Ik_switch", Sil.Ik_switch) ]
|
||||||
|
in
|
||||||
|
List.Assoc.find_exn ~equal:String.equal ifkind_map (to_string json)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_csu (json : Safe.t) =
|
||||||
|
let csu = to_string (member "csu_kind" json) in
|
||||||
|
let name = to_string (member "name" json) in
|
||||||
|
match csu with
|
||||||
|
| "Class" ->
|
||||||
|
typename_of_classname name
|
||||||
|
| _ ->
|
||||||
|
raise (JsonParse_Error "JSON Parse Error: Can only parse Class types so far.")
|
||||||
|
|
||||||
|
|
||||||
|
let parse_unop (json : Safe.t) =
|
||||||
|
let unop_map = [("Neg", Unop.Neg); ("BNot", Unop.BNot); ("LNot", Unop.LNot)] in
|
||||||
|
List.Assoc.find_exn ~equal:String.equal unop_map (to_string json)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_binop (json : Safe.t) =
|
||||||
|
(*TODO: need to check the usage of "None" *)
|
||||||
|
let binop_map =
|
||||||
|
[ ("PlusA", Binop.PlusA None)
|
||||||
|
; ("PlusPI", Binop.PlusPI)
|
||||||
|
; ("MinusA", Binop.MinusA None)
|
||||||
|
; ("MinusPI", Binop.MinusPI)
|
||||||
|
; ("MinusPP", Binop.MinusPP)
|
||||||
|
; ("Mult", Binop.Mult None)
|
||||||
|
; ("Div", Binop.Div)
|
||||||
|
; ("Mod", Binop.Mod)
|
||||||
|
; ("Shiftlt", Binop.Shiftlt)
|
||||||
|
; ("Shiftrt", Binop.Shiftrt)
|
||||||
|
; ("Lt", Binop.Lt)
|
||||||
|
; ("Gt", Binop.Gt)
|
||||||
|
; ("Le", Binop.Le)
|
||||||
|
; ("Ge", Binop.Ge)
|
||||||
|
; ("Eq", Binop.Eq)
|
||||||
|
; ("Ne", Binop.Ne)
|
||||||
|
; ("BAnd", Binop.BAnd)
|
||||||
|
; ("BXor", Binop.BXor)
|
||||||
|
; ("BOr", Binop.BOr)
|
||||||
|
; ("LAnd", Binop.LAnd)
|
||||||
|
; ("LOr", Binop.LOr) ]
|
||||||
|
in
|
||||||
|
List.Assoc.find_exn ~equal:String.equal binop_map (to_string json)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_typename (json : Safe.t) =
|
||||||
|
let tname = to_string (member "type_name_kind" json) in
|
||||||
|
if String.equal tname "TN_typedef" then typename_of_classname (to_string (member "name" json))
|
||||||
|
else if String.equal tname "CsuTypeName" then parse_csu json
|
||||||
|
(*what about if the name is <Module>*)
|
||||||
|
else Logging.die InternalError "Can't parse typename"
|
||||||
|
|
||||||
|
|
||||||
|
let parse_long (json : Safe.t) = Int64.of_string (Yojson.Safe.to_string json)
|
||||||
|
|
||||||
|
let parse_intrep (json : Safe.t) =
|
||||||
|
let v = parse_long (member "value" json) in
|
||||||
|
let p = to_bool (member "is_pointer" json) in
|
||||||
|
match (p, v) with true, 0L -> IntLit.null | _ -> IntLit.of_int64 v
|
||||||
|
|
||||||
|
|
||||||
|
let parse_floatrep (json : Safe.t) = Float.of_string (Yojson.Safe.to_string json)
|
||||||
|
|
||||||
|
let parse_ident (json : Safe.t) =
|
||||||
|
let k = to_string (member "kind" json) in
|
||||||
|
let kind =
|
||||||
|
if String.equal k "Normal" then Ident.knormal
|
||||||
|
else if String.equal k "Primed" then Ident.kprimed
|
||||||
|
else if String.equal k "Footprint" then Ident.kfootprint
|
||||||
|
else if String.equal k "None" then Ident.knone
|
||||||
|
else Logging.die InternalError "Unsupported identifier kind: %s" k
|
||||||
|
in
|
||||||
|
Ident.create_with_stamp kind
|
||||||
|
(Ident.string_to_name (to_string (member "name" json)))
|
||||||
|
(to_int (member "stamp" json))
|
||||||
|
|
||||||
|
|
||||||
|
let parse_fieldident (json : Safe.t) =
|
||||||
|
Fieldname.make StdTyp.Name.CSharp.system_string (to_string (member "field_name" json))
|
||||||
|
|
||||||
|
|
||||||
|
let parse_source_file (json : Safe.t) =
|
||||||
|
let p = to_string (member "path" json) in
|
||||||
|
SourceFile.create ~warn_on_error:false p
|
||||||
|
|
||||||
|
|
||||||
|
let parse_location (json : Safe.t) =
|
||||||
|
{ Location.line= to_int (member "line" json)
|
||||||
|
; Location.col= to_int (member "col" json)
|
||||||
|
; Location.file= parse_source_file (member "source_file" json) }
|
||||||
|
|
||||||
|
|
||||||
|
let rec parse_pvar (json : Safe.t) =
|
||||||
|
let pvname = Mangled.from_string (to_string (member "pv_name" json)) in
|
||||||
|
let pvkind = to_string (member "pv_kind" json) in
|
||||||
|
if String.equal pvkind "LocalVariable" then
|
||||||
|
let pname = parse_cil_procname (member "proc_name" json) in
|
||||||
|
Pvar.mk pvname pname
|
||||||
|
else if String.equal pvkind "CalledVariable" then
|
||||||
|
let pname = parse_cil_procname (member "proc_name" json) in
|
||||||
|
Pvar.mk_callee pvname pname
|
||||||
|
else if String.equal pvkind "GlobalVariable" then Pvar.mk_global pvname
|
||||||
|
else Logging.die InternalError "Unknown program variable kind %s" pvkind
|
||||||
|
|
||||||
|
|
||||||
|
and parse_constant (json : Safe.t) =
|
||||||
|
let const_kind = to_string (member "kind" json) in
|
||||||
|
let const_value = member "const_value" json in
|
||||||
|
if String.equal const_kind "Int" then
|
||||||
|
let i = parse_intrep const_value in
|
||||||
|
Const.Cint i
|
||||||
|
else if String.equal const_kind "Float" then
|
||||||
|
try
|
||||||
|
let f = parse_floatrep const_value in
|
||||||
|
Const.Cfloat f
|
||||||
|
with _ -> Const.Cfloat Float.nan
|
||||||
|
else if String.equal const_kind "Fun" then
|
||||||
|
let pname = parse_cil_procname const_value in
|
||||||
|
Const.Cfun pname
|
||||||
|
else if String.equal const_kind "Str" then Const.Cstr (to_string const_value)
|
||||||
|
else if String.equal const_kind "Class" then
|
||||||
|
Const.Cclass (Ident.string_to_name (to_string const_value))
|
||||||
|
else Logging.die InternalError "Unknown constant kind %s" const_kind
|
||||||
|
|
||||||
|
|
||||||
|
and parse_exp (json : Safe.t) =
|
||||||
|
let ekind = to_string (member "expr_kind" json) in
|
||||||
|
if String.equal ekind "VarExpression" then Exp.Var (parse_ident (member "identifier" json))
|
||||||
|
else if String.equal ekind "UnopExpression" then
|
||||||
|
let op = parse_unop (member "operator" json) in
|
||||||
|
let e = parse_exp (member "expression" json) in
|
||||||
|
let t =
|
||||||
|
let t_nullable = member "type" json in
|
||||||
|
match t_nullable with `Null -> None | _ -> Some (parse_sil_type_name t_nullable)
|
||||||
|
in
|
||||||
|
Exp.UnOp (op, e, t)
|
||||||
|
else if String.equal ekind "BinopExpression" then
|
||||||
|
let op = parse_binop (member "operator" json) in
|
||||||
|
let e1 = parse_exp (member "left" json) in
|
||||||
|
let e2 = parse_exp (member "right" json) in
|
||||||
|
Exp.BinOp (op, e1, e2)
|
||||||
|
else if String.equal ekind "ConstExpression" then Exp.Const (parse_constant json)
|
||||||
|
else if String.equal ekind "CastExpression" then
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
let e = parse_exp (member "expression" json) in
|
||||||
|
Exp.Cast (t, e)
|
||||||
|
else if String.equal ekind "LvarExpression" then Exp.Lvar (parse_pvar (member "pvar" json))
|
||||||
|
else if String.equal ekind "LfieldExpression" then
|
||||||
|
let e = parse_exp (member "expression" json) in
|
||||||
|
let fi = parse_fieldident (member "identifier" json) in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
Exp.Lfield (e, fi, t)
|
||||||
|
else if String.equal ekind "LindexExpression" then
|
||||||
|
let e1 = parse_exp (member "array" json) in
|
||||||
|
let e2 = parse_exp (member "index" json) in
|
||||||
|
Exp.Lindex (e1, e2)
|
||||||
|
else if String.equal ekind "SizeofExpression" then
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
let s = to_string (member "kind" json) in
|
||||||
|
match s with
|
||||||
|
| "exact" ->
|
||||||
|
Exp.Sizeof {typ= t; nbytes= None; dynamic_length= None; subtype= Subtype.exact}
|
||||||
|
| _ ->
|
||||||
|
Logging.die InternalError "Subtype in Sizeof instruction is not 'exact'"
|
||||||
|
else Logging.die InternalError "Unknown expression kind %s" ekind
|
||||||
|
|
||||||
|
|
||||||
|
and parse_struct_field (json : Safe.t) =
|
||||||
|
let fi = parse_fieldident json in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
let annot = parse_item_annotation (member "annotation" json) in
|
||||||
|
(fi, t, annot)
|
||||||
|
|
||||||
|
|
||||||
|
and parse_sil_type_name (json : Safe.t) : Typ.t =
|
||||||
|
let type_kind = to_string (member "type_kind" json) in
|
||||||
|
if String.equal type_kind "Tarray" then
|
||||||
|
let t = parse_sil_type_name (member "content_type" json) in
|
||||||
|
Typ.mk_array t
|
||||||
|
else if String.equal type_kind "Tfloat" then
|
||||||
|
let fkind = parse_fkind (member "kind" json) in
|
||||||
|
Typ.mk (Typ.Tfloat fkind)
|
||||||
|
else if String.equal type_kind "Tint" then
|
||||||
|
let ikind = parse_ikind (member "kind" json) in
|
||||||
|
Typ.mk (Typ.Tint ikind)
|
||||||
|
else if String.equal type_kind "Tptr" then
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
let pkind = parse_ptr_kind (member "kind" json) in
|
||||||
|
Typ.mk (Typ.Tptr (t, pkind))
|
||||||
|
else if String.equal type_kind "Tstruct" then
|
||||||
|
let tn = typename_of_classname (to_string (member "struct_name" json)) in
|
||||||
|
Typ.mk (Tstruct tn)
|
||||||
|
else if String.equal type_kind "Tvar" then
|
||||||
|
let tn = parse_typename (member "type_name" json) in
|
||||||
|
Typ.mk (Typ.TVar (Typ.Name.name tn))
|
||||||
|
else if String.equal type_kind "Tvoid" then StdTyp.void
|
||||||
|
else if String.equal type_kind "Tenum" then
|
||||||
|
(* Sil.Tenum (parse_list (parse_pair (fun n -> Mangled.from_string (to_string n)) parse_constant) value) *)
|
||||||
|
Logging.die InternalError "Enums are not supported yet"
|
||||||
|
else Logging.die InternalError "Unknown sil type kind %s" type_kind
|
||||||
|
|
||||||
|
|
||||||
|
and parse_item_annotation (json : Safe.t) : Annot.Item.t =
|
||||||
|
let parse_annotation (json : Safe.t) =
|
||||||
|
let class_name = to_string (member "class_name" json) in
|
||||||
|
let p = member "params" json in
|
||||||
|
let parameters = parse_list_parameters to_string p in
|
||||||
|
{Annot.class_name; Annot.parameters}
|
||||||
|
in
|
||||||
|
parse_list
|
||||||
|
(fun j ->
|
||||||
|
let a = member "annotation" j in
|
||||||
|
let v = member "visible" j in
|
||||||
|
(parse_annotation a, to_bool v) )
|
||||||
|
(member "annotations" json)
|
||||||
|
|
||||||
|
|
||||||
|
and parse_struct (json : Safe.t) =
|
||||||
|
let fields = parse_list parse_struct_field (member "instance_fields" json) in
|
||||||
|
let statics = parse_list parse_struct_field (member "static_fields" json) in
|
||||||
|
let supers = parse_list parse_csu (member "supers" json) in
|
||||||
|
let methods = parse_list parse_cil_procname (member "methods" json) in
|
||||||
|
let annots = parse_item_annotation json in
|
||||||
|
(fields, statics, supers, methods, annots)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_method_annotation (json : Safe.t) : Annot.Method.t =
|
||||||
|
let return = parse_item_annotation (member "return_value" json) in
|
||||||
|
let params = parse_list parse_item_annotation (member "params" json) in
|
||||||
|
{return; params}
|
||||||
|
|
||||||
|
|
||||||
|
let parse_captured_var (json : Safe.t) =
|
||||||
|
let n = to_string (member "name" json) in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
CapturedVar.make ~name:(Mangled.from_string n) ~typ:t ~capture_mode:Pvar.ByValue
|
||||||
|
|
||||||
|
|
||||||
|
let parse_proc_attributes_var (json : Safe.t) =
|
||||||
|
let n = to_string (member "name" json) in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
(Mangled.from_string n, t, Pvar.ByValue)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_proc_attributes_formals (json : Safe.t) =
|
||||||
|
let n, t, _ = parse_proc_attributes_var json in
|
||||||
|
(n, t)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_proc_attributes_locals (json : Safe.t) : ProcAttributes.var_data =
|
||||||
|
let n, t, _ = parse_proc_attributes_var json in
|
||||||
|
let mib = to_bool (member "modify_in_block" json) in
|
||||||
|
let ice = to_bool (member "is_const_expr" json) in
|
||||||
|
{name= n; typ= t; modify_in_block= mib; is_constexpr= ice; is_declared_unused= false}
|
||||||
|
|
||||||
|
|
||||||
|
let parse_proc_attributes (json : Safe.t) =
|
||||||
|
let access =
|
||||||
|
match to_string (member "access" json) with
|
||||||
|
| "Default" ->
|
||||||
|
PredSymb.Default
|
||||||
|
| "Public" ->
|
||||||
|
PredSymb.Public
|
||||||
|
| "Private" ->
|
||||||
|
PredSymb.Private
|
||||||
|
| "Protected" ->
|
||||||
|
PredSymb.Protected
|
||||||
|
| atype ->
|
||||||
|
Logging.die InternalError "Unsupported access type %s" atype
|
||||||
|
in
|
||||||
|
let captured = parse_list parse_captured_var (member "captured" json) in
|
||||||
|
let formals = parse_list parse_proc_attributes_formals (member "formals" json) in
|
||||||
|
let locals = parse_list parse_proc_attributes_locals (member "locals" json) in
|
||||||
|
let loc = parse_location (member "loc" json) in
|
||||||
|
let file = loc.file in
|
||||||
|
let proc_name = parse_cil_procname (member "proc_name" json) in
|
||||||
|
{ (ProcAttributes.default file proc_name) with
|
||||||
|
access
|
||||||
|
; captured
|
||||||
|
; exceptions= parse_list to_string (member "exceptions" json)
|
||||||
|
; formals
|
||||||
|
; is_abstract= to_bool (member "is_abstract" json)
|
||||||
|
; is_bridge_method= to_bool (member "is_bridge_method" json)
|
||||||
|
; is_defined= to_bool (member "is_defined" json)
|
||||||
|
; is_synthetic_method= to_bool (member "is_synthetic_method" json)
|
||||||
|
; loc
|
||||||
|
; locals
|
||||||
|
; method_annotation= parse_method_annotation (member "method_annotations" json)
|
||||||
|
; ret_type= parse_sil_type_name (member "ret_type" json) }
|
||||||
|
|
||||||
|
|
||||||
|
let parse_call_flags (json : Safe.t) =
|
||||||
|
{ CallFlags.default with
|
||||||
|
CallFlags.cf_virtual= to_bool (member "cf_virtual" json)
|
||||||
|
; CallFlags.cf_is_objc_block= to_bool (member "cf_is_objc_block" json) }
|
||||||
|
|
||||||
|
|
||||||
|
let parse_call_args (json : Safe.t) =
|
||||||
|
let e = parse_exp (member "expression" json) in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
(e, t)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_instr (json : Safe.t) =
|
||||||
|
let instr_kind = to_string (member "instruction_kind" json) in
|
||||||
|
let l = parse_location (member "location" json) in
|
||||||
|
if String.equal instr_kind "Load" then
|
||||||
|
let i = parse_ident (member "identifier" json) in
|
||||||
|
let e = parse_exp (member "expression" json) in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
Sil.Load {id= i; e; root_typ= t; typ= t; loc= l}
|
||||||
|
else if String.equal instr_kind "Store" then
|
||||||
|
let e1 = parse_exp (member "lvalue" json) in
|
||||||
|
let e2 = parse_exp (member "rvalue" json) in
|
||||||
|
let t = parse_sil_type_name (member "type" json) in
|
||||||
|
Sil.Store {e1; root_typ= t; typ= t; e2; loc= l}
|
||||||
|
else if String.equal instr_kind "Prune" then
|
||||||
|
let e = parse_exp (member "condition" json) in
|
||||||
|
let f = to_bool (member "true_branch" json) in
|
||||||
|
let k = parse_if_kind (member "if_kind" json) in
|
||||||
|
Sil.Prune (e, l, f, k)
|
||||||
|
else if String.equal instr_kind "Call" then
|
||||||
|
let rs =
|
||||||
|
(parse_ident (member "return_var" json), parse_sil_type_name (member "return_type" json))
|
||||||
|
in
|
||||||
|
let e = parse_exp (member "function_expression" json) in
|
||||||
|
let ps = parse_list parse_call_args (member "args" json) in
|
||||||
|
let fs = parse_call_flags (member "flags" json) in
|
||||||
|
Sil.Call (rs, e, ps, l, fs)
|
||||||
|
else Logging.die InternalError "Unknown instruction kind %s" instr_kind
|
||||||
|
|
||||||
|
|
||||||
|
(* This has the side-effect of inserting the procedure description into the CFG. *)
|
||||||
|
let parse_pdesc (cfg : Cfg.t) (pd_id_to_pd : Procdesc.t IntTbl.t) (start_nd_tbl : int IntTbl.t)
|
||||||
|
(exit_nd_tbl : int IntTbl.t) (json : Safe.t) =
|
||||||
|
let _attrs = parse_proc_attributes (member "pd_attributes" json) in
|
||||||
|
let _id = to_int (member "pd_id" json) in
|
||||||
|
(* Store away start/end node, to be filled in later *)
|
||||||
|
IntTbl.add start_nd_tbl _id (to_int (member "pd_start_node" json)) ;
|
||||||
|
IntTbl.add exit_nd_tbl _id (to_int (member "pd_exit_node" json)) ;
|
||||||
|
(* let open Procdesc in *)
|
||||||
|
let pd =
|
||||||
|
let pname = _attrs.proc_name in
|
||||||
|
match Procname.Hash.find_opt cfg pname with
|
||||||
|
| Some pdesc ->
|
||||||
|
pdesc
|
||||||
|
| None ->
|
||||||
|
Cfg.create_proc_desc cfg _attrs
|
||||||
|
in
|
||||||
|
IntTbl.add pd_id_to_pd _id pd
|
||||||
|
|
||||||
|
|
||||||
|
(* Expect the entire node json to be passed *)
|
||||||
|
let parse_stmt_nodekind (json : Safe.t) : Procdesc.Node.stmt_nodekind =
|
||||||
|
let nk_comment = member "stmt_node_comment" json in
|
||||||
|
match to_string (member "stmt_node_kind" json) with
|
||||||
|
| "AssertionFailure" ->
|
||||||
|
Procdesc.Node.AssertionFailure
|
||||||
|
| "BetweenJoinAndExit" ->
|
||||||
|
Procdesc.Node.BetweenJoinAndExit
|
||||||
|
| "BinaryConditionalStmtInit" ->
|
||||||
|
Procdesc.Node.BinaryConditionalStmtInit
|
||||||
|
| "BinaryOperatorStmt" ->
|
||||||
|
Procdesc.Node.BinaryOperatorStmt (to_string nk_comment)
|
||||||
|
| "Call" ->
|
||||||
|
Procdesc.Node.Call (to_string nk_comment)
|
||||||
|
| "CallObjCNew" ->
|
||||||
|
Procdesc.Node.CallObjCNew
|
||||||
|
| "ClassCastException" ->
|
||||||
|
Procdesc.Node.ClassCastException
|
||||||
|
| "ConditionalStmtBranch" ->
|
||||||
|
Procdesc.Node.ConditionalStmtBranch
|
||||||
|
| "ConstructorInit" ->
|
||||||
|
Procdesc.Node.ConstructorInit
|
||||||
|
| "CXXDynamicCast" ->
|
||||||
|
Procdesc.Node.CXXDynamicCast
|
||||||
|
| "CXXNewExpr" ->
|
||||||
|
Procdesc.Node.CXXNewExpr
|
||||||
|
| "CXXStdInitializerListExpr" ->
|
||||||
|
Procdesc.Node.CXXStdInitializerListExpr
|
||||||
|
| "CXXTypeidExpr" ->
|
||||||
|
Procdesc.Node.CXXTypeidExpr
|
||||||
|
| "DeclStmt" ->
|
||||||
|
Procdesc.Node.DeclStmt
|
||||||
|
| "DefineBody" ->
|
||||||
|
Procdesc.Node.DefineBody
|
||||||
|
| "ExceptionHandler" ->
|
||||||
|
Procdesc.Node.ExceptionHandler
|
||||||
|
| "ExceptionsSink" ->
|
||||||
|
Procdesc.Node.ExceptionsSink
|
||||||
|
| "FinallyBranch" ->
|
||||||
|
Procdesc.Node.FinallyBranch
|
||||||
|
| "GCCAsmStmt" ->
|
||||||
|
Procdesc.Node.GCCAsmStmt
|
||||||
|
| "GenericSelectionExpr" ->
|
||||||
|
Procdesc.Node.GenericSelectionExpr
|
||||||
|
| "IfStmtBranch" ->
|
||||||
|
Procdesc.Node.IfStmtBranch
|
||||||
|
| "InitializeDynamicArrayLength" ->
|
||||||
|
Procdesc.Node.InitializeDynamicArrayLength
|
||||||
|
| "InitListExp" ->
|
||||||
|
Procdesc.Node.InitListExp
|
||||||
|
| "MessageCall" ->
|
||||||
|
Procdesc.Node.MessageCall (to_string nk_comment)
|
||||||
|
| "MethodBody" ->
|
||||||
|
Procdesc.Node.MethodBody
|
||||||
|
| "MonitorEnter" ->
|
||||||
|
Procdesc.Node.MonitorEnter
|
||||||
|
| "MonitorExit" ->
|
||||||
|
Procdesc.Node.MonitorExit
|
||||||
|
| "ObjCCPPThrow" ->
|
||||||
|
Procdesc.Node.ObjCCPPThrow
|
||||||
|
| "OutOfBound" ->
|
||||||
|
Procdesc.Node.OutOfBound
|
||||||
|
| "ReturnStmt" ->
|
||||||
|
Procdesc.Node.ReturnStmt
|
||||||
|
| "Skip" ->
|
||||||
|
Procdesc.Node.Skip (to_string nk_comment)
|
||||||
|
| "SwitchStmt" ->
|
||||||
|
Procdesc.Node.SwitchStmt
|
||||||
|
| "ThisNotNull" ->
|
||||||
|
Procdesc.Node.ThisNotNull
|
||||||
|
| "Throw" ->
|
||||||
|
Procdesc.Node.Throw
|
||||||
|
| "ThrowNPE" ->
|
||||||
|
Procdesc.Node.ThrowNPE
|
||||||
|
| "UnaryOperator" ->
|
||||||
|
Procdesc.Node.UnaryOperator
|
||||||
|
| snk ->
|
||||||
|
Logging.die InternalError "Unknown stmt node kind %s" snk
|
||||||
|
|
||||||
|
|
||||||
|
let parse_prune_nodekind (json : Safe.t) : Procdesc.Node.prune_node_kind =
|
||||||
|
match to_string json with
|
||||||
|
| "ExceptionHandler" ->
|
||||||
|
PruneNodeKind_ExceptionHandler
|
||||||
|
| "FalseBranch" ->
|
||||||
|
PruneNodeKind_FalseBranch
|
||||||
|
| "InBound" ->
|
||||||
|
PruneNodeKind_InBound
|
||||||
|
| "IsInstance" ->
|
||||||
|
PruneNodeKind_IsInstance
|
||||||
|
| "MethodBody" ->
|
||||||
|
PruneNodeKind_MethodBody
|
||||||
|
| "NotNull" ->
|
||||||
|
PruneNodeKind_NotNull
|
||||||
|
| "TrueBranch" ->
|
||||||
|
PruneNodeKind_TrueBranch
|
||||||
|
| pnk ->
|
||||||
|
Logging.die InternalError "Unknown prune node kind %s" pnk
|
||||||
|
|
||||||
|
|
||||||
|
let parse_nodekind (_pd_id_to_pd : Procdesc.t IntTbl.t) (json : Safe.t) =
|
||||||
|
let nkname = to_string (member "nd_kind" json) in
|
||||||
|
if String.equal nkname "StartNode" then Procdesc.Node.Start_node
|
||||||
|
else if String.equal nkname "ExitNode" then Procdesc.Node.Exit_node
|
||||||
|
else if String.equal nkname "StatementNode" then
|
||||||
|
Procdesc.Node.Stmt_node (parse_stmt_nodekind json)
|
||||||
|
else if String.equal nkname "JoinNode" then Procdesc.Node.Join_node
|
||||||
|
else if String.equal nkname "PruneNode" then
|
||||||
|
let f = to_bool (member "true_branch" json) in
|
||||||
|
let ik = parse_if_kind (member "if_kind" json) in
|
||||||
|
let d = parse_prune_nodekind (member "prune_node_kind" json) in
|
||||||
|
Procdesc.Node.Prune_node (f, ik, d)
|
||||||
|
else if String.equal nkname "SkipNode" then
|
||||||
|
Procdesc.Node.Skip_node (to_string (member "skip_node_comment" json))
|
||||||
|
else Logging.die InternalError "Unknown nodekind: %s" nkname
|
||||||
|
|
||||||
|
|
||||||
|
let parse_node (pd_id_to_pd : Procdesc.t IntTbl.t) (nd_id_to_node : Procdesc.Node.t IntTbl.t)
|
||||||
|
(nd_id_to_exn_nodes : int list IntTbl.t) (nd_id_to_pred_nodes : int list IntTbl.t)
|
||||||
|
(nd_id_to_succ_nodes : int list IntTbl.t) (json : Safe.t) =
|
||||||
|
let nd_id = to_int (member "nd_id" json) in
|
||||||
|
IntTbl.add nd_id_to_exn_nodes nd_id (parse_list to_int (member "nd_exn_ids" json)) ;
|
||||||
|
let nd_instrs = parse_list parse_instr (member "nd_instrs" json) in
|
||||||
|
let nd_kind = parse_nodekind pd_id_to_pd json in
|
||||||
|
let nd_loc = parse_location (member "nd_loc" json) in
|
||||||
|
IntTbl.add nd_id_to_pred_nodes nd_id (parse_list to_int (member "nd_pred_ids" json)) ;
|
||||||
|
IntTbl.add nd_id_to_succ_nodes nd_id (parse_list to_int (member "nd_succ_ids" json)) ;
|
||||||
|
let nd_proc_desc = IntTbl.find pd_id_to_pd (to_int (member "nd_proc_id" json)) in
|
||||||
|
let node = Procdesc.create_node nd_proc_desc nd_loc nd_kind nd_instrs in
|
||||||
|
IntTbl.add nd_id_to_node nd_id node ;
|
||||||
|
node
|
||||||
|
|
||||||
|
|
||||||
|
let parse_cfg (json : Safe.t) =
|
||||||
|
let cfg = Cfg.create () in
|
||||||
|
(* These hold information that's in the procedure description or nodes, but can only be completed once we've parsed all nodes. *)
|
||||||
|
let pd_id_to_pd = IntTbl.create 1000 in
|
||||||
|
let pd_id_to_start_node = IntTbl.create 1000 in
|
||||||
|
let pd_id_to_exit_node = IntTbl.create 1000 in
|
||||||
|
let nd_id_to_node = IntTbl.create 1000 in
|
||||||
|
let nd_id_to_exn_nodes = IntTbl.create 1000 in
|
||||||
|
let nd_id_to_pred_nodes = IntTbl.create 1000 in
|
||||||
|
let nd_id_to_succ_nodes = IntTbl.create 1000 in
|
||||||
|
List.iter
|
||||||
|
~f:(fun (_, pdjson) -> parse_pdesc cfg pd_id_to_pd pd_id_to_start_node pd_id_to_exit_node pdjson)
|
||||||
|
(to_assoc (member "procs" json)) ;
|
||||||
|
let _ =
|
||||||
|
parse_list
|
||||||
|
(parse_node pd_id_to_pd nd_id_to_node nd_id_to_exn_nodes nd_id_to_pred_nodes
|
||||||
|
nd_id_to_succ_nodes)
|
||||||
|
(member "nodes" json)
|
||||||
|
in
|
||||||
|
(* Now fix up the dangling ends *)
|
||||||
|
IntTbl.iter
|
||||||
|
(fun pd_id pd ->
|
||||||
|
let start_node = IntTbl.find nd_id_to_node (IntTbl.find pd_id_to_start_node pd_id) in
|
||||||
|
let exit_node = IntTbl.find nd_id_to_node (IntTbl.find pd_id_to_exit_node pd_id) in
|
||||||
|
Procdesc.set_start_node pd start_node ;
|
||||||
|
Procdesc.set_exit_node pd exit_node )
|
||||||
|
pd_id_to_pd ;
|
||||||
|
IntTbl.iter
|
||||||
|
(fun (nd_id : int) (node : Procdesc.Node.t) ->
|
||||||
|
let exn_nodes =
|
||||||
|
List.map ~f:(IntTbl.find nd_id_to_node) (IntTbl.find nd_id_to_exn_nodes nd_id)
|
||||||
|
in
|
||||||
|
let succ_nodes =
|
||||||
|
List.map ~f:(IntTbl.find nd_id_to_node) (IntTbl.find nd_id_to_succ_nodes nd_id)
|
||||||
|
in
|
||||||
|
Procdesc.set_succs node ~normal:(Some succ_nodes) ~exn:(Some exn_nodes) )
|
||||||
|
nd_id_to_node ;
|
||||||
|
cfg
|
||||||
|
|
||||||
|
|
||||||
|
let parse_tenv_type (json : Safe.t) tenv =
|
||||||
|
let tn = parse_typename (member "type_name" json) in
|
||||||
|
let fields, statics, supers, methods, annots = parse_struct (member "type_struct" json) in
|
||||||
|
ignore (Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots tn)
|
||||||
|
|
||||||
|
|
||||||
|
let parse_tenv (json : Safe.t) =
|
||||||
|
let tenv = Tenv.create () in
|
||||||
|
List.iter ~f:(fun entry -> parse_tenv_type entry tenv) (to_list json) ;
|
||||||
|
tenv
|
||||||
|
|
||||||
|
|
||||||
|
let clear_caches () =
|
||||||
|
Summary.OnDisk.clear_cache () ;
|
||||||
|
Procname.SQLite.clear_cache ()
|
||||||
|
|
||||||
|
|
||||||
|
let analyze_json cfg_json tenv_json =
|
||||||
|
clear_caches () ;
|
||||||
|
InferAnalyze.register_active_checkers () ;
|
||||||
|
if not Config.continue_analysis then
|
||||||
|
if Config.reanalyze then (
|
||||||
|
L.progress "Invalidating procedures to be reanalyzed@." ;
|
||||||
|
Summary.OnDisk.reset_all ~filter:(Lazy.force Filtering.procedures_filter) () ;
|
||||||
|
L.progress "Done@." )
|
||||||
|
else if not Config.incremental_analysis then DBWriter.delete_all_specs () ;
|
||||||
|
Printexc.record_backtrace true ;
|
||||||
|
let tenv = parse_tenv (Yojson.Safe.from_file tenv_json) in
|
||||||
|
let cfg = parse_cfg (Yojson.Safe.from_file cfg_json) in
|
||||||
|
let source_file = SourceFile.create ~warn_on_error:false "./Program.cs" in
|
||||||
|
(* let source_dir = DB.source_dir_from_source_file source_file in
|
||||||
|
Utils.create_dir (DB.source_dir_to_string source_dir) ;
|
||||||
|
let tenv_file = DB.source_dir_get_internal_file source_dir ".tenv" in
|
||||||
|
let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in
|
||||||
|
Tenv.store_to_filename tenv tenv_file ; *)
|
||||||
|
Tenv.store_global tenv ;
|
||||||
|
Cfg.store source_file cfg ;
|
||||||
|
SourceFiles.add source_file cfg Tenv.Global None ;
|
||||||
|
(*Cfg.print_cfg_procs cfg ;*)
|
||||||
|
Language.curr_language := Language.CIL ;
|
||||||
|
let exe_env = Exe_env.mk () in
|
||||||
|
Ondemand.analyze_file exe_env source_file ;
|
||||||
|
if Config.write_html then Printer.write_all_html_files source_file ;
|
||||||
|
()
|
@ -0,0 +1,12 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
|
||||||
|
(** Main module for the analyzejson analysis after the capture phase *)
|
||||||
|
|
||||||
|
val analyze_json : string -> string -> unit
|
@ -0,0 +1,266 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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 Hashtbl = Caml.Hashtbl
|
||||||
|
|
||||||
|
module LeakList = struct
|
||||||
|
include Base.List
|
||||||
|
|
||||||
|
let append_one leakList typeWithLeak = leakList @ [typeWithLeak]
|
||||||
|
end
|
||||||
|
|
||||||
|
(** Resource analysis in loop and branch*)
|
||||||
|
module FiniteBounds = struct
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let leq ~lhs ~rhs = lhs <= rhs
|
||||||
|
|
||||||
|
let join a b = max a b
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
||||||
|
|
||||||
|
let pp fmt astate = F.fprintf fmt "%d" astate
|
||||||
|
end
|
||||||
|
|
||||||
|
(** Resource analysis in loop *)
|
||||||
|
module BoundsWithTop = struct
|
||||||
|
open AbstractDomain.Types
|
||||||
|
include AbstractDomain.TopLifted (FiniteBounds)
|
||||||
|
|
||||||
|
let widening_threshold = 5
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters =
|
||||||
|
match (prev, next) with
|
||||||
|
| Top, _ | _, Top ->
|
||||||
|
Top
|
||||||
|
| NonTop prev, NonTop next when num_iters < widening_threshold ->
|
||||||
|
NonTop (FiniteBounds.join prev next)
|
||||||
|
| NonTop _, NonTop _ (* num_iters >= widening_threshold *) ->
|
||||||
|
Top
|
||||||
|
end
|
||||||
|
|
||||||
|
module ResourcesHeld = AbstractDomain.Map (AccessPath) (BoundsWithTop)
|
||||||
|
open AbstractDomain.Types
|
||||||
|
|
||||||
|
(** Initializes resources to type map *)
|
||||||
|
let type_map = ref (Hashtbl.create 100)
|
||||||
|
|
||||||
|
(** Initializes resources to count map *)
|
||||||
|
let initial = ResourcesHeld.empty
|
||||||
|
|
||||||
|
(** Updates the count of a specific resource *)
|
||||||
|
let update_count count n = match count with Top -> Top | NonTop held -> NonTop (held + n)
|
||||||
|
|
||||||
|
(** Increments the count of a specific resource *)
|
||||||
|
let incr_count count = update_count count 1
|
||||||
|
|
||||||
|
(** Decrements the count of a specific resource *)
|
||||||
|
let decr_count count = update_count count (-1)
|
||||||
|
|
||||||
|
(** Checks the count of a specific resource *)
|
||||||
|
let find_count access_path held =
|
||||||
|
match ResourcesHeld.find_opt access_path held with Some count -> count | None -> NonTop 0
|
||||||
|
|
||||||
|
|
||||||
|
(** Checks the count of resources held if resource exists, otherwise returns false*)
|
||||||
|
let check_count access_path held =
|
||||||
|
let old_count = find_count access_path held in
|
||||||
|
match old_count with NonTop count when count > 0 -> true | _ -> false
|
||||||
|
|
||||||
|
|
||||||
|
let get_type_map = !type_map
|
||||||
|
|
||||||
|
let reset_type_map = Hashtbl.reset !type_map
|
||||||
|
|
||||||
|
(** Adds resources acquired to records *)
|
||||||
|
let acquire_resource access_path class_name held =
|
||||||
|
let add_resource_to_hash =
|
||||||
|
match ResourcesHeld.find_opt access_path held with
|
||||||
|
| Some _ ->
|
||||||
|
()
|
||||||
|
| None ->
|
||||||
|
Hashtbl.add !type_map access_path class_name
|
||||||
|
in
|
||||||
|
add_resource_to_hash ;
|
||||||
|
let old_count = find_count access_path held in
|
||||||
|
ResourcesHeld.add access_path (incr_count old_count) held
|
||||||
|
|
||||||
|
|
||||||
|
(** Releases acquired resources from records when release function is called*)
|
||||||
|
let release_resource access_path held =
|
||||||
|
let old_count = find_count access_path held in
|
||||||
|
let remove_resource_from_hash =
|
||||||
|
match old_count with
|
||||||
|
| NonTop count when count < 2 ->
|
||||||
|
Hashtbl.remove !type_map access_path
|
||||||
|
| _ ->
|
||||||
|
()
|
||||||
|
in
|
||||||
|
remove_resource_from_hash ;
|
||||||
|
ResourcesHeld.add access_path (decr_count old_count) held
|
||||||
|
|
||||||
|
|
||||||
|
(** Re-assigns resources when transferred to other objects*)
|
||||||
|
let assign lhs_access_path rhs_access_path held =
|
||||||
|
let add_type_map search_access_path access_path =
|
||||||
|
match Hashtbl.find !type_map search_access_path with
|
||||||
|
| class_name ->
|
||||||
|
Hashtbl.add !type_map access_path class_name ;
|
||||||
|
Hashtbl.remove !type_map search_access_path
|
||||||
|
| exception Caml.Not_found ->
|
||||||
|
()
|
||||||
|
in
|
||||||
|
let one_binding access_path count held =
|
||||||
|
match
|
||||||
|
AccessPath.replace_prefix ~prefix:rhs_access_path ~replace_with:access_path lhs_access_path
|
||||||
|
with
|
||||||
|
| Some base_access_path ->
|
||||||
|
add_type_map access_path base_access_path ;
|
||||||
|
ResourcesHeld.add base_access_path count held
|
||||||
|
| None ->
|
||||||
|
if AccessPath.equal rhs_access_path access_path then (
|
||||||
|
add_type_map access_path lhs_access_path ;
|
||||||
|
ResourcesHeld.add lhs_access_path count held )
|
||||||
|
else ResourcesHeld.add access_path count held
|
||||||
|
in
|
||||||
|
ResourcesHeld.fold one_binding held ResourcesHeld.empty
|
||||||
|
|
||||||
|
|
||||||
|
(** Checks if there is a resource leak*)
|
||||||
|
let has_leak formal_map held =
|
||||||
|
(* test if we acquired resources that we do not return to the caller *)
|
||||||
|
let is_local_leak access_path count =
|
||||||
|
let base, _ = access_path in
|
||||||
|
match (count, base) with
|
||||||
|
| Top, _ ->
|
||||||
|
false
|
||||||
|
| NonTop count, _ when count > 1 ->
|
||||||
|
true
|
||||||
|
| NonTop count, _ when count <= 0 ->
|
||||||
|
false
|
||||||
|
(* count = 1 *)
|
||||||
|
| _, (var, _) when Var.is_global var ->
|
||||||
|
false
|
||||||
|
| _, (ret, _) when Var.is_return ret ->
|
||||||
|
false
|
||||||
|
| _, base when FormalMap.is_formal base formal_map ->
|
||||||
|
false
|
||||||
|
| _ ->
|
||||||
|
true
|
||||||
|
in
|
||||||
|
ResourcesHeld.exists is_local_leak held
|
||||||
|
|
||||||
|
|
||||||
|
(** module for resource leak summary *)
|
||||||
|
module Summary = struct
|
||||||
|
module InterfaceAccessPath = struct
|
||||||
|
type base = Return | Formal of int [@@deriving compare]
|
||||||
|
|
||||||
|
let pp_base f = function
|
||||||
|
| Return ->
|
||||||
|
F.pp_print_string f "Return"
|
||||||
|
| Formal i ->
|
||||||
|
F.fprintf f "Formal(%d)" i
|
||||||
|
|
||||||
|
|
||||||
|
type t = base * AccessPath.access list [@@deriving compare]
|
||||||
|
|
||||||
|
let pp f = function
|
||||||
|
| base, [] ->
|
||||||
|
pp_base f base
|
||||||
|
| base, accesses ->
|
||||||
|
F.fprintf f "%a.%a" pp_base base AccessPath.pp_access_list accesses
|
||||||
|
end
|
||||||
|
|
||||||
|
module ResourcesFromFormals = PrettyPrintable.MakePPMap (InterfaceAccessPath)
|
||||||
|
|
||||||
|
let interface_type_map = ref (Hashtbl.create 100)
|
||||||
|
|
||||||
|
let reset_interface_type_map = Hashtbl.reset !interface_type_map
|
||||||
|
|
||||||
|
type t = BoundsWithTop.t ResourcesFromFormals.t
|
||||||
|
|
||||||
|
let pp = ResourcesFromFormals.pp ~pp_value:BoundsWithTop.pp
|
||||||
|
|
||||||
|
let make formal_map held =
|
||||||
|
let to_interface access_path =
|
||||||
|
let base, accesses = access_path in
|
||||||
|
match FormalMap.get_formal_index base formal_map with
|
||||||
|
| Some i ->
|
||||||
|
Some (InterfaceAccessPath.Formal i, accesses)
|
||||||
|
| None ->
|
||||||
|
if Var.is_return (fst base) then Some (InterfaceAccessPath.Return, accesses) else None
|
||||||
|
in
|
||||||
|
let add_resource_to_hash interface_access_path class_name =
|
||||||
|
Hashtbl.add !interface_type_map interface_access_path class_name
|
||||||
|
in
|
||||||
|
let add_to_type_map search_access_path interface_access_path =
|
||||||
|
match Hashtbl.find !type_map search_access_path with
|
||||||
|
| class_name ->
|
||||||
|
add_resource_to_hash interface_access_path class_name
|
||||||
|
| exception Caml.Not_found ->
|
||||||
|
()
|
||||||
|
in
|
||||||
|
ResourcesHeld.fold
|
||||||
|
(fun access_path count acquired ->
|
||||||
|
match to_interface access_path with
|
||||||
|
| Some interface_access_path ->
|
||||||
|
add_to_type_map access_path interface_access_path ;
|
||||||
|
ResourcesFromFormals.add interface_access_path count acquired
|
||||||
|
| None ->
|
||||||
|
acquired )
|
||||||
|
held ResourcesFromFormals.empty
|
||||||
|
|
||||||
|
|
||||||
|
let apply ~callee:summary ~return ~actuals held =
|
||||||
|
let apply_one (base, accesses) callee_count held =
|
||||||
|
let access_path_opt =
|
||||||
|
match (base : InterfaceAccessPath.base) with
|
||||||
|
| Return ->
|
||||||
|
Some (return, accesses)
|
||||||
|
| Formal i -> (
|
||||||
|
match List.nth actuals i with
|
||||||
|
| Some (HilExp.AccessExpression actual_expr) ->
|
||||||
|
Some (AccessPath.append (HilExp.AccessExpression.to_access_path actual_expr) accesses)
|
||||||
|
| _ ->
|
||||||
|
None )
|
||||||
|
in
|
||||||
|
let add_type_map search_access_path =
|
||||||
|
match Hashtbl.find !interface_type_map (base, accesses) with
|
||||||
|
| class_name ->
|
||||||
|
Hashtbl.add !type_map search_access_path class_name
|
||||||
|
| exception Caml.Not_found ->
|
||||||
|
()
|
||||||
|
in
|
||||||
|
match access_path_opt with
|
||||||
|
| None ->
|
||||||
|
held
|
||||||
|
| Some access_path ->
|
||||||
|
let new_count =
|
||||||
|
match callee_count with
|
||||||
|
| Top ->
|
||||||
|
Top
|
||||||
|
| NonTop callee_count ->
|
||||||
|
let old_count =
|
||||||
|
ResourcesHeld.find_opt access_path held |> Option.value ~default:(NonTop 0)
|
||||||
|
in
|
||||||
|
update_count old_count callee_count
|
||||||
|
in
|
||||||
|
let add_resource_to_hash =
|
||||||
|
match new_count with NonTop count when count > 0 -> add_type_map access_path | _ -> ()
|
||||||
|
in
|
||||||
|
add_resource_to_hash ;
|
||||||
|
ResourcesHeld.add access_path new_count held
|
||||||
|
in
|
||||||
|
ResourcesFromFormals.fold apply_one summary held
|
||||||
|
end
|
||||||
|
|
||||||
|
type summary = Summary.t
|
||||||
|
|
||||||
|
include ResourcesHeld
|
@ -0,0 +1,46 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
module LeakList : sig
|
||||||
|
include module type of Base.List
|
||||||
|
|
||||||
|
val append_one : 'a list -> 'a -> 'a list
|
||||||
|
end
|
||||||
|
|
||||||
|
val check_count : AccessPath.t -> t -> bool
|
||||||
|
|
||||||
|
val get_type_map : (AccessPath.t, string) Caml.Hashtbl.t
|
||||||
|
|
||||||
|
val reset_type_map : unit
|
||||||
|
|
||||||
|
val acquire_resource : AccessPath.t -> string -> t -> t
|
||||||
|
|
||||||
|
val release_resource : AccessPath.t -> t -> t
|
||||||
|
|
||||||
|
val assign : AccessPath.t -> AccessPath.t -> t -> t
|
||||||
|
|
||||||
|
val has_leak : FormalMap.t -> t -> bool
|
||||||
|
|
||||||
|
type summary
|
||||||
|
|
||||||
|
module Summary : sig
|
||||||
|
val apply : callee:summary -> return:AccessPath.base -> actuals:HilExp.t list -> t -> t
|
||||||
|
|
||||||
|
val reset_interface_type_map : unit
|
||||||
|
|
||||||
|
val make : FormalMap.t -> t -> summary
|
||||||
|
|
||||||
|
val pp : Format.formatter -> summary -> unit
|
||||||
|
|
||||||
|
type t = summary
|
||||||
|
end
|
@ -0,0 +1,200 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
module Hashtbl = Caml.Hashtbl
|
||||||
|
|
||||||
|
let leak_list = ref []
|
||||||
|
|
||||||
|
let type_map = ref (Hashtbl.create 100)
|
||||||
|
|
||||||
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
||||||
|
module CFG = CFG
|
||||||
|
module Domain = ResourceLeakCSDomain
|
||||||
|
|
||||||
|
type analysis_data = ResourceLeakCSDomain.Summary.t InterproceduralAnalysis.t
|
||||||
|
|
||||||
|
let is_closeable_typename tenv typename =
|
||||||
|
let is_closable_interface typename _ =
|
||||||
|
match Typ.Name.name typename with
|
||||||
|
| "java.io.AutoCloseable" | "java.io.Closeable" ->
|
||||||
|
true
|
||||||
|
| "System.IDisposable" ->
|
||||||
|
true
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
in
|
||||||
|
PatternMatch.supertype_exists tenv is_closable_interface typename
|
||||||
|
|
||||||
|
|
||||||
|
let is_closeable_procname tenv procname =
|
||||||
|
match procname with
|
||||||
|
| Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv (Procname.Java.get_class_type_name java_procname)
|
||||||
|
| Procname.CSharp csharp_procname ->
|
||||||
|
is_closeable_typename tenv (Procname.CSharp.get_class_type_name csharp_procname)
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Procname.is_constructor procname && is_closeable_procname tenv procname
|
||||||
|
|
||||||
|
|
||||||
|
let releases_resource tenv procname =
|
||||||
|
(* We assume the close method of a Closeable releases all of its resources *)
|
||||||
|
match procname with
|
||||||
|
| Procname.CSharp _ ->
|
||||||
|
( String.equal "Close" (Procname.get_method procname)
|
||||||
|
|| String.equal "Dispose" (Procname.get_method procname) )
|
||||||
|
&& is_closeable_procname tenv procname
|
||||||
|
| _ ->
|
||||||
|
String.equal "close" (Procname.get_method procname) && is_closeable_procname tenv procname
|
||||||
|
|
||||||
|
|
||||||
|
(** Take an abstract state and instruction, produce a new abstract state *)
|
||||||
|
let exec_instr (astate : ResourceLeakCSDomain.t)
|
||||||
|
{InterproceduralAnalysis.proc_desc; tenv; analyze_dependency; _} _ (instr : HilInstr.t) =
|
||||||
|
let assign_type_map = type_map := ResourceLeakCSDomain.get_type_map in
|
||||||
|
assign_type_map ;
|
||||||
|
let is_not_enumerable =
|
||||||
|
let contains s1 s2 =
|
||||||
|
let re = Str.regexp_string s2 in
|
||||||
|
try
|
||||||
|
ignore (Str.search_forward re s1 0) ;
|
||||||
|
false
|
||||||
|
with Not_found_s _ | Caml.Not_found -> true
|
||||||
|
in
|
||||||
|
contains (Procname.to_string (Procdesc.get_proc_name proc_desc)) "IEnumerable"
|
||||||
|
&& contains (Procname.to_string (Procdesc.get_proc_name proc_desc)) "Enumerator"
|
||||||
|
in
|
||||||
|
match instr with
|
||||||
|
| Call (_return, Direct callee_procname, HilExp.AccessExpression allocated :: _, _, _loc)
|
||||||
|
when acquires_resource tenv callee_procname && is_not_enumerable ->
|
||||||
|
let get_class_name =
|
||||||
|
match callee_procname with
|
||||||
|
| Procname.Java java_procname ->
|
||||||
|
Procname.Java.get_class_name java_procname
|
||||||
|
| Procname.CSharp csharp_procname ->
|
||||||
|
Procname.CSharp.get_class_name csharp_procname
|
||||||
|
| _ ->
|
||||||
|
L.die InternalError "Unsupported procname kind! Only Java and .NET is supported"
|
||||||
|
in
|
||||||
|
ResourceLeakCSDomain.acquire_resource
|
||||||
|
(HilExp.AccessExpression.to_access_path allocated)
|
||||||
|
get_class_name astate
|
||||||
|
| Call (_, Direct callee_procname, [actual], _, _loc)
|
||||||
|
when releases_resource tenv callee_procname -> (
|
||||||
|
match actual with
|
||||||
|
| HilExp.AccessExpression access_expr ->
|
||||||
|
ResourceLeakCSDomain.release_resource
|
||||||
|
(HilExp.AccessExpression.to_access_path access_expr)
|
||||||
|
astate
|
||||||
|
| _ ->
|
||||||
|
astate )
|
||||||
|
| Call (return, Direct callee_procname, actuals, _, _loc) -> (
|
||||||
|
match analyze_dependency callee_procname with
|
||||||
|
| Some (_callee_proc_desc, callee_summary) ->
|
||||||
|
(* interprocedural analysis produced a summary: use it *)
|
||||||
|
ResourceLeakCSDomain.Summary.apply ~callee:callee_summary ~return ~actuals astate
|
||||||
|
| None ->
|
||||||
|
(* No summary for [callee_procname]; it's native code or missing for some reason *)
|
||||||
|
astate )
|
||||||
|
| Assign (access_expr, AccessExpression rhs_access_expr, _loc) ->
|
||||||
|
ResourceLeakCSDomain.assign
|
||||||
|
(HilExp.AccessExpression.to_access_path access_expr)
|
||||||
|
(HilExp.AccessExpression.to_access_path rhs_access_expr)
|
||||||
|
astate
|
||||||
|
| Assign (lhs_access_path, rhs_exp, _loc) -> (
|
||||||
|
match rhs_exp with
|
||||||
|
| HilExp.AccessExpression access_expr ->
|
||||||
|
ResourceLeakCSDomain.assign
|
||||||
|
(HilExp.AccessExpression.to_access_path lhs_access_path)
|
||||||
|
(HilExp.AccessExpression.to_access_path access_expr)
|
||||||
|
astate
|
||||||
|
| _ ->
|
||||||
|
astate )
|
||||||
|
| Assume (assume_exp, _, _, _loc) -> (
|
||||||
|
(* a conditional assume([assume_exp]). blocks if [assume_exp] evaluates to false *)
|
||||||
|
let rec extract_null_compare_expr expr =
|
||||||
|
match expr with
|
||||||
|
| HilExp.Cast (_, e) ->
|
||||||
|
extract_null_compare_expr e
|
||||||
|
| HilExp.BinaryOperator (Binop.Eq, HilExp.AccessExpression access_expr, exp)
|
||||||
|
| HilExp.BinaryOperator (Binop.Eq, exp, HilExp.AccessExpression access_expr)
|
||||||
|
| HilExp.UnaryOperator
|
||||||
|
( Unop.LNot
|
||||||
|
, HilExp.BinaryOperator (Binop.Ne, HilExp.AccessExpression access_expr, exp)
|
||||||
|
, _ )
|
||||||
|
| HilExp.UnaryOperator
|
||||||
|
( Unop.LNot
|
||||||
|
, HilExp.BinaryOperator (Binop.Ne, exp, HilExp.AccessExpression access_expr)
|
||||||
|
, _ ) ->
|
||||||
|
Option.some_if (HilExp.is_null_literal exp)
|
||||||
|
(HilExp.AccessExpression.to_access_path access_expr)
|
||||||
|
| _ ->
|
||||||
|
None
|
||||||
|
in
|
||||||
|
match extract_null_compare_expr assume_exp with
|
||||||
|
| Some ap ->
|
||||||
|
ResourceLeakCSDomain.release_resource ap astate
|
||||||
|
| _ ->
|
||||||
|
astate )
|
||||||
|
| Call (_, Indirect _, _, _, _) ->
|
||||||
|
(* This should never happen in Java. Fail if it does. *)
|
||||||
|
L.(die InternalError) "Unexpected indirect call %a" HilInstr.pp instr
|
||||||
|
| Metadata _ ->
|
||||||
|
astate
|
||||||
|
|
||||||
|
|
||||||
|
let pp_session_name _node fmt = F.pp_print_string fmt "resource leaks"
|
||||||
|
end
|
||||||
|
|
||||||
|
(** 5(a) Type of CFG to analyze--Exceptional to follow exceptional control-flow edges, Normal to
|
||||||
|
ignore them *)
|
||||||
|
module CFG = ProcCfg.Normal
|
||||||
|
|
||||||
|
(* Create an intraprocedural abstract interpreter from the transfer functions we defined *)
|
||||||
|
module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions (CFG))
|
||||||
|
|
||||||
|
(** Report an error when we have acquired more resources than we have released *)
|
||||||
|
let report_if_leak {InterproceduralAnalysis.proc_desc; err_log; _} formal_map post =
|
||||||
|
if ResourceLeakCSDomain.has_leak formal_map post then (
|
||||||
|
let last_loc = Procdesc.Node.get_loc (Procdesc.get_exit_node proc_desc) in
|
||||||
|
let message =
|
||||||
|
let concat_types =
|
||||||
|
Hashtbl.iter
|
||||||
|
(fun x y ->
|
||||||
|
if ResourceLeakCSDomain.check_count x post then
|
||||||
|
leak_list := ResourceLeakCSDomain.LeakList.append_one !leak_list y )
|
||||||
|
!type_map
|
||||||
|
in
|
||||||
|
concat_types ;
|
||||||
|
let concat_leak_list = String.concat ~sep:", " !leak_list in
|
||||||
|
F.asprintf "Leaked %a resource(s) at type(s) %s" ResourceLeakCSDomain.pp post concat_leak_list
|
||||||
|
in
|
||||||
|
ResourceLeakCSDomain.reset_type_map ;
|
||||||
|
ResourceLeakCSDomain.Summary.reset_interface_type_map ;
|
||||||
|
leak_list := [] ;
|
||||||
|
Reporting.log_issue proc_desc err_log ~loc:last_loc DOTNETResourceLeaks
|
||||||
|
IssueType.dotnet_resource_leak message )
|
||||||
|
else ResourceLeakCSDomain.reset_type_map ;
|
||||||
|
ResourceLeakCSDomain.Summary.reset_interface_type_map
|
||||||
|
|
||||||
|
|
||||||
|
(* Callback for invoking the checker from the outside--registered in RegisterCheckers *)
|
||||||
|
let checker ({InterproceduralAnalysis.proc_desc} as analysis_data) =
|
||||||
|
let result =
|
||||||
|
Analyzer.compute_post analysis_data ~initial:ResourceLeakCSDomain.initial proc_desc
|
||||||
|
in
|
||||||
|
Option.map result ~f:(fun post ->
|
||||||
|
let formal_map = FormalMap.make proc_desc in
|
||||||
|
report_if_leak analysis_data formal_map post ;
|
||||||
|
ResourceLeakCSDomain.Summary.make formal_map post )
|
@ -0,0 +1,11 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
|
||||||
|
val checker :
|
||||||
|
ResourceLeakCSDomain.summary InterproceduralAnalysis.t -> ResourceLeakCSDomain.summary option
|
@ -0,0 +1,14 @@
|
|||||||
|
; Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
;
|
||||||
|
; This source code is licensed under the MIT license found in the
|
||||||
|
; LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
(library
|
||||||
|
(name Dotnet)
|
||||||
|
(public_name infer.Dotnet)
|
||||||
|
(flags
|
||||||
|
(:standard -open Core -open IR -open IStdlib -open IStd -open ATDGenerated
|
||||||
|
-open IBase -open Absint))
|
||||||
|
(libraries core IStdlib ATDGenerated IBase IR Absint)
|
||||||
|
(preprocess
|
||||||
|
(pps ppx_compare)))
|
@ -0,0 +1,19 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
# Makefiles that include this one must define TESTS_DIR and then include
|
||||||
|
# $(TESTS_DIR)/analyzejson.make.
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/infer.make
|
||||||
|
|
||||||
|
cfg.json: jsons.tar.xz
|
||||||
|
$(QUIET)tar xf $<
|
||||||
|
|
||||||
|
infer-out/report.json: $(MAKEFILE_LIST) cfg.json
|
||||||
|
$(QUIET)$(call silent_on_success,Testing infer/dotnet in $(TEST_REL_DIR),\
|
||||||
|
$(INFER_BIN) capture && \
|
||||||
|
$(INFER_BIN) analyzejson --project-root $(TESTS_DIR) \
|
||||||
|
$(INFER_OPTIONS) && \
|
||||||
|
sed -i -e 's#/app/infernew/infer/tests/##g' $@ )
|
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1,13 @@
|
|||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionANDBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionBitwiseComp1Bad(), 6, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionDivBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionLeftShftBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionMinusBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionModBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionMultBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionORBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionPlusBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionRightShftBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionUnsignedDivBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionUnsignedModBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/nullderef-arithmetic/NullDerefArithmetic.cs, Void TestCodeArithmetic.NullExceptionXORBad(), 8, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1,3 @@
|
|||||||
|
codetoanalyze/dotnet/array/NullDerefArray.cs, Void TestCodeArray.NullExceptionArrayOneDim1Bad(), 5, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/array/NullDerefArray.cs, Void TestCodeArray.NullExceptionArrayOneDim2Bad(), 5, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/array/NullDerefArray.cs, Void TestCodeArray.NullExceptionArrayTwoDimBad(), 5, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1,2 @@
|
|||||||
|
codetoanalyze/dotnet/bgeble/NullDerefBgeBle.cs, Void TestCodeBgeBle.NullExceptionBgeBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/bgeble/NullDerefBgeBle.cs, Void TestCodeBgeBle.NullExceptionBleBad(), 8, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1 @@
|
|||||||
|
codetoanalyze/dotnet/box/NullDerefBox.cs, Void TestCodeBox.NullExceptionBoxIntegersBad(), 5, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1,4 @@
|
|||||||
|
codetoanalyze/dotnet/fieldderef/NullDerefFieldDeref.cs, Void TestCodeFieldDeref.NullExceptionFieldDeref1Bad(), 4, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/fieldderef/NullDerefFieldDeref.cs, Void TestCodeFieldDeref.NullExceptionFieldDeref2Bad(), 4, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/fieldderef/NullDerefFieldDeref.cs, Void TestCodeFieldDeref.NullExceptionStaticDeref1Bad(), 2, NULL_DEREFERENCE, B1
|
||||||
|
codetoanalyze/dotnet/fieldderef/NullDerefFieldDeref.cs, Void TestCodeFieldDeref.NullExceptionStaticDeref2Bad(), 2, NULL_DEREFERENCE, B1
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1,2 @@
|
|||||||
|
codetoanalyze/dotnet/isinst/NullDerefIsInst.cs, Void TestCodeIsInst.NullExceptionIsInst1Bad(), 3, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/isinst/NullDerefIsInst.cs, Void TestCodeIsInst.NullExceptionIsInst2Bad(), 3, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1 @@
|
|||||||
|
codetoanalyze/dotnet/ldstr/NullExceptionLdstr.cs, Void TestCodeLdstr.NullExceptionLdstrBad(), 3, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1,6 @@
|
|||||||
|
codetoanalyze/dotnet/logical/NullExceptionLogical.cs, Void TestCodeLogical.NullExceptionANDBad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/logical/NullExceptionLogical.cs, Void TestCodeLogical.NullExceptionOR1Bad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/logical/NullExceptionLogical.cs, Void TestCodeLogical.NullExceptionOR2Bad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/logical/NullExceptionLogical.cs, Void TestCodeLogical.NullExceptionOR3Bad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/logical/NullExceptionLogical.cs, Void TestCodeLogical.NullExceptionXOR1Bad(), 8, NULL_DEREFERENCE, B5
|
||||||
|
codetoanalyze/dotnet/logical/NullExceptionLogical.cs, Void TestCodeLogical.NullExceptionXOR2Bad(), 8, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1 @@
|
|||||||
|
codetoanalyze/dotnet/nullderef-interproc/NullDerefInterproc.cs, Void TestCodeNullDerefInterproc.NullExceptionInterprocBad(), 3, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1 @@
|
|||||||
|
codetoanalyze/dotnet/nullderef-simple/NullExceptionSimple.cs, Void TestCodeSimple.NullExceptionSimpleBad(), 3, NULL_DEREFERENCE, B5
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = \
|
||||||
|
--debug --cfg-json cfg.json \
|
||||||
|
--tenv-json tenv.json \
|
||||||
|
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests-fields "file,procedure,line_offset,bug_type,bucket" --issues-tests
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/analyzejson.make
|
@ -0,0 +1 @@
|
|||||||
|
codetoanalyze/dotnet/nullparam/NullExceptionNullParam.cs, Void TestCodeNullParam.NullExceptionNullParamBad(), 3, NULL_DEREFERENCE, B5
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue