-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Infer# integration #1361
Infer# integration #1361
Changes from 19 commits
661ef65
cd1e6fc
3fa4160
a5c023b
d9b00ca
0e4489a
9374648
b5a8d6d
545c78b
cc87294
4f4acf6
4e86aa8
280a5fb
ebf8e88
f0b6e33
0ec94eb
71914ae
a0e0c28
01b1037
a67918c
7047a71
8848fdb
6986656
f3b95de
db3ada8
4a001b4
b2f300a
77270e2
4270215
de588c5
6ca305a
0145560
d0945ba
56d910e
c35f6c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
(* | ||
* 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] | ||
|
||
module Map = Caml.Map.Make (struct | ||
type nonrec t = t [@@deriving compare] | ||
end) | ||
|
||
module Set = Caml.Set.Make (struct | ||
type nonrec t = t [@@deriving compare] | ||
end) | ||
|
||
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 namespace {namespace} = namespace | ||
|
||
let classname {classname} = classname | ||
|
||
let is_int s = | ||
try | ||
ignore (int_of_string s) ; | ||
true | ||
with Failure _ -> false | ||
|
||
|
||
let get_outer_class_name {namespace; classname} = | ||
String.rsplit2 classname ~on:'$' |> Option.map ~f:(fun (outer, _) -> {namespace; classname= outer}) | ||
|
||
|
||
(* | ||
Anonymous classes have two forms: | ||
- classic anonymous classes: suffixes in form of $<int>. | ||
- classes corresponding to lambda-expressions: they are manifested as $Lambda$. | ||
- two forms above nested inside each other. | ||
Also non-anonymous (user-defined) name can be nested as well (Class$NestedClass). | ||
In general case anonymous class name looks something like | ||
Class$NestedClass$1$17$5$Lambda$_1_2, and we need to return Class$NestedClass *) | ||
let get_user_defined_class_if_anonymous_inner {namespace; classname} = | ||
let is_anonymous_name = function | ||
| "Lambda" -> | ||
true | ||
| name when is_int name -> | ||
true | ||
| _ -> | ||
false | ||
in | ||
let pieces = String.split classname ~on:'$' in | ||
let first_anonymous_name = List.findi pieces ~f:(fun _index name -> is_anonymous_name name) in | ||
Option.bind first_anonymous_name ~f:(fun (index, _name) -> | ||
(* Everything before this index did not have anonymous prefixes. Deem it user defined. *) | ||
match List.take pieces index with | ||
| [] -> | ||
(* This is a weird situation - our class _starts_ with an anonymous name. | ||
This should not happen normally, but we can not rule this out completely since there is no physical limitations on | ||
the bytecode names. | ||
In this case, we return [None] because formally this is not an anonymous _inner_ class, but anonymous outermost class instead. | ||
TODO: redesign this API so this case is modelled directly | ||
*) | ||
None | ||
| list -> | ||
(* Assemble back all pieces together *) | ||
Some {namespace; classname= String.concat ~sep:"$" list} ) | ||
|
||
|
||
let is_anonymous_inner_class_name t = get_user_defined_class_if_anonymous_inner t |> is_some | ||
|
||
let is_external_via_config t = | ||
let namespace = namespace t in | ||
Option.exists ~f:Config.csharp_namespace_is_external namespace | ||
|
||
|
||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
(* | ||
* 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] | ||
|
||
module Map : Caml.Map.S with type key = t | ||
|
||
module Set : Caml.Set.S with type elt = t | ||
|
||
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 namespace : t -> string option | ||
|
||
val classname : t -> string | ||
|
||
val is_external_via_config : t -> bool | ||
(** Considered external based on config flags. *) | ||
|
||
val get_outer_class_name : t -> t option | ||
(** If this is an inner class, return the closest outer, e.g. A$B for A$B$C. None if the class is | ||
outermost *) | ||
|
||
val is_anonymous_inner_class_name : t -> bool | ||
(** True if it is either "classic" anonymous csharp class: *) | ||
|
||
val get_user_defined_class_if_anonymous_inner : t -> t option | ||
(** If the current class is anonymous ([is_anonymous_inner_class_name] is true), return the | ||
corresponding user defined (not anonymous) class this anonymous class belongs to. | ||
|
||
In general case, BOTH anonymous classes and user-defined classes can be nested: | ||
SomeClass$NestedClass$1$17$5. In this example, we should return SomeClass$NestedClass. | ||
|
||
If this is not an anonymous class, returns [None]. *) | ||
|
||
module Normalizer : HashNormalizer.S with type t = t |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,17 +17,19 @@ type t = | |
; cf_injected_destructor: bool | ||
; cf_interface: bool | ||
; cf_is_objc_block: bool | ||
; cf_noreturn: bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was deleted by 9979489 because it is dead code. It looks like the flag has been reintroduced by mistake in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Removed. Thanks |
||
; cf_virtual: bool } | ||
[@@deriving compare] | ||
|
||
let pp f | ||
({cf_assign_last_arg; cf_injected_destructor; cf_interface; cf_is_objc_block; cf_virtual}[@warning | ||
({cf_assign_last_arg; cf_injected_destructor; cf_interface; cf_is_objc_block; cf_noreturn; cf_virtual}[@warning | ||
"+9"]) | ||
= | ||
if cf_assign_last_arg then F.pp_print_string f " assign_last" ; | ||
if cf_injected_destructor then F.pp_print_string f " injected" ; | ||
if cf_interface then F.pp_print_string f " interface" ; | ||
if cf_is_objc_block then F.pp_print_string f " objc_block" ; | ||
if cf_noreturn then F.pp_print_string f " noreturn" ; | ||
if cf_virtual then F.pp_print_string f " virtual" ; | ||
() | ||
|
||
|
@@ -37,4 +39,5 @@ let default = | |
; cf_injected_destructor= false | ||
; cf_interface= false | ||
; cf_is_objc_block= false | ||
; cf_noreturn= false | ||
; cf_virtual= false } |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,6 @@ let iter_over_sorted_procs cfg ~f = | |
|> List.sort ~compare:compare_proc_desc_by_proc_name | ||
|> List.iter ~f | ||
|
||
|
||
let get_all_defined_proc_names cfg = | ||
let procs = ref [] in | ||
let f pname pdesc = if Procdesc.is_defined pdesc then procs := pname :: !procs in | ||
|
@@ -40,6 +39,14 @@ let create_proc_desc cfg (proc_attributes : ProcAttributes.t) = | |
Procname.Hash.add cfg pname pdesc ; | ||
pdesc | ||
|
||
(** Get a procdesc or create if it is a new one*) | ||
let get_proc_desc cfg (proc_attributes : ProcAttributes.t) = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks a bit dangerous to add to the Cfg module and I'm not sure I understand why it's needed: what happens if the proc name is already present? The proc desc will get created but not recorded in the CFG, so in the end it's discarded? This is only used once, could you maybe rewrite the call site to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good suggestion. I have rewritten this call. |
||
let pdesc = Procdesc.from_proc_attributes proc_attributes in | ||
let pname = proc_attributes.proc_name in | ||
if not (Procname.Hash.mem cfg pname) then | ||
Procname.Hash.add cfg pname pdesc ; | ||
pdesc | ||
|
||
|
||
let iter_sorted cfg ~f = iter_over_sorted_procs cfg ~f | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,6 +187,8 @@ module Node : sig | |
val pp_stmt : Format.formatter -> stmt_nodekind -> unit | ||
|
||
val compute_key : t -> NodeKey.t | ||
|
||
val print_node : t -> unit | ||
end | ||
|
||
(** Map with node id keys. *) | ||
|
@@ -315,6 +317,8 @@ val fold_nodes : t -> init:'accum -> f:('accum -> Node.t -> 'accum) -> 'accum | |
val fold_slope_range : Node.t -> Node.t -> init:'accum -> f:('accum -> Node.t -> 'accum) -> 'accum | ||
(** fold between two nodes or until we reach a branching structure *) | ||
|
||
val set_succs_exn_base : Node.t -> Node.t list -> Node.t list -> unit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use If this is needed could you add a doc string explaining that it should be used carefully because it won't update the node's successors' pred links? (not sure why that is ok except when the current node is known to have no succs, but then this is the same as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It was an outdated function. I have replaced it with |
||
|
||
val set_succs : Node.t -> normal:Node.t list option -> exn:Node.t list option -> unit | ||
(** Set the successor nodes and exception nodes, if given, and update predecessor links *) | ||
|
||
|
@@ -351,3 +355,5 @@ val shallow_copy_code_from_pdesc : orig_pdesc:t -> dest_pdesc:t -> unit | |
module SQLite : SqliteUtils.Data with type t = t option | ||
|
||
val load : Procname.t -> t option | ||
|
||
val print_pdesc_nodes : t -> unit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is sqlite3 itself used anywhere yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got build error in this ocamlformat branch. I will try again after they fixed the pipeline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed. Good catch!