[AL] Refactor CTL formulas and debugger into separate modules

Summary: The debugger in the middle of the evaluation makes working on that file difficult. Separating modules a bit, so that we can change the code easier. This is in preparation to trying to add aliases and witnesses to formulas in a next diff.

Reviewed By: jvillard

Differential Revision: D18708542

fbshipit-source-id: 523f30fc9
master
Dulma Churchill 5 years ago committed by Facebook Github Bot
parent c3e16dbdbc
commit 75794301dc

@ -296,7 +296,7 @@ and do_frontend_checks_decl linters (context : CLintersContext.context) decl =
ALIssues.invoke_set_of_checkers_on_node linters context' an ; ALIssues.invoke_set_of_checkers_on_node linters context' an ;
(* We need to visit explicitly nodes reachable via Parameters transitions (* We need to visit explicitly nodes reachable via Parameters transitions
because they won't be visited during the evaluation of the formula *) because they won't be visited during the evaluation of the formula *)
do_frontend_checks_via_transition linters context' an CTL.Parameters ; do_frontend_checks_via_transition linters context' an CTLTypes.Parameters ;
match CAst_utils.get_method_body_opt decl with match CAst_utils.get_method_body_opt decl with
| Some stmt -> | Some stmt ->
do_frontend_checks_stmt linters context' stmt do_frontend_checks_stmt linters context' stmt

@ -0,0 +1,340 @@
(*
* 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 L = Logging
let pp_ast ~ast_node_to_highlight ?(prettifier = Fn.id) fmt root =
let open Ctl_parser_types in
let pp_node_info fmt an =
let name = Ctl_parser_types.ast_node_name an in
let typ = Ctl_parser_types.ast_node_type an in
let cast_kind = Ctl_parser_types.ast_node_cast_kind an in
Format.fprintf fmt " %s %s %s" name typ cast_kind
in
let rec pp_children pp_node wrapper fmt level nodes =
match nodes with
| [] ->
()
| node :: nodes ->
pp_node fmt (wrapper node) level "|-" ;
pp_children pp_node wrapper fmt level nodes
in
let rec pp_ast_aux fmt root level prefix =
let get_node_name (an : ast_node) =
match an with
| Stmt stmt ->
Clang_ast_proj.get_stmt_kind_string stmt
| Decl decl ->
Clang_ast_proj.get_decl_kind_string decl
in
let should_highlight =
match (root, ast_node_to_highlight) with
| Stmt r, Stmt n ->
phys_equal r n
| Decl r, Decl n ->
phys_equal r n
| _ ->
false
in
let node_name =
let node_name = get_node_name root in
if should_highlight then prettifier node_name else node_name
in
let spaces = String.make (level * String.length prefix) ' ' in
let next_level = level + 1 in
Format.fprintf fmt "%s%s%s %a@\n" spaces prefix node_name pp_node_info root ;
match root with
| Stmt (DeclStmt (_, stmts, ([VarDecl _] as var_decl))) ->
(* handling special case of DeclStmt with VarDecl: emit the VarDecl node
then emit the statements in DeclStmt as children of VarDecl. This is
because despite being equal, the statements inside VarDecl and those
inside DeclStmt belong to different instances, hence they fail the
phys_equal check that should colour them *)
pp_children pp_ast_aux (fun n -> Decl n) fmt next_level var_decl ;
pp_stmts fmt (next_level + 1) stmts
| Stmt stmt ->
let _, stmts = Clang_ast_proj.get_stmt_tuple stmt in
pp_stmts fmt next_level stmts
| Decl decl ->
let decls =
Clang_ast_proj.get_decl_context_tuple decl
|> Option.map ~f:(fun (decls, _) -> decls)
|> Option.value ~default:[]
in
pp_decls fmt next_level decls
and pp_stmts fmt level stmts = pp_children pp_ast_aux (fun n -> Stmt n) fmt level stmts
and pp_decls fmt level decls = pp_children pp_ast_aux (fun n -> Decl n) fmt level decls in
pp_ast_aux fmt root 0 ""
module EvaluationTracker = struct
exception Empty_stack of string
type eval_result = Eval_undefined | Eval_true | Eval_false
type content =
{ ast_node: Ctl_parser_types.ast_node
; phi: CTLTypes.t
; lcxt: CLintersContext.context
; eval_result: eval_result
; witness: Ctl_parser_types.ast_node option }
type eval_node = {id: int; content: content}
type tree = Tree of eval_node * tree list
type ast_node_to_display =
(* the node can be used to describe further sub calls in the evaluation stack *)
| Carry_forward of Ctl_parser_types.ast_node
(* the node cannot be further used to describe sub calls in the evaluation stack *)
| Last_occurrence of Ctl_parser_types.ast_node
type t =
{ next_id: int
; eval_stack: (tree * ast_node_to_display) Stack.t
; forest: tree list
; breakpoint_line: int option
; debugger_active: bool }
let create_content ast_node phi lcxt =
{ast_node; phi; eval_result= Eval_undefined; lcxt; witness= None}
let create source_file =
let breakpoint_token = "INFER_BREAKPOINT" in
let breakpoint_line =
In_channel.read_lines (SourceFile.to_abs_path source_file)
|> List.findi ~f:(fun _ line -> String.is_substring line ~substring:breakpoint_token)
|> Option.map ~f:(fun (i, _) -> i + 1)
in
{next_id= 0; eval_stack= Stack.create (); forest= []; breakpoint_line; debugger_active= false}
let explain t ~eval_node ~ast_node_to_display =
let open Ctl_parser_types in
let line_number an =
let line_of_source_range (sr : Clang_ast_t.source_range) =
let loc_info, _ = sr in
loc_info.sl_line
in
match an with
| Stmt stmt ->
let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in
line_of_source_range stmt_info.si_source_range
| Decl decl ->
let decl_info = Clang_ast_proj.get_decl_tuple decl in
line_of_source_range decl_info.di_source_range
in
let stop_and_explain_step () =
let highlight_style =
match eval_node.content.eval_result with
| Eval_undefined ->
ANSITerminal.[Bold]
| Eval_true ->
ANSITerminal.[Bold; green]
| Eval_false ->
ANSITerminal.[Bold; red]
in
let ast_node_to_highlight = eval_node.content.ast_node in
let ast_root, is_last_occurrence =
match ast_node_to_display with
| Carry_forward n ->
(n, false)
| Last_occurrence n ->
(n, true)
in
let witness_str =
match eval_node.content.witness with
| Some witness ->
"\n- witness: "
^ Ctl_parser_types.ast_node_kind witness
^ " "
^ Ctl_parser_types.ast_node_name witness
| None ->
""
in
let ast_str =
Format.asprintf "%a %s"
(pp_ast ~ast_node_to_highlight ~prettifier:(ANSITerminal.sprintf highlight_style "%s"))
ast_root witness_str
in
L.progress "@\nNode ID: %d\tEvaluation stack level: %d\tSource line-number: %s@\n"
eval_node.id (Stack.length t.eval_stack)
(Option.value_map ~default:"Unknown" ~f:string_of_int (line_number ast_node_to_highlight)) ;
let is_eval_result_undefined =
match eval_node.content.eval_result with Eval_undefined -> true | _ -> false
in
if is_last_occurrence && is_eval_result_undefined then
L.progress "From this step, a transition to a different part of the AST may follow.@\n" ;
let phi_str = Format.asprintf "%a" CTLTypes.pp_formula eval_node.content.phi in
L.progress "CTL Formula: %s@\n@\n" phi_str ;
L.progress "%s@\n" ast_str ;
let quit_token = "q" in
L.progress "Press Enter to continue or type %s to quit... @?" quit_token ;
match In_channel.input_line_exn In_channel.stdin |> String.lowercase with
| s when String.equal s quit_token ->
L.exit 0
| _ ->
(* Remove the line at the bottom of terminal with the debug instructions *)
let open ANSITerminal in
(* move one line up, as current line is the one generated by pressing enter *)
move_cursor 0 (-1) ;
move_bol () ;
(* move to the beginning of the line *)
erase Below
(* erase what follows the cursor's position *)
in
match (t.debugger_active, t.breakpoint_line, line_number eval_node.content.ast_node) with
| false, Some break_point_ln, Some ln when ln >= break_point_ln ->
L.progress "Attaching debugger at line %d" ln ;
stop_and_explain_step () ;
{t with debugger_active= true}
| true, _, _ ->
stop_and_explain_step () ; t
| _ ->
t
let eval_begin t content =
let node = {id= t.next_id; content} in
let create_subtree root = Tree (root, []) in
let subtree' = create_subtree node in
let ast_node_from_previous_call =
match Stack.top t.eval_stack with
| Some (_, Last_occurrence _) ->
content.ast_node
| Some (_, Carry_forward an) ->
an
| None ->
content.ast_node
in
let ast_node_to_display =
if CTLTypes.has_transition content.phi then Last_occurrence ast_node_from_previous_call
else Carry_forward ast_node_from_previous_call
in
Stack.push t.eval_stack (subtree', ast_node_to_display) ;
let t' = explain t ~eval_node:node ~ast_node_to_display in
{t' with next_id= t.next_id + 1}
let eval_end t result =
let result_bool = Option.is_some result in
let eval_result_of_bool = function true -> Eval_true | false -> Eval_false in
if Stack.is_empty t.eval_stack then
raise (Empty_stack "Unbalanced number of eval_begin/eval_end invocations") ;
let evaluated_tree, eval_node, ast_node_to_display =
match Stack.pop_exn t.eval_stack with
| Tree (({id= _; content} as eval_node), children), ast_node_to_display ->
let content' =
{content with eval_result= eval_result_of_bool result_bool; witness= result}
in
let eval_node' = {eval_node with content= content'} in
(Tree (eval_node', children), eval_node', ast_node_to_display)
in
let t' = explain t ~eval_node ~ast_node_to_display in
let forest' =
if Stack.is_empty t'.eval_stack then evaluated_tree :: t'.forest
else
let parent =
match Stack.pop_exn t'.eval_stack with
| Tree (node, children), ntd ->
(Tree (node, evaluated_tree :: children), ntd)
in
Stack.push t'.eval_stack parent ; t'.forest
in
{t' with forest= forest'}
let equal_ast_node = Poly.equal
module DottyPrinter = struct
let dotty_of_ctl_evaluation t =
let open CTLTypes in
let open Ctl_parser_types in
let buffer_content buf =
let result = Buffer.contents buf in
Buffer.reset buf ; result
in
let dotty_of_tree cluster_id tree =
let get_root tree = match tree with Tree (root, _) -> root in
let get_children tree = match tree with Tree (_, children) -> List.rev children in
(* shallow: emit dotty about root node and edges to its children *)
let shallow_dotty_of_tree tree =
let root_node = get_root tree in
let children = get_children tree in
let edge child_node =
if equal_ast_node root_node.content.ast_node child_node.content.ast_node then
Printf.sprintf "%d -> %d [style=dotted]" root_node.id child_node.id
else Printf.sprintf "%d -> %d [style=bold]" root_node.id child_node.id
in
let color =
match root_node.content.eval_result with
| Eval_true ->
"green"
| Eval_false ->
"red"
| _ ->
L.(die InternalError) "Tree is not fully evaluated"
in
let label =
let string_of_lcxt c =
match c.CLintersContext.et_evaluation_node with
| Some s ->
"et_evaluation_node = " ^ s
| _ ->
"et_evaluation_node = NONE"
in
let string_of_ast_node an =
match an with
| Stmt stmt ->
Clang_ast_proj.get_stmt_kind_string stmt
| Decl decl ->
Clang_ast_proj.get_decl_kind_string decl
in
let smart_string_of_formula phi =
let num_children = List.length children in
match phi with
| And _ when Int.equal num_children 2 ->
"(...) AND (...)"
| Or _ when Int.equal num_children 2 ->
"(...) OR (...)"
| Implies _ when Int.equal num_children 2 ->
"(...) ==> (...)"
| Not _ ->
"NOT(...)"
| _ ->
Format.asprintf "%a" CTLTypes.pp_formula phi
in
Format.sprintf "(%d)\\n%s\\n%s\\n%s" root_node.id
(Escape.escape_dotty (string_of_ast_node root_node.content.ast_node))
(Escape.escape_dotty (string_of_lcxt root_node.content.lcxt))
(Escape.escape_dotty (smart_string_of_formula root_node.content.phi))
in
let edges =
let buf = Buffer.create 16 in
List.iter
~f:(fun subtree -> Buffer.add_string buf (edge (get_root subtree) ^ "\n"))
children ;
buffer_content buf
in
Printf.sprintf "%d [label=\"%s\" shape=box color=%s]\n%s\n" root_node.id label color edges
in
let rec traverse buf tree =
Buffer.add_string buf (shallow_dotty_of_tree tree) ;
List.iter ~f:(traverse buf) (get_children tree)
in
let buf = Buffer.create 16 in
traverse buf tree ;
Printf.sprintf "subgraph cluster_%d {\n%s\n}" cluster_id (buffer_content buf)
in
let buf = Buffer.create 16 in
List.iteri
~f:(fun cluster_id tree -> Buffer.add_string buf (dotty_of_tree cluster_id tree ^ "\n"))
(List.rev t.forest) ;
Printf.sprintf "digraph CTL_Evaluation {\n%s\n}\n" (buffer_content buf)
end
end

@ -0,0 +1,48 @@
(*
* 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 EvaluationTracker : sig
type eval_result = Eval_undefined | Eval_true | Eval_false
type content =
{ ast_node: Ctl_parser_types.ast_node
; phi: CTLTypes.t
; lcxt: CLintersContext.context
; eval_result: eval_result
; witness: Ctl_parser_types.ast_node option }
type eval_node = {id: int; content: content}
type tree = Tree of eval_node * tree list
type ast_node_to_display =
(* the node can be used to describe further sub calls in the evaluation stack *)
| Carry_forward of Ctl_parser_types.ast_node
(* the node cannot be further used to describe sub calls in the evaluation stack *)
| Last_occurrence of Ctl_parser_types.ast_node
type t =
{ next_id: int
; eval_stack: (tree * ast_node_to_display) Stack.t
; forest: tree list
; breakpoint_line: int option
; debugger_active: bool }
val create : SourceFile.t -> t
val create_content : Ctl_parser_types.ast_node -> CTLTypes.t -> CLintersContext.context -> content
val eval_begin : t -> content -> t
val eval_end : t -> Ctl_parser_types.ast_node option -> t
module DottyPrinter : sig
val dotty_of_ctl_evaluation : t -> string
end
end

@ -11,7 +11,7 @@ module L = Logging
module MF = MarkupFormatter module MF = MarkupFormatter
type linter = type linter =
{ condition: CTL.t { condition: CTLTypes.t
; issue_desc: CIssue.issue_desc ; issue_desc: CIssue.issue_desc
; whitelist_paths: ALVar.t list ; whitelist_paths: ALVar.t list
; blacklist_paths: ALVar.t list } ; blacklist_paths: ALVar.t list }
@ -66,7 +66,7 @@ let pp_linters fmt linters =
(* Map a formula id to a triple (visited, parameters, definition). (* Map a formula id to a triple (visited, parameters, definition).
Visited is used during the expansion phase to understand if the Visited is used during the expansion phase to understand if the
formula was already expanded and, if yes we have a cyclic definifion *) formula was already expanded and, if yes we have a cyclic definifion *)
type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t type macros_map = (bool * ALVar.t list * CTLTypes.t) ALVar.FormulaIdMap.t
(* Map a path name to a list of paths. *) (* Map a path name to a list of paths. *)
type paths_map = ALVar.t list ALVar.VarMap.t type paths_map = ALVar.t list ALVar.VarMap.t
@ -249,11 +249,12 @@ let create_parsed_linters linters_def_file checkers : linter list =
| _ -> | _ ->
(issue, cond, wl_paths, bl_paths) (issue, cond, wl_paths, bl_paths)
in in
List.fold ~f:process_linter_definitions ~init:(dummy_issue, CTL.False, [], []) List.fold ~f:process_linter_definitions
~init:(dummy_issue, CTLTypes.False, [], [])
checker.definitions checker.definitions
in in
L.(debug Linters Medium) "@\nMaking condition and issue desc for checker '%s'@\n" checker.id ; L.(debug Linters Medium) "@\nMaking condition and issue desc for checker '%s'@\n" checker.id ;
L.(debug Linters Medium) "@\nCondition =@\n %a@\n" CTL.Debug.pp_formula condition ; L.(debug Linters Medium) "@\nCondition =@\n %a@\n" CTLTypes.pp_formula condition ;
let issue_type = let issue_type =
let doc_url = let doc_url =
Option.first_some Option.first_some
@ -276,7 +277,7 @@ let rec apply_substitution f sub =
with Not_found_s _ | Caml.Not_found -> p with Not_found_s _ | Caml.Not_found -> p
in in
let sub_list_param ps = List.map ps ~f:sub_param in let sub_list_param ps = List.map ps ~f:sub_param in
let open CTL in let open CTLTypes in
match f with match f with
| True | False -> | True | False ->
f f
@ -320,7 +321,7 @@ let expand_formula phi map_ error_msg_ =
let fail_with_circular_macro_definition name error_msg = let fail_with_circular_macro_definition name error_msg =
L.(die ExternalError) "Macro '%s' has a circular definition.@\n Cycle:@\n%s" name error_msg L.(die ExternalError) "Macro '%s' has a circular definition.@\n Cycle:@\n%s" name error_msg
in in
let open CTL in let open CTLTypes in
let rec expand acc map error_msg = let rec expand acc map error_msg =
match acc with match acc with
| True | False -> | True | False ->

@ -10,7 +10,7 @@ open! IStd
val issue_log : IssueLog.t ref val issue_log : IssueLog.t ref
type linter = type linter =
{ condition: CTL.t { condition: CTLTypes.t
; issue_desc: CIssue.issue_desc ; issue_desc: CIssue.issue_desc
; whitelist_paths: ALVar.t list ; whitelist_paths: ALVar.t list
; blacklist_paths: ALVar.t list } ; blacklist_paths: ALVar.t list }
@ -23,7 +23,7 @@ val pp_linters : Format.formatter -> linter list -> unit
(visited, parameters, definition). (visited, parameters, definition).
Visited is used during the expansion phase to understand if the Visited is used during the expansion phase to understand if the
formula was already expanded and, if yes we have a cyclic definifion *) formula was already expanded and, if yes we have a cyclic definifion *)
type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t type macros_map = (bool * ALVar.t list * CTLTypes.t) ALVar.FormulaIdMap.t
(** Map a path name to a list of paths. *) (** Map a path name to a list of paths. *)
type paths_map = ALVar.t list ALVar.VarMap.t type paths_map = ALVar.t list ALVar.VarMap.t

@ -44,7 +44,7 @@ let tag_name_of_node an =
let decl_ref_or_selector_name an = let decl_ref_or_selector_name an =
match CTL.next_state_via_transition an CTL.PointerToDecl with match CTL.next_state_via_transition an CTLTypes.PointerToDecl with
| [(Ctl_parser_types.Decl (ObjCMethodDecl _) as decl_an)] -> | [(Ctl_parser_types.Decl (ObjCMethodDecl _) as decl_an)] ->
"The selector " ^ Ctl_parser_types.ast_node_name decl_an "The selector " ^ Ctl_parser_types.ast_node_name decl_an
| [(Ctl_parser_types.Decl _ as decl_an)] -> | [(Ctl_parser_types.Decl _ as decl_an)] ->
@ -62,7 +62,7 @@ let iphoneos_target_sdk_version context _ =
let available_ios_sdk an = let available_ios_sdk an =
let open Ctl_parser_types in let open Ctl_parser_types in
match CTL.next_state_via_transition an CTL.PointerToDecl with match CTL.next_state_via_transition an CTLTypes.PointerToDecl with
| [Decl decl] -> ( | [Decl decl] -> (
match CPredicates.get_available_attr_ios_sdk (Decl decl) with match CPredicates.get_available_attr_ios_sdk (Decl decl) with
| Some version -> | Some version ->

@ -14,86 +14,6 @@ module L = Logging
CTL formula which express a condition saying when the checker should CTL formula which express a condition saying when the checker should
report a problem *) report a problem *)
(** Transition labels used for example to switch from decl to stmt *)
type transitions =
| AccessorForProperty of ALVar.alexp (** decl to decl *)
| Body (** decl to stmt *)
| FieldName of ALVar.alexp (** stmt to stmt, decl to decl *)
| Fields (** stmt to stmt, decl to decl *)
| InitExpr (** decl to stmt *)
| Super (** decl to decl *)
| ParameterName of ALVar.alexp (** stmt to stmt, decl to decl *)
| ParameterPos of ALVar.alexp (** stmt to stmt, decl to decl *)
| Parameters (** stmt to stmt, decl to decl *)
| Cond
| PointerToDecl (** stmt to decl *)
| Protocol (** decl to decl *)
| Sibling (** decl to decl *)
| SourceExpr
[@@deriving compare]
let is_transition_to_successor trans =
match trans with
| Body
| InitExpr
| FieldName _
| Fields
| ParameterName _
| ParameterPos _
| Parameters
| Cond
| SourceExpr ->
true
| Super | PointerToDecl | Protocol | AccessorForProperty _ | Sibling ->
false
(* In formulas below prefix
"E" means "exists a path"
"A" means "for all path" *)
type t =
(* A ctl formula *)
| True
| False
(* not really necessary but it makes it evaluation faster *)
| Atomic of CPredicates.t
| Not of t
| And of t * t
| Or of t * t
| Implies of t * t
| InNode of ALVar.alexp list * t
| AX of transitions option * t
| EX of transitions option * t
| AF of transitions option * t
| EF of transitions option * t
| AG of transitions option * t
| EG of transitions option * t
| AU of transitions option * t * t
| EU of transitions option * t * t
| EH of ALVar.alexp list * t
| ET of ALVar.alexp list * transitions option * t
| InObjCClass of t * t
[@@deriving compare]
let equal = [%compare.equal: t]
let has_transition phi =
match phi with
| True | False | Atomic _ | Not _ | And _ | Or _ | Implies _ | InNode _ | EH _ | InObjCClass _ ->
false
| AX (trans_opt, _)
| AF (trans_opt, _)
| AG (trans_opt, _)
| AU (trans_opt, _, _)
| EX (trans_opt, _)
| EF (trans_opt, _)
| EG (trans_opt, _)
| EU (trans_opt, _, _)
| ET (_, trans_opt, _) ->
Option.is_some trans_opt
(* "set" clauses are used for defining mandatory variables that will be used (* "set" clauses are used for defining mandatory variables that will be used
by when reporting issues: eg for defining the condition. by when reporting issues: eg for defining the condition.
@ -112,9 +32,9 @@ let has_transition phi =
*) *)
type clause = type clause =
| CLet of ALVar.formula_id * ALVar.t list * t | CLet of ALVar.formula_id * ALVar.t list * CTLTypes.t
(* Let clause: let id = definifion; *) (* Let clause: let id = definifion; *)
| CSet of ALVar.keyword * t | CSet of ALVar.keyword * CTLTypes.t
(* Set clause: set id = definition *) (* Set clause: set id = definition *)
| CDesc of ALVar.keyword * string | CDesc of ALVar.keyword * string
(* Description clause eg: set message = "..." *) (* Description clause eg: set message = "..." *)
@ -129,427 +49,6 @@ type al_file =
; global_paths: (string * ALVar.alexp list) list ; global_paths: (string * ALVar.alexp list) list
; checkers: ctl_checker list } ; checkers: ctl_checker list }
let equal_ast_node = Poly.equal
module Debug = struct
let pp_transition fmt trans_opt =
let pp_aux fmt trans =
match trans with
| AccessorForProperty kind ->
Format.pp_print_string fmt ("AccessorForProperty " ^ ALVar.alexp_to_string kind)
| Body ->
Format.pp_print_string fmt "Body"
| FieldName name ->
Format.pp_print_string fmt ("FieldName " ^ ALVar.alexp_to_string name)
| Fields ->
Format.pp_print_string fmt "Fields"
| InitExpr ->
Format.pp_print_string fmt "InitExpr"
| Super ->
Format.pp_print_string fmt "Super"
| ParameterName name ->
Format.pp_print_string fmt ("ParameterName " ^ ALVar.alexp_to_string name)
| ParameterPos pos ->
Format.pp_print_string fmt ("ParameterPos " ^ ALVar.alexp_to_string pos)
| Parameters ->
Format.pp_print_string fmt "Parameters"
| Cond ->
Format.pp_print_string fmt "Cond"
| Protocol ->
Format.pp_print_string fmt "Protocol"
| PointerToDecl ->
Format.pp_print_string fmt "PointerToDecl"
| Sibling ->
Format.pp_print_string fmt "Sibling"
| SourceExpr ->
Format.pp_print_string fmt "SourceExpr"
in
match trans_opt with Some trans -> pp_aux fmt trans | None -> Format.pp_print_char fmt '_'
(* a flag to print more or less in the dotty graph *)
let full_print = true
let rec pp_formula fmt phi =
let nodes_to_string nl = List.map ~f:ALVar.alexp_to_string nl in
match phi with
| True ->
Format.pp_print_string fmt "True"
| False ->
Format.pp_print_string fmt "False"
| Atomic p ->
CPredicates.pp_predicate fmt p
| Not phi ->
if full_print then Format.fprintf fmt "NOT(%a)" pp_formula phi
else Format.pp_print_string fmt "NOT(...)"
| And (phi1, phi2) ->
if full_print then Format.fprintf fmt "(%a AND %a)" pp_formula phi1 pp_formula phi2
else Format.pp_print_string fmt "(... AND ...)"
| Or (phi1, phi2) ->
if full_print then Format.fprintf fmt "(%a OR %a)" pp_formula phi1 pp_formula phi2
else Format.pp_print_string fmt "(... OR ...)"
| Implies (phi1, phi2) ->
Format.fprintf fmt "(%a ==> %a)" pp_formula phi1 pp_formula phi2
| InNode (nl, phi) ->
Format.fprintf fmt "IN-NODE %a: (%a)"
(Pp.comma_seq Format.pp_print_string)
(nodes_to_string nl) pp_formula phi
| AX (trs, phi) ->
Format.fprintf fmt "AX[->%a](%a)" pp_transition trs pp_formula phi
| EX (trs, phi) ->
Format.fprintf fmt "EX[->%a](%a)" pp_transition trs pp_formula phi
| AF (trs, phi) ->
Format.fprintf fmt "AF[->%a](%a)" pp_transition trs pp_formula phi
| EF (trs, phi) ->
Format.fprintf fmt "EF[->%a](%a)" pp_transition trs pp_formula phi
| AG (trs, phi) ->
Format.fprintf fmt "AG[->%a](%a)" pp_transition trs pp_formula phi
| EG (trs, phi) ->
Format.fprintf fmt "EG[->%a](%a)" pp_transition trs pp_formula phi
| AU (trs, phi1, phi2) ->
Format.fprintf fmt "A[->%a][%a UNTIL %a]" pp_transition trs pp_formula phi1 pp_formula phi2
| EU (trs, phi1, phi2) ->
Format.fprintf fmt "E[->%a][%a UNTIL %a]" pp_transition trs pp_formula phi1 pp_formula phi2
| EH (arglist, phi) ->
Format.fprintf fmt "EH[%a](%a)"
(Pp.comma_seq Format.pp_print_string)
(nodes_to_string arglist) pp_formula phi
| ET (arglist, trans, phi) ->
Format.fprintf fmt "ET[%a][%a](%a)"
(Pp.comma_seq Format.pp_print_string)
(nodes_to_string arglist) pp_transition trans pp_formula phi
| InObjCClass (phi1, phi2) ->
if full_print then Format.fprintf fmt "InObjCClass(%a, %a)" pp_formula phi1 pp_formula phi2
let pp_ast ~ast_node_to_highlight ?(prettifier = Fn.id) fmt root =
let pp_node_info fmt an =
let name = Ctl_parser_types.ast_node_name an in
let typ = Ctl_parser_types.ast_node_type an in
let cast_kind = Ctl_parser_types.ast_node_cast_kind an in
Format.fprintf fmt " %s %s %s" name typ cast_kind
in
let rec pp_children pp_node wrapper fmt level nodes =
match nodes with
| [] ->
()
| node :: nodes ->
pp_node fmt (wrapper node) level "|-" ;
pp_children pp_node wrapper fmt level nodes
in
let rec pp_ast_aux fmt root level prefix =
let get_node_name (an : ast_node) =
match an with
| Stmt stmt ->
Clang_ast_proj.get_stmt_kind_string stmt
| Decl decl ->
Clang_ast_proj.get_decl_kind_string decl
in
let should_highlight =
match (root, ast_node_to_highlight) with
| Stmt r, Stmt n ->
phys_equal r n
| Decl r, Decl n ->
phys_equal r n
| _ ->
false
in
let node_name =
let node_name = get_node_name root in
if should_highlight then prettifier node_name else node_name
in
let spaces = String.make (level * String.length prefix) ' ' in
let next_level = level + 1 in
Format.fprintf fmt "%s%s%s %a@\n" spaces prefix node_name pp_node_info root ;
match root with
| Stmt (DeclStmt (_, stmts, ([VarDecl _] as var_decl))) ->
(* handling special case of DeclStmt with VarDecl: emit the VarDecl node
then emit the statements in DeclStmt as children of VarDecl. This is
because despite being equal, the statements inside VarDecl and those
inside DeclStmt belong to different instances, hence they fail the
phys_equal check that should colour them *)
pp_children pp_ast_aux (fun n -> Decl n) fmt next_level var_decl ;
pp_stmts fmt (next_level + 1) stmts
| Stmt stmt ->
let _, stmts = Clang_ast_proj.get_stmt_tuple stmt in
pp_stmts fmt next_level stmts
| Decl decl ->
let decls =
Clang_ast_proj.get_decl_context_tuple decl
|> Option.map ~f:(fun (decls, _) -> decls)
|> Option.value ~default:[]
in
pp_decls fmt next_level decls
and pp_stmts fmt level stmts = pp_children pp_ast_aux (fun n -> Stmt n) fmt level stmts
and pp_decls fmt level decls = pp_children pp_ast_aux (fun n -> Decl n) fmt level decls in
pp_ast_aux fmt root 0 ""
module EvaluationTracker = struct
exception Empty_stack of string
type eval_result = Eval_undefined | Eval_true | Eval_false
type content =
{ ast_node: ast_node
; phi: t
; lcxt: CLintersContext.context
; eval_result: eval_result
; witness: ast_node option }
type eval_node = {id: int; content: content}
type tree = Tree of eval_node * tree list
type ast_node_to_display =
(* the node can be used to describe further sub calls in the evaluation stack *)
| Carry_forward of ast_node
(* the node cannot be further used to describe sub calls in the evaluation stack *)
| Last_occurrence of ast_node
type t =
{ next_id: int
; eval_stack: (tree * ast_node_to_display) Stack.t
; forest: tree list
; breakpoint_line: int option
; debugger_active: bool }
let create_content ast_node phi lcxt =
{ast_node; phi; eval_result= Eval_undefined; lcxt; witness= None}
let create source_file =
let breakpoint_token = "INFER_BREAKPOINT" in
let breakpoint_line =
In_channel.read_lines (SourceFile.to_abs_path source_file)
|> List.findi ~f:(fun _ line -> String.is_substring line ~substring:breakpoint_token)
|> Option.map ~f:(fun (i, _) -> i + 1)
in
{next_id= 0; eval_stack= Stack.create (); forest= []; breakpoint_line; debugger_active= false}
let explain t ~eval_node ~ast_node_to_display =
let line_number an =
let line_of_source_range (sr : Clang_ast_t.source_range) =
let loc_info, _ = sr in
loc_info.sl_line
in
match an with
| Stmt stmt ->
let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in
line_of_source_range stmt_info.si_source_range
| Decl decl ->
let decl_info = Clang_ast_proj.get_decl_tuple decl in
line_of_source_range decl_info.di_source_range
in
let stop_and_explain_step () =
let highlight_style =
match eval_node.content.eval_result with
| Eval_undefined ->
ANSITerminal.[Bold]
| Eval_true ->
ANSITerminal.[Bold; green]
| Eval_false ->
ANSITerminal.[Bold; red]
in
let ast_node_to_highlight = eval_node.content.ast_node in
let ast_root, is_last_occurrence =
match ast_node_to_display with
| Carry_forward n ->
(n, false)
| Last_occurrence n ->
(n, true)
in
let witness_str =
match eval_node.content.witness with
| Some witness ->
"\n- witness: "
^ Ctl_parser_types.ast_node_kind witness
^ " "
^ Ctl_parser_types.ast_node_name witness
| None ->
""
in
let ast_str =
Format.asprintf "%a %s"
(pp_ast ~ast_node_to_highlight ~prettifier:(ANSITerminal.sprintf highlight_style "%s"))
ast_root witness_str
in
L.progress "@\nNode ID: %d\tEvaluation stack level: %d\tSource line-number: %s@\n"
eval_node.id (Stack.length t.eval_stack)
(Option.value_map ~default:"Unknown" ~f:string_of_int (line_number ast_node_to_highlight)) ;
let is_eval_result_undefined =
match eval_node.content.eval_result with Eval_undefined -> true | _ -> false
in
if is_last_occurrence && is_eval_result_undefined then
L.progress "From this step, a transition to a different part of the AST may follow.@\n" ;
let phi_str = Format.asprintf "%a" pp_formula eval_node.content.phi in
L.progress "CTL Formula: %s@\n@\n" phi_str ;
L.progress "%s@\n" ast_str ;
let quit_token = "q" in
L.progress "Press Enter to continue or type %s to quit... @?" quit_token ;
match In_channel.input_line_exn In_channel.stdin |> String.lowercase with
| s when String.equal s quit_token ->
L.exit 0
| _ ->
(* Remove the line at the bottom of terminal with the debug instructions *)
let open ANSITerminal in
(* move one line up, as current line is the one generated by pressing enter *)
move_cursor 0 (-1) ;
move_bol () ;
(* move to the beginning of the line *)
erase Below
(* erase what follows the cursor's position *)
in
match (t.debugger_active, t.breakpoint_line, line_number eval_node.content.ast_node) with
| false, Some break_point_ln, Some ln when ln >= break_point_ln ->
L.progress "Attaching debugger at line %d" ln ;
stop_and_explain_step () ;
{t with debugger_active= true}
| true, _, _ ->
stop_and_explain_step () ; t
| _ ->
t
let eval_begin t content =
let node = {id= t.next_id; content} in
let create_subtree root = Tree (root, []) in
let subtree' = create_subtree node in
let ast_node_from_previous_call =
match Stack.top t.eval_stack with
| Some (_, Last_occurrence _) ->
content.ast_node
| Some (_, Carry_forward an) ->
an
| None ->
content.ast_node
in
let ast_node_to_display =
if has_transition content.phi then Last_occurrence ast_node_from_previous_call
else Carry_forward ast_node_from_previous_call
in
Stack.push t.eval_stack (subtree', ast_node_to_display) ;
let t' = explain t ~eval_node:node ~ast_node_to_display in
{t' with next_id= t.next_id + 1}
let eval_end t result =
let result_bool = Option.is_some result in
let eval_result_of_bool = function true -> Eval_true | false -> Eval_false in
if Stack.is_empty t.eval_stack then
raise (Empty_stack "Unbalanced number of eval_begin/eval_end invocations") ;
let evaluated_tree, eval_node, ast_node_to_display =
match Stack.pop_exn t.eval_stack with
| Tree (({id= _; content} as eval_node), children), ast_node_to_display ->
let content' =
{content with eval_result= eval_result_of_bool result_bool; witness= result}
in
let eval_node' = {eval_node with content= content'} in
(Tree (eval_node', children), eval_node', ast_node_to_display)
in
let t' = explain t ~eval_node ~ast_node_to_display in
let forest' =
if Stack.is_empty t'.eval_stack then evaluated_tree :: t'.forest
else
let parent =
match Stack.pop_exn t'.eval_stack with
| Tree (node, children), ntd ->
(Tree (node, evaluated_tree :: children), ntd)
in
Stack.push t'.eval_stack parent ; t'.forest
in
{t' with forest= forest'}
module DottyPrinter = struct
let dotty_of_ctl_evaluation t =
let buffer_content buf =
let result = Buffer.contents buf in
Buffer.reset buf ; result
in
let dotty_of_tree cluster_id tree =
let get_root tree = match tree with Tree (root, _) -> root in
let get_children tree = match tree with Tree (_, children) -> List.rev children in
(* shallow: emit dotty about root node and edges to its children *)
let shallow_dotty_of_tree tree =
let root_node = get_root tree in
let children = get_children tree in
let edge child_node =
if equal_ast_node root_node.content.ast_node child_node.content.ast_node then
Printf.sprintf "%d -> %d [style=dotted]" root_node.id child_node.id
else Printf.sprintf "%d -> %d [style=bold]" root_node.id child_node.id
in
let color =
match root_node.content.eval_result with
| Eval_true ->
"green"
| Eval_false ->
"red"
| _ ->
L.(die InternalError) "Tree is not fully evaluated"
in
let label =
let string_of_lcxt c =
match c.CLintersContext.et_evaluation_node with
| Some s ->
"et_evaluation_node = " ^ s
| _ ->
"et_evaluation_node = NONE"
in
let string_of_ast_node an =
match an with
| Stmt stmt ->
Clang_ast_proj.get_stmt_kind_string stmt
| Decl decl ->
Clang_ast_proj.get_decl_kind_string decl
in
let smart_string_of_formula phi =
let num_children = List.length children in
match phi with
| And _ when Int.equal num_children 2 ->
"(...) AND (...)"
| Or _ when Int.equal num_children 2 ->
"(...) OR (...)"
| Implies _ when Int.equal num_children 2 ->
"(...) ==> (...)"
| Not _ ->
"NOT(...)"
| _ ->
Format.asprintf "%a" pp_formula phi
in
Format.sprintf "(%d)\\n%s\\n%s\\n%s" root_node.id
(Escape.escape_dotty (string_of_ast_node root_node.content.ast_node))
(Escape.escape_dotty (string_of_lcxt root_node.content.lcxt))
(Escape.escape_dotty (smart_string_of_formula root_node.content.phi))
in
let edges =
let buf = Buffer.create 16 in
List.iter
~f:(fun subtree -> Buffer.add_string buf (edge (get_root subtree) ^ "\n"))
children ;
buffer_content buf
in
Printf.sprintf "%d [label=\"%s\" shape=box color=%s]\n%s\n" root_node.id label color
edges
in
let rec traverse buf tree =
Buffer.add_string buf (shallow_dotty_of_tree tree) ;
List.iter ~f:(traverse buf) (get_children tree)
in
let buf = Buffer.create 16 in
traverse buf tree ;
Printf.sprintf "subgraph cluster_%d {\n%s\n}" cluster_id (buffer_content buf)
in
let buf = Buffer.create 16 in
List.iteri
~f:(fun cluster_id tree -> Buffer.add_string buf (dotty_of_tree cluster_id tree ^ "\n"))
(List.rev t.forest) ;
Printf.sprintf "digraph CTL_Evaluation {\n%s\n}\n" (buffer_content buf)
end
end
end
let print_checker c = let print_checker c =
L.(debug Linters Medium) "@\n-------------------- @\n" ; L.(debug Linters Medium) "@\n-------------------- @\n" ;
L.(debug Linters Medium) "@\nChecker name: %s@\n" c.id ; L.(debug Linters Medium) "@\nChecker name: %s@\n" c.id ;
@ -558,10 +57,10 @@ let print_checker c =
match d with match d with
| CSet (keyword, phi) -> | CSet (keyword, phi) ->
let cn_str = ALVar.keyword_to_string keyword in let cn_str = ALVar.keyword_to_string keyword in
L.(debug Linters Medium) " %s= @\n %a@\n@\n" cn_str Debug.pp_formula phi L.(debug Linters Medium) " %s= @\n %a@\n@\n" cn_str CTLTypes.pp_formula phi
| CLet (exp, _, phi) -> | CLet (exp, _, phi) ->
let cn_str = ALVar.formula_id_to_string exp in let cn_str = ALVar.formula_id_to_string exp in
L.(debug Linters Medium) " %s= @\n %a@\n@\n" cn_str Debug.pp_formula phi L.(debug Linters Medium) " %s= @\n %a@\n@\n" cn_str CTLTypes.pp_formula phi
| CDesc (keyword, s) -> | CDesc (keyword, s) ->
let cn_str = ALVar.keyword_to_string keyword in let cn_str = ALVar.keyword_to_string keyword in
L.(debug Linters Medium) " %s= @\n %s@\n@\n" cn_str s L.(debug Linters Medium) " %s= @\n %s@\n@\n" cn_str s
@ -580,7 +79,7 @@ let ctl_evaluation_tracker = ref None
let create_ctl_evaluation_tracker source_file = let create_ctl_evaluation_tracker source_file =
match (Config.linters_developer_mode, !ctl_evaluation_tracker) with match (Config.linters_developer_mode, !ctl_evaluation_tracker) with
| true, None -> | true, None ->
ctl_evaluation_tracker := Some (Debug.EvaluationTracker.create source_file) ctl_evaluation_tracker := Some (ALDebugger.EvaluationTracker.create source_file)
| true, _ -> | true, _ ->
L.(die InternalError) "A CTL evaluation tracker has already been created" L.(die InternalError) "A CTL evaluation tracker has already been created"
| _ -> | _ ->
@ -590,7 +89,7 @@ let create_ctl_evaluation_tracker source_file =
let debug_create_payload ast_node phi lcxt = let debug_create_payload ast_node phi lcxt =
match !ctl_evaluation_tracker with match !ctl_evaluation_tracker with
| Some _ -> | Some _ ->
Some (Debug.EvaluationTracker.create_content ast_node phi lcxt) Some (ALDebugger.EvaluationTracker.create_content ast_node phi lcxt)
| None -> | None ->
None None
@ -598,7 +97,7 @@ let debug_create_payload ast_node phi lcxt =
let debug_eval_begin payload = let debug_eval_begin payload =
match (!ctl_evaluation_tracker, payload) with match (!ctl_evaluation_tracker, payload) with
| Some tracker, Some payload -> | Some tracker, Some payload ->
ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_begin tracker payload) ctl_evaluation_tracker := Some (ALDebugger.EvaluationTracker.eval_begin tracker payload)
| _ -> | _ ->
() ()
@ -606,7 +105,7 @@ let debug_eval_begin payload =
let debug_eval_end result = let debug_eval_end result =
match !ctl_evaluation_tracker with match !ctl_evaluation_tracker with
| Some tracker -> | Some tracker ->
ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_end tracker result) ctl_evaluation_tracker := Some (ALDebugger.EvaluationTracker.eval_end tracker result)
| None -> | None ->
() ()
@ -618,7 +117,7 @@ let save_dotty_when_in_debug_mode source_file =
Utils.create_dir dotty_dir ; Utils.create_dir dotty_dir ;
let source_file_basename = Filename.basename (SourceFile.to_abs_path source_file) in let source_file_basename = Filename.basename (SourceFile.to_abs_path source_file) in
let file = dotty_dir ^/ source_file_basename ^ ".dot" in let file = dotty_dir ^/ source_file_basename ^ ".dot" in
let dotty = Debug.EvaluationTracker.DottyPrinter.dotty_of_ctl_evaluation tracker in let dotty = ALDebugger.EvaluationTracker.DottyPrinter.dotty_of_ctl_evaluation tracker in
Utils.with_file_out file ~f:(fun oc -> Out_channel.output_string oc dotty) Utils.with_file_out file ~f:(fun oc -> Out_channel.output_string oc dotty)
| _ -> | _ ->
() ()
@ -628,6 +127,7 @@ let save_dotty_when_in_debug_mode source_file =
(* given a decl returns a stmt such that decl--->stmt via label trs *) (* given a decl returns a stmt such that decl--->stmt via label trs *)
let transition_decl_to_stmt d trs = let transition_decl_to_stmt d trs =
let open Clang_ast_t in let open Clang_ast_t in
let open CTLTypes in
let temp_res = let temp_res =
match (trs, d) with match (trs, d) with
| Body, ObjCMethodDecl (_, _, omdi) -> | Body, ObjCMethodDecl (_, _, omdi) ->
@ -962,6 +462,7 @@ let transition_via_sibling node =
(* given a node an returns a list of nodes an' such that an transition to an' via label trans *) (* given a node an returns a list of nodes an' such that an transition to an' via label trans *)
let next_state_via_transition an trans = let next_state_via_transition an trans =
let open CTLTypes in
match (an, trans) with match (an, trans) with
| Decl d, Super -> | Decl d, Super ->
transition_decl_to_decl_via_super d transition_decl_to_decl_via_super d
@ -1285,6 +786,7 @@ and eval_Implies an lcxt f1 f2 =
such that (an', lcxt) satifies EF phi such that (an', lcxt) satifies EF phi
*) *)
and eval_EF phi an lcxt trans = and eval_EF phi an lcxt trans =
let open CTLTypes in
match (trans, an) with match (trans, an) with
| Some _, _ -> | Some _, _ ->
(* Using equivalence EF[->trans] phi = phi OR EX[->trans](EF[->trans] phi)*) (* Using equivalence EF[->trans] phi = phi OR EX[->trans](EF[->trans] phi)*)
@ -1317,7 +819,7 @@ and eval_EX phi an lcxt trans =
choose_witness_opt (eval_formula phi node lcxt) acc ) choose_witness_opt (eval_formula phi node lcxt) acc )
in in
match (witness_opt, trans) with match (witness_opt, trans) with
| Some _, Some trans when not (is_transition_to_successor trans) -> | Some _, Some trans when not (CTLTypes.is_transition_to_successor trans) ->
Some an (* We want to limit the witnesses to the successors of the original node. *) Some an (* We want to limit the witnesses to the successors of the original node. *)
| _ -> | _ ->
witness_opt witness_opt
@ -1330,6 +832,7 @@ and eval_EX phi an lcxt trans =
an,lcxt satifies the formula phi2 or (phi1 and EX(E(phi1 U phi2))) an,lcxt satifies the formula phi2 or (phi1 and EX(E(phi1 U phi2)))
*) *)
and eval_EU phi1 phi2 an lcxt trans = and eval_EU phi1 phi2 an lcxt trans =
let open CTLTypes in
let f = Or (phi2, And (phi1, EX (trans, EU (trans, phi1, phi2)))) in let f = Or (phi2, And (phi1, EX (trans, EU (trans, phi1, phi2)))) in
eval_formula f an lcxt eval_formula f an lcxt
@ -1340,6 +843,7 @@ and eval_EU phi1 phi2 an lcxt trans =
Same as EU but for the all path quantifier A Same as EU but for the all path quantifier A
*) *)
and eval_AU phi1 phi2 an lcxt trans = and eval_AU phi1 phi2 an lcxt trans =
let open CTLTypes in
let f = Or (phi2, And (phi1, AX (trans, AU (trans, phi1, phi2)))) in let f = Or (phi2, And (phi1, AX (trans, AU (trans, phi1, phi2)))) in
eval_formula f an lcxt eval_formula f an lcxt
@ -1370,6 +874,7 @@ and in_node node_type_list phi an lctx =
the node an is in Classes and there exists a declaration d in Hierarchy(an) the node an is in Classes and there exists a declaration d in Hierarchy(an)
such that d,lcxt |= phi *) such that d,lcxt |= phi *)
and eval_EH classes phi an lcxt = and eval_EH classes phi an lcxt =
let open CTLTypes in
(* Define EH[Classes] phi = ET[Classes](EF[->Super] phi) *) (* Define EH[Classes] phi = ET[Classes](EF[->Super] phi) *)
let f = ET (classes, None, EX (Some Super, EF (Some Super, phi))) in let f = ET (classes, None, EX (Some Super, EF (Some Super, phi))) in
eval_formula f an lcxt eval_formula f an lcxt
@ -1386,6 +891,7 @@ and eval_EH classes phi an lcxt =
or l is unspecified and an,lcxt |= phi or l is unspecified and an,lcxt |= phi
*) *)
and eval_ET tl trs phi an lcxt = and eval_ET tl trs phi an lcxt =
let open CTLTypes in
let f = let f =
match trs with match trs with
| Some _ -> | Some _ ->
@ -1412,6 +918,7 @@ and eval_InObjCClass an lcxt f1 f2 =
(* Formulas are evaluated on a AST node an and a linter context lcxt *) (* Formulas are evaluated on a AST node an and a linter context lcxt *)
and eval_formula f an lcxt : Ctl_parser_types.ast_node option = and eval_formula f an lcxt : Ctl_parser_types.ast_node option =
let open CTLTypes in
debug_eval_begin (debug_create_payload an f lcxt) ; debug_eval_begin (debug_create_payload an f lcxt) ;
let res = let res =
match f with match f with

