From 4e821129a49e62878fdc541effac9f5ab121694e Mon Sep 17 00:00:00 2001
From: Daiva Naudziuniene <daivan@fb.com>
Date: Mon, 14 May 2018 06:20:39 -0700
Subject: [PATCH] [cfg] Log if the cfg is broken for a join node or not

Reviewed By: dulmarod

Differential Revision: D7985793

fbshipit-source-id: 4010954
---
 infer/src/IR/Cfg.ml               | 33 +++++++++++++++++++++++--------
 infer/src/IR/Cfg.mli              |  2 +-
 infer/src/clang/ClangLogging.ml   |  7 +++++--
 infer/src/clang/ClangLogging.mli  |  3 ++-
 infer/src/clang/cFrontend_decl.ml | 16 +++++++++------
 5 files changed, 43 insertions(+), 18 deletions(-)

diff --git a/infer/src/IR/Cfg.ml b/infer/src/IR/Cfg.ml
index d4333002d..eb5021fc5 100644
--- a/infer/src/IR/Cfg.ml
+++ b/infer/src/IR/Cfg.ml
@@ -53,7 +53,7 @@ let iter_all_nodes ?(sorted= false) cfg ~f =
     |> List.iter ~f:(fun (_, d, n) -> f d n)
 
 
-let is_proc_cfg_connected proc_desc =
+let proc_cfg_broken_for_node proc_desc =
   let is_exit_node n =
     match Procdesc.Node.get_kind n with Procdesc.Node.Exit_node _ -> true | _ -> false
   in
@@ -79,26 +79,43 @@ let is_proc_cfg_connected proc_desc =
     | _ ->
         is_between_join_and_exit_node n
   in
-  let is_broken_node n =
+  let find_broken_node n =
     let succs = Procdesc.Node.get_succs n in
     let preds = Procdesc.Node.get_preds n in
     match Procdesc.Node.get_kind n with
     | Procdesc.Node.Start_node _ ->
-        List.is_empty succs || not (List.is_empty preds)
+        if List.is_empty succs || not (List.is_empty preds) then Some `Other else None
     | Procdesc.Node.Exit_node _ ->
-        not (List.is_empty succs) || List.is_empty preds
+        if not (List.is_empty succs) || List.is_empty preds then Some `Other else None
     | Procdesc.Node.Stmt_node _ | Procdesc.Node.Prune_node _ | Procdesc.Node.Skip_node _ ->
-        List.is_empty succs || List.is_empty preds
+        if List.is_empty succs || List.is_empty preds then Some `Other else None
     | Procdesc.Node.Join_node ->
         (* Join node has the exception that it may be without predecessors
          and pointing to between_join_and_exit which points to an exit node.
          This happens when the if branches end with a return.
          Nested if statements, where all branches have return statements,
          introduce a sequence of join nodes *)
-        (List.is_empty preds && not (is_consecutive_join_nodes n Procdesc.NodeSet.empty))
-        || (not (List.is_empty preds) && List.is_empty succs)
+        if
+          (List.is_empty preds && not (is_consecutive_join_nodes n Procdesc.NodeSet.empty))
+          || (not (List.is_empty preds) && List.is_empty succs)
+        then Some `Join
+        else None
   in
-  not (List.exists ~f:is_broken_node (Procdesc.get_nodes proc_desc))
+  let rec find_first_broken nodes res_broken_node =
+    match nodes with
+    | [] ->
+        res_broken_node
+    | n :: nodes' ->
+        let broken_node = find_broken_node n in
+        match broken_node with
+        | None ->
+            find_first_broken nodes' res_broken_node
+        | Some `Join ->
+            find_first_broken nodes' broken_node
+        | Some `Other ->
+            broken_node
+  in
+  find_first_broken (Procdesc.get_nodes proc_desc) None
 
 
 let load_statement =
diff --git a/infer/src/IR/Cfg.mli b/infer/src/IR/Cfg.mli
index 0b8216e95..4d1781676 100644
--- a/infer/src/IR/Cfg.mli
+++ b/infer/src/IR/Cfg.mli
@@ -32,7 +32,7 @@ val create_proc_desc : t -> ProcAttributes.t -> Procdesc.t
 val iter_all_nodes : ?sorted:bool -> t -> f:(Procdesc.t -> Procdesc.Node.t -> unit) -> unit
 (** Iterate over all the nodes in the cfg *)
 