@ -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
(**
This module defines a language to define checkers. These checkers are interpreted over the AST of
the program. A checker is defined by a CTL formula which expresses a condition saying when the
checker should report a problem.
*)
(** "set" clauses are used for defining mandatory variables that will be used
by when reporting issues: eg for defining the condition.
"desc" clauses are used for defining the error message,
the suggestion, the severity.
"let" clauses are used to define temporary formulas which are then
used to abbreviate the another formula. For example
let f = a And B
set formula = f OR f
set message = "bla"
*)
type clause =
| CLet of ALVar.formula_id * ALVar.t list * CTLTypes.t (** Let clause: let id = definifion; *)
| CSet of ALVar.keyword * CTLTypes.t (** Set clause: set id = definition *)
| CDesc of ALVar.keyword * string (** Description clause eg: set message = "..." *)
| CPath of [`WhitelistPath | `BlacklistPath] * ALVar.t list
type ctl_checker =
{id: string (** Checker's id *); definitions: clause list (** A list of let/set definitions *)}
type al_file =
{ import_files: string list
; global_macros: clause list
; global_paths: (string * ALVar.alexp list) list
; checkers: ctl_checker list }
val print_checker : ctl_checker -> unit
val eval_formula :
CTLTypes.t
-> Ctl_parser_types.ast_node
-> CLintersContext.context
-> Ctl_parser_types.ast_node option
(** return the evaluation of the formula and a witness *)
val save_dotty_when_in_debug_mode : SourceFile.t -> unit
val next_state_via_transition :
Ctl_parser_types.ast_node -> CTLTypes.transitions -> Ctl_parser_types.ast_node list
val create_ctl_evaluation_tracker : SourceFile.t -> unit

@ -0,0 +1,177 @@
(*
* 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
(** Transition labels used for example to switch from decl to stmt *)
type transitions =
| AccessorForProperty of ALVar.alexp (** decl to decl *)
| Body (** decl to stmt *)
| FieldName of ALVar.alexp (** stmt to stmt, decl to decl *)
| Fields (** stmt to stmt, decl to decl *)
| InitExpr (** decl to stmt *)
| Super (** decl to decl *)
| ParameterName of ALVar.alexp (** stmt to stmt, decl to decl *)
| ParameterPos of ALVar.alexp (** stmt to stmt, decl to decl *)
| Parameters (** stmt to stmt, decl to decl *)
| Cond
| PointerToDecl (** stmt to decl *)
| Protocol (** decl to decl *)
| Sibling (** decl to decl *)
| SourceExpr
[@@deriving compare]
let is_transition_to_successor trans =
match trans with
| Body
| InitExpr
| FieldName _
| Fields
| ParameterName _
| ParameterPos _
| Parameters
| Cond
| SourceExpr ->
true
| Super | PointerToDecl | Protocol | AccessorForProperty _ | Sibling ->
false
(* In formulas below prefix
"E" means "exists a path"
"A" means "for all path" *)
type t =
(* A ctl formula *)
| True
| False
(* not really necessary but it makes it evaluation faster *)
| Atomic of CPredicates.t
| Not of t
| And of t * t
| Or of t * t
| Implies of t * t
| InNode of ALVar.alexp list * t
| AX of transitions option * t
| EX of transitions option * t
| AF of transitions option * t
| EF of transitions option * t
| AG of transitions option * t
| EG of transitions option * t
| AU of transitions option * t * t
| EU of transitions option * t * t
| EH of ALVar.alexp list * t
| ET of ALVar.alexp list * transitions option * t
| InObjCClass of t * t
[@@deriving compare]
let equal = [%compare.equal: t]
let has_transition phi =
match phi with
| True | False | Atomic _ | Not _ | And _ | Or _ | Implies _ | InNode _ | EH _ | InObjCClass _ ->
false
| AX (trans_opt, _)
| AF (trans_opt, _)
| AG (trans_opt, _)
| AU (trans_opt, _, _)
| EX (trans_opt, _)
| EF (trans_opt, _)
| EG (trans_opt, _)
| EU (trans_opt, _, _)
| ET (_, trans_opt, _) ->
Option.is_some trans_opt
let pp_transition fmt trans_opt =
let pp_aux fmt trans =
match trans with
| AccessorForProperty kind ->
Format.pp_print_string fmt ("AccessorForProperty " ^ ALVar.alexp_to_string kind)
| Body ->
Format.pp_print_string fmt "Body"
| FieldName name ->
Format.pp_print_string fmt ("FieldName " ^ ALVar.alexp_to_string name)
| Fields ->
Format.pp_print_string fmt "Fields"
| InitExpr ->
Format.pp_print_string fmt "InitExpr"
| Super ->
Format.pp_print_string fmt "Super"
| ParameterName name ->
Format.pp_print_string fmt ("ParameterName " ^ ALVar.alexp_to_string name)
| ParameterPos pos ->
Format.pp_print_string fmt ("ParameterPos " ^ ALVar.alexp_to_string pos)
| Parameters ->
Format.pp_print_string fmt "Parameters"
| Cond ->
Format.pp_print_string fmt "Cond"
| Protocol ->
Format.pp_print_string fmt "Protocol"
| PointerToDecl ->
Format.pp_print_string fmt "PointerToDecl"
| Sibling ->
Format.pp_print_string fmt "Sibling"
| SourceExpr ->
Format.pp_print_string fmt "SourceExpr"
in
match trans_opt with Some trans -> pp_aux fmt trans | None -> Format.pp_print_char fmt '_'
(* a flag to print more or less in the dotty graph *)
let full_print = true
let rec pp_formula fmt phi =
let nodes_to_string nl = List.map ~f:ALVar.alexp_to_string nl in
match phi with
| True ->
Format.pp_print_string fmt "True"
| False ->
Format.pp_print_string fmt "False"
| Atomic p ->
CPredicates.pp_predicate fmt p
| Not phi ->
if full_print then Format.fprintf fmt "NOT(%a)" pp_formula phi
else Format.pp_print_string fmt "NOT(...)"
| And (phi1, phi2) ->
if full_print then Format.fprintf fmt "(%a AND %a)" pp_formula phi1 pp_formula phi2
else Format.pp_print_string fmt "(... AND ...)"
| Or (phi1, phi2) ->
if full_print then Format.fprintf fmt "(%a OR %a)" pp_formula phi1 pp_formula phi2
else Format.pp_print_string fmt "(... OR ...)"
| Implies (phi1, phi2) ->
Format.fprintf fmt "(%a ==> %a)" pp_formula phi1 pp_formula phi2
| InNode (nl, phi) ->
Format.fprintf fmt "IN-NODE %a: (%a)"
(Pp.comma_seq Format.pp_print_string)
(nodes_to_string nl) pp_formula phi
| AX (trs, phi) ->
Format.fprintf fmt "AX[->%a](%a)" pp_transition trs pp_formula phi
| EX (trs, phi) ->
Format.fprintf fmt "EX[->%a](%a)" pp_transition trs pp_formula phi
| AF (trs, phi) ->
Format.fprintf fmt "AF[->%a](%a)" pp_transition trs pp_formula phi
| EF (trs, phi) ->
Format.fprintf fmt "EF[->%a](%a)" pp_transition trs pp_formula phi
| AG (trs, phi) ->
Format.fprintf fmt "AG[->%a](%a)" pp_transition trs pp_formula phi
| EG (trs, phi) ->
Format.fprintf fmt "EG[->%a](%a)" pp_transition trs pp_formula phi
| AU (trs, phi1, phi2) ->
Format.fprintf fmt "A[->%a][%a UNTIL %a]" pp_transition trs pp_formula phi1 pp_formula phi2
| EU (trs, phi1, phi2) ->
Format.fprintf fmt "E[->%a][%a UNTIL %a]" pp_transition trs pp_formula phi1 pp_formula phi2
| EH (arglist, phi) ->
Format.fprintf fmt "EH[%a](%a)"
(Pp.comma_seq Format.pp_print_string)
(nodes_to_string arglist) pp_formula phi
| ET (arglist, trans, phi) ->
Format.fprintf fmt "ET[%a][%a](%a)"
(Pp.comma_seq Format.pp_print_string)
(nodes_to_string arglist) pp_transition trans pp_formula phi
| InObjCClass (phi1, phi2) ->
if full_print then Format.fprintf fmt "InObjCClass(%a, %a)" pp_formula phi1 pp_formula phi2

@ -5,15 +5,6 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*) *)
open! IStd
open Ctl_parser_types
(**
This module defines a language to define checkers. These checkers are interpreted over the AST of
the program. A checker is defined by a CTL formula which expresses a condition saying when the
checker should report a problem.
*)
(** Transition labels used for example to switch from decl to stmt *) (** Transition labels used for example to switch from decl to stmt *)
type transitions = type transitions =
| AccessorForProperty of ALVar.alexp (** decl to decl *) | AccessorForProperty of ALVar.alexp (** decl to decl *)
@ -68,53 +59,12 @@ type t =
| InObjCClass of t * t | InObjCClass of t * t
[@@deriving compare] [@@deriving compare]
(* "set" clauses are used for defining mandatory variables that will be used val is_transition_to_successor : transitions -> bool
by when reporting issues: eg for defining the condition.
"desc" clauses are used for defining the error message,
the suggestion, the severity.
"let" clauses are used to define temporary formulas which are then
used to abbreviate the another formula. For example
let f = a And B val has_transition : t -> bool
set formula = f OR f
set message = "bla"
*)
val equal : t -> t -> bool val equal : t -> t -> bool
type clause = val pp_transition : Format.formatter -> transitions option -> unit
| CLet of ALVar.formula_id * ALVar.t list * t (** Let clause: let id = definifion; *)
| CSet of ALVar.keyword * t (** Set clause: set id = definition *)
| CDesc of ALVar.keyword * string (** Description clause eg: set message = "..." *)
| CPath of [`WhitelistPath | `BlacklistPath] * ALVar.t list
type ctl_checker =
{id: string (** Checker's id *); definitions: clause list (** A list of let/set definitions *)}
type al_file =
{ import_files: string list
; global_macros: clause list
; global_paths: (string * ALVar.alexp list) list
; checkers: ctl_checker list }
val print_checker : ctl_checker -> unit
val eval_formula : t -> ast_node -> CLintersContext.context -> ast_node option
(** return the evaluation of the formula and a witness *)
val save_dotty_when_in_debug_mode : SourceFile.t -> unit
val next_state_via_transition : ast_node -> transitions -> ast_node list
val create_ctl_evaluation_tracker : SourceFile.t -> unit
module Debug : sig
val pp_formula : Format.formatter -> t -> unit val pp_formula : Format.formatter -> t -> unit
val pp_transition : Format.formatter -> transitions option -> unit
end

@ -216,10 +216,10 @@ let_clause:
; ;
atomic_formula: atomic_formula:
| TRUE { L.(debug Linters Verbose) "\tParsed True@\n"; CTL.True } | TRUE { L.(debug Linters Verbose) "\tParsed True@\n"; CTLTypes.True }
| FALSE { L.(debug Linters Verbose) "\tParsed False@\n"; CTL.False } | FALSE { L.(debug Linters Verbose) "\tParsed False@\n"; CTLTypes.False }
| identifier LEFT_PAREN actual_params RIGHT_PAREN | identifier LEFT_PAREN actual_params RIGHT_PAREN
{ L.(debug Linters Verbose) "\tParsed predicate@\n"; CTL.Atomic(ALVar.Formula_id $1, $3) } { L.(debug Linters Verbose) "\tParsed predicate@\n"; CTLTypes.Atomic(ALVar.Formula_id $1, $3) }
; ;
formula_id_def: formula_id_def:
@ -240,20 +240,20 @@ actual_params:
; ;
transition_label: transition_label:
| ACCESSOR_FOR_PROPERTY alexp { Some (CTL.AccessorForProperty $2) } | ACCESSOR_FOR_PROPERTY alexp { Some (CTLTypes.AccessorForProperty $2) }
| ANY { None } | ANY { None }
| BODY { Some CTL.Body } | BODY { Some CTLTypes.Body }
| COND { Some CTL.Cond } | COND { Some CTLTypes.Cond }
| FIELDS { Some CTL.Fields } | FIELDS { Some CTLTypes.Fields }
| FIELD_NAME alexp { Some (CTL.FieldName $2) } | FIELD_NAME alexp { Some (CTLTypes.FieldName $2) }
| INIT_EXPR { Some CTL.InitExpr } | INIT_EXPR { Some CTLTypes.InitExpr }
| PARAMETERS { Some CTL.Parameters } | PARAMETERS { Some CTLTypes.Parameters }
| PARAMETER_NAME alexp { Some (CTL.ParameterName $2) } | PARAMETER_NAME alexp { Some (CTLTypes.ParameterName $2) }
| PARAMETER_POS alexp { Some (CTL.ParameterPos $2) } | PARAMETER_POS alexp { Some (CTLTypes.ParameterPos $2) }
| POINTER_TO_DECL { Some CTL.PointerToDecl } | POINTER_TO_DECL { Some CTLTypes.PointerToDecl }
| PROTOCOL { Some CTL.Protocol } | PROTOCOL { Some CTLTypes.Protocol }
| SIBLING { Some CTL.Sibling} | SIBLING { Some CTLTypes.Sibling}
| SOURCE_EXPR { Some CTL.SourceExpr} | SOURCE_EXPR { Some CTLTypes.SourceExpr}
; ;
formula_EF: formula_EF:
@ -268,50 +268,50 @@ when_formula:
| INTERFACE LEFT_SQBRACE formula RIGHT_SQBRACE | INTERFACE LEFT_SQBRACE formula RIGHT_SQBRACE
IMPLEMENTATION LEFT_SQBRACE formula RIGHT_SQBRACE HOLDS_IN_OBJCCLASS IMPLEMENTATION LEFT_SQBRACE formula RIGHT_SQBRACE HOLDS_IN_OBJCCLASS
{ L.(debug Linters Verbose) "\tParsed HOLDS-IN-OBJC-CLASS @\n"; { L.(debug Linters Verbose) "\tParsed HOLDS-IN-OBJC-CLASS @\n";
CTL.InObjCClass ($3, $7) } CTLTypes.InObjCClass ($3, $7) }
| formula HOLDS_IN_NODE node_list | formula HOLDS_IN_NODE node_list
{ L.(debug Linters Verbose) "\tParsed InNode@\n"; CTL.InNode ($3, $1)} { L.(debug Linters Verbose) "\tParsed InNode@\n"; CTLTypes.InNode ($3, $1)}
; ;
formula: formula:
| formula_with_paren { $1 } | formula_with_paren { $1 }
| formula_id { CTL.Atomic($1, []) } | formula_id { CTLTypes.Atomic($1, []) }
| atomic_formula { L.(debug Linters Verbose) "\tParsed atomic formula@\n"; $1 } | atomic_formula { L.(debug Linters Verbose) "\tParsed atomic formula@\n"; $1 }
| formula EU formula { L.(debug Linters Verbose) "\tParsed EU@\n"; CTL.EU (None, $1, $3) } | formula EU formula { L.(debug Linters Verbose) "\tParsed EU@\n"; CTLTypes.EU (None, $1, $3) }
| formula AU formula { L.(debug Linters Verbose) "\tParsed AU@\n"; CTL.AU (None,$1, $3) } | formula AU formula { L.(debug Linters Verbose) "\tParsed AU@\n"; CTLTypes.AU (None,$1, $3) }
| formula AF { L.(debug Linters Verbose) "\tParsed AF@\n"; CTL.AF (None,$1) } | formula AF { L.(debug Linters Verbose) "\tParsed AF@\n"; CTLTypes.AF (None,$1) }
| formula EX { L.(debug Linters Verbose) "\tParsed EX@\n"; CTL.EX (None, $1) } | formula EX { L.(debug Linters Verbose) "\tParsed EX@\n"; CTLTypes.EX (None, $1) }
| formula EX WITH_TRANSITION transition_label | formula EX WITH_TRANSITION transition_label
{ L.(debug Linters Verbose) "\tParsed EX WITH-TRANSITION '%a'@\n" CTL.Debug.pp_transition $4; { L.(debug Linters Verbose) "\tParsed EX WITH-TRANSITION '%a'@\n" CTLTypes.pp_transition $4;
CTL.EX ($4, $1) } CTLTypes.EX ($4, $1) }
| formula AX { L.(debug Linters Verbose) "\tParsed AX@\n"; CTL.AX (None, $1) } | formula AX { L.(debug Linters Verbose) "\tParsed AX@\n"; CTLTypes.AX (None, $1) }
| formula AX WITH_TRANSITION transition_label | formula AX WITH_TRANSITION transition_label
{ L.(debug Linters Verbose) "\tParsed AX WITH-TRANSITION '%a'@\n" CTL.Debug.pp_transition $4; { L.(debug Linters Verbose) "\tParsed AX WITH-TRANSITION '%a'@\n" CTLTypes.pp_transition $4;
CTL.AX ($4, $1) } CTLTypes.AX ($4, $1) }
| formula EG { L.(debug Linters Verbose) "\tParsed EG@\n"; CTL.EG (None, $1) } | formula EG { L.(debug Linters Verbose) "\tParsed EG@\n"; CTLTypes.EG (None, $1) }
| formula AG { L.(debug Linters Verbose) "\tParsed AG@\n"; CTL.AG (None, $1) } | formula AG { L.(debug Linters Verbose) "\tParsed AG@\n"; CTLTypes.AG (None, $1) }
| formula EH node_list { L.(debug Linters Verbose) "\tParsed EH@\n"; CTL.EH ($3, $1) } | formula EH node_list { L.(debug Linters Verbose) "\tParsed EH@\n"; CTLTypes.EH ($3, $1) }
| formula EF { L.(debug Linters Verbose) "\tParsed EF@\n"; CTL.EF (None, $1) } | formula EF { L.(debug Linters Verbose) "\tParsed EF@\n"; CTLTypes.EF (None, $1) }
| formula EF WITH_TRANSITION transition_label | formula EF WITH_TRANSITION transition_label
{ L.(debug Linters Verbose) "\tParsed EF WITH-TRANSITION '%a'@\n" CTL.Debug.pp_transition $4; { L.(debug Linters Verbose) "\tParsed EF WITH-TRANSITION '%a'@\n" CTLTypes.pp_transition $4;
CTL.EF($4, $1) } CTLTypes.EF($4, $1) }
| WHEN when_formula { $2 } | WHEN when_formula { $2 }
| ET node_list WITH_TRANSITION transition_label formula_EF | ET node_list WITH_TRANSITION transition_label formula_EF
{ L.(debug Linters Verbose) "\tParsed ET with transition '%a'@\n" CTL.Debug.pp_transition $4; { L.(debug Linters Verbose) "\tParsed ET with transition '%a'@\n" CTLTypes.pp_transition $4;
CTL.ET ($2, $4, $5)} CTLTypes.ET ($2, $4, $5)}
| EX WITH_TRANSITION transition_label formula_with_paren | EX WITH_TRANSITION transition_label formula_with_paren
{ L.(debug Linters Verbose) "\tParsed EX with transition '%a'@\n" CTL.Debug.pp_transition $3; { L.(debug Linters Verbose) "\tParsed EX with transition '%a'@\n" CTLTypes.pp_transition $3;
CTL.EX ($3, $4)} CTLTypes.EX ($3, $4)}
| AX WITH_TRANSITION transition_label formula_with_paren | AX WITH_TRANSITION transition_label formula_with_paren
{ L.(debug Linters Verbose) { L.(debug Linters Verbose)
"\tParsed AX with transition '%a'@\n" CTL.Debug.pp_transition $3; "\tParsed AX with transition '%a'@\n" CTLTypes.pp_transition $3;
CTL.AX ($3, $4)} CTLTypes.AX ($3, $4)}
| formula AND formula { L.(debug Linters Verbose) "\tParsed AND@\n"; CTL.And ($1, $3) } | formula AND formula { L.(debug Linters Verbose) "\tParsed AND@\n"; CTLTypes.And ($1, $3) }
| formula OR formula { L.(debug Linters Verbose) "\tParsed OR@\n"; CTL.Or ($1, $3) } | formula OR formula { L.(debug Linters Verbose) "\tParsed OR@\n"; CTLTypes.Or ($1, $3) }
| formula IMPLIES formula | formula IMPLIES formula
{ L.(debug Linters Verbose) "\tParsed IMPLIES@\n"; CTL.Implies ($1, $3) } { L.(debug Linters Verbose) "\tParsed IMPLIES@\n"; CTLTypes.Implies ($1, $3) }
| NOT formula { L.(debug Linters Verbose) "\tParsed NOT@\n"; CTL.Not ($2) } | NOT formula { L.(debug Linters Verbose) "\tParsed NOT@\n"; CTLTypes.Not ($2) }
; ;

Loading…
Cancel
Save