-val is_proc_cfg_connected : Procdesc.t -> bool
+val proc_cfg_broken_for_node : Procdesc.t -> [`Join | `Other] option
 (** checks whether a cfg for the given procdesc is connected or not *)
 
 val save_attributes : SourceFile.t -> t -> unit
diff --git a/infer/src/clang/ClangLogging.ml b/infer/src/clang/ClangLogging.ml
index 137662d36..310e93efa 100644
--- a/infer/src/clang/ClangLogging.ml
+++ b/infer/src/clang/ClangLogging.ml
@@ -22,15 +22,18 @@ let log_caught_exception (trans_unit_ctx: CFrontend_config.translation_unit_cont
   EventLogger.log caught_exception
 
 
-let log_broken_cfg procdesc exception_triggered_location ~lang =
+let log_broken_cfg ~broken_node procdesc exception_triggered_location ~lang =
   let proc_location = Procdesc.get_loc procdesc in
+  let exception_type =
+    match broken_node with `Other -> "Broken CFG" | `Join -> "Broken CFG at join node"
+  in
   let cfg_exception =
     EventLogger.FrontendException
       { source_location_start= proc_location
       ; source_location_end= proc_location
       ; ast_node= None
       ; exception_triggered_location
-      ; exception_type= "Broken CFG"
+      ; exception_type
       ; lang }
   in
   EventLogger.log cfg_exception
diff --git a/infer/src/clang/ClangLogging.mli b/infer/src/clang/ClangLogging.mli
index 90774b49d..6ab4387de 100644
--- a/infer/src/clang/ClangLogging.mli
+++ b/infer/src/clang/ClangLogging.mli
@@ -13,4 +13,5 @@ val log_caught_exception :
   CFrontend_config.translation_unit_context -> string -> Logging.ocaml_pos
   -> Clang_ast_t.source_location * Clang_ast_t.source_location -> string option -> unit
 
-val log_broken_cfg : Procdesc.t -> Logging.ocaml_pos -> lang:string -> unit
+val log_broken_cfg :
+  broken_node:[`Join | `Other] -> Procdesc.t -> Logging.ocaml_pos -> lang:string -> unit
diff --git a/infer/src/clang/cFrontend_decl.ml b/infer/src/clang/cFrontend_decl.ml
index 559f8256f..db067f88f 100644
--- a/infer/src/clang/cFrontend_decl.ml
+++ b/infer/src/clang/cFrontend_decl.ml
@@ -62,7 +62,8 @@ module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFron
     in
     let f () =
       match Typ.Procname.Hash.find cfg procname with
-      | procdesc when Procdesc.is_defined procdesc && not (model_exists procname) ->
+      | procdesc when Procdesc.is_defined procdesc && not (model_exists procname)
+        -> (
           let vars_to_destroy = CTrans_utils.Scope.compute_vars_to_destroy body in
           let context =
             CContext.create_context trans_unit_ctx tenv cfg procdesc class_decl_opt
@@ -80,11 +81,14 @@ module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFron
           Procdesc.Node.add_locals_ret_declaration start_node proc_attributes
             (Procdesc.get_locals procdesc) ;
           Procdesc.node_set_succs_exn procdesc start_node meth_body_nodes [] ;
-          if not (Cfg.is_proc_cfg_connected procdesc) then
-            let lang =
-              CFrontend_config.string_of_clang_lang trans_unit_ctx.CFrontend_config.lang
-            in
-            ClangLogging.log_broken_cfg procdesc __POS__ ~lang
+          match Cfg.proc_cfg_broken_for_node procdesc with
+          | None ->
+              ()
+          | Some broken_node ->
+              let lang =
+                CFrontend_config.string_of_clang_lang trans_unit_ctx.CFrontend_config.lang
+              in
+              ClangLogging.log_broken_cfg ~broken_node procdesc __POS__ ~lang )
       | _ ->
           ()
       | exception Caml.Not_found ->