|
|
|
(*
|
|
|
|
* Copyright (c) 2016 - present Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*)
|
|
|
|
|
|
|
|
open! IStd
|
|
|
|
open Ctl_parser_types
|
|
|
|
|
|
|
|
module L = Logging
|
|
|
|
|
|
|
|
(* This module defines a language to define checkers. These checkers
|
|
|
|
are intepreted over the AST of the program. A checker is defined by a
|
|
|
|
CTL formula which express a condition saying when the checker should
|
|
|
|
report a problem *)
|
|
|
|
|
|
|
|
(* Transition labels used for example to switch from decl to stmt *)
|
|
|
|
type transitions =
|
|
|
|
| Body (** decl to stmt *)
|
|
|
|
| InitExpr (** decl to stmt *)
|
|
|
|
| Super (** decl to decl *)
|
|
|
|
| Parameters (** decl to decl *)
|
|
|
|
| Cond
|
|
|
|
| PointerToDecl (** stmt to decl *)
|
|
|
|
|
|
|
|
(* 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
|
|
|
|
| ETX of ALVar.alexp list * transitions option * t
|
|
|
|
|
|
|
|
let has_transition phi = match phi with
|
|
|
|
| True | False | Atomic _ | Not _ | And (_, _)
|
|
|
|
| Or (_, _) | Implies (_, _) | InNode (_, _)
|
|
|
|
| EH (_, _) -> 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, _) | ETX (_, trans_opt, _) -> Option.is_some trans_opt
|
|
|
|
|
|
|
|
(* "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 * 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 = "..." *)
|
|
|
|
|
|
|
|
type ctl_checker = {
|
|
|
|
name : string; (* Checker's name *)
|
|
|
|
definitions : clause list (* A list of let/set definitions *)
|
|
|
|
}
|
|
|
|
|
|
|
|
type al_file = {
|
|
|
|
import_files : string list;
|
|
|
|
global_macros : clause list;
|
|
|
|
checkers : ctl_checker list
|
|
|
|
}
|
|
|
|
|
|
|
|
let equal_ast_node = Poly.(=)
|
|
|
|
|
|
|
|
module Debug = struct
|
|
|
|
let pp_transition fmt trans_opt =
|
|
|
|
let pp_aux fmt trans = match trans with
|
|
|
|
| Body -> Format.pp_print_string fmt "Body"
|
|
|
|
| InitExpr -> Format.pp_print_string fmt "InitExpr"
|
|
|
|
| Super -> Format.pp_print_string fmt "Super"
|
|
|
|
| Parameters -> Format.pp_print_string fmt "Parameters"
|
|
|
|
| Cond -> Format.pp_print_string fmt "Cond"
|
|
|
|
| PointerToDecl -> Format.pp_print_string fmt "PointerToDecl" in
|
|
|
|
match trans_opt with
|
|
|
|
| Some trans -> pp_aux fmt trans
|
|
|
|
| None -> Format.pp_print_string 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.fprintf fmt "True"
|
|
|
|
| False -> Format.fprintf 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.fprintf fmt "NOT(...)"
|
|
|
|
| And (phi1, phi2) -> if full_print then
|
|
|
|
Format.fprintf fmt "(%a AND %a)" pp_formula phi1 pp_formula phi2
|
|
|
|
else Format.fprintf fmt "(... AND ...)"
|
|
|
|
| Or (phi1, phi2) -> if full_print then
|
|
|
|
Format.fprintf fmt "(%a OR %a)" pp_formula phi1 pp_formula phi2
|
|
|
|
else Format.fprintf 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
|
|
|
|
| ETX (arglist, trans, phi) -> Format.fprintf fmt "ETX[%a][%a](%a)"
|
|
|
|
(Pp.comma_seq Format.pp_print_string)
|
|
|
|
(nodes_to_string arglist)
|
|
|
|
pp_transition trans
|
|
|
|
pp_formula phi
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = lcxt; }
|
|
|
|
|
|
|
|
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 ast_str =
|
|
|
|
Format.asprintf "%a"
|
|
|
|
(pp_ast
|
|
|
|
~ast_node_to_highlight ~prettifier:(ANSITerminal.sprintf highlight_style "%s"))
|
|
|
|
ast_root in
|
|
|
|
L.(debug Linters Medium)
|
|
|
|
"@\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.(debug Linters Medium)
|
|
|
|
"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.(debug Linters Medium) "CTL Formula: %s@\n@\n" phi_str;
|
|
|
|
L.(debug Linters Medium) "%s@\n" ast_str;
|
|
|
|
let quit_token = "q" in
|
|
|
|
L.(debug Linters Medium) "Press Enter to continue or type %s to quit... @?" quit_token;
|
|
|
|
match read_line () |> String.lowercase with
|
|
|
|
| s when String.equal s quit_token -> exit 0
|
|
|
|
| _ ->
|
|
|
|
(* Remove the line at the bottom of terminal with the debug instructions *)
|
|
|
|
ANSITerminal.(
|
|
|
|
(* 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.(debug Linters Medium) "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 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} 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"
|
|
|
|
| _ -> failwith "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 =
|
|
|
|
L.(debug Linters Medium) "@\n-------------------- @\n";
|
|
|
|
L.(debug Linters Medium) "@\nChecker name: %s@\n" c.name;
|
|
|
|
List.iter ~f:(fun d -> (match d with
|
|
|
|
| CSet (keyword, phi) ->
|
|
|
|
let cn_str = ALVar.keyword_to_string keyword in
|
|
|
|
L.(debug Linters Medium) " %s= @\n %a@\n@\n"
|
|
|
|
cn_str Debug.pp_formula phi
|
|
|
|
| CLet (exp, _, phi) ->
|
|
|
|
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
|
|
|
|
| CDesc (keyword, s) ->
|
|
|
|
let cn_str = ALVar.keyword_to_string keyword in
|
|
|
|
L.(debug Linters Medium) " %s= @\n %s@\n@\n" cn_str s)
|
|
|
|
) c.definitions;
|
|
|
|
L.(debug Linters Medium) "@\n-------------------- @\n"
|
|
|
|
|
|
|
|
|
|
|
|
let ctl_evaluation_tracker = ref None
|
|
|
|
|
|
|
|
let create_ctl_evaluation_tracker source_file =
|
|
|
|
match Config.linters_developer_mode, !ctl_evaluation_tracker with
|
|
|
|
| true, None -> ctl_evaluation_tracker := Some (Debug.EvaluationTracker.create source_file)
|
|
|
|
| true, _ -> failwith "A CTL evaluation tracker has already been created"
|
|
|
|
| _ -> ()
|
|
|
|
|
|
|
|
let debug_create_payload ast_node phi lcxt =
|
|
|
|
match !ctl_evaluation_tracker with
|
|
|
|
| Some _ -> Some (Debug.EvaluationTracker.create_content ast_node phi lcxt)
|
|
|
|
| None -> None
|
|
|
|
|
|
|
|
let debug_eval_begin payload =
|
|
|
|
match !ctl_evaluation_tracker, payload with
|
|
|
|
| Some tracker, Some payload ->
|
|
|
|
ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_begin tracker payload)
|
|
|
|
| _ -> ()
|
|
|
|
|
|
|
|
let debug_eval_end result =
|
|
|
|
match !ctl_evaluation_tracker with
|
|
|
|
| Some tracker ->
|
|
|
|
ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_end tracker result)
|
|
|
|
| None -> ()
|
|
|
|
|
|
|
|
let save_dotty_when_in_debug_mode source_file =
|
|
|
|
match !ctl_evaluation_tracker with
|
|
|
|
| Some tracker ->
|
|
|
|
let dotty_dir = Config.results_dir ^/ Config.lint_dotty_dir_name in
|
|
|
|
Utils.create_dir dotty_dir;
|
|
|
|
let source_file_basename = Filename.basename (SourceFile.to_abs_path source_file) in
|
|
|
|
let file = dotty_dir ^/ (source_file_basename ^ ".dot") in
|
|
|
|
let dotty = Debug.EvaluationTracker.DottyPrinter.dotty_of_ctl_evaluation tracker in
|
|
|
|
Utils.with_file_out file ~f:(fun oc -> output_string oc dotty)
|
|
|
|
| _ -> ()
|
|
|
|
|
|
|
|
(* Helper functions *)
|
|
|
|
|
|
|
|
let get_successor_nodes an =
|
|
|
|
(* get_decl_of_stmt get declarations that are directly embedded
|
|
|
|
as immediate children (i.e. distance 1) of an stmt (i.e., no transition).
|
|
|
|
TBD: check if a dual is needed for get_stmt_of_decl
|
|
|
|
*)
|
|
|
|
let get_decl_of_stmt st =
|
|
|
|
match st with
|
|
|
|
| Clang_ast_t.BlockExpr (_, _, _, d) -> [Decl d]
|
|
|
|
| _ -> [] in
|
|
|
|
match an with
|
|
|
|
| Stmt st ->
|
|
|
|
let _, succs_st = Clang_ast_proj.get_stmt_tuple st in
|
|
|
|
let succs = List.map ~f:(fun s -> Stmt s) succs_st in
|
|
|
|
succs @ (get_decl_of_stmt st)
|
|
|
|
| Decl dec ->
|
|
|
|
(match Clang_ast_proj.get_decl_context_tuple dec with
|
|
|
|
| Some (decl_list, _) -> List.map ~f:(fun d -> Decl d) decl_list
|
|
|
|
| None -> [])
|
|
|
|
|
|
|
|
let node_to_string an =
|
|
|
|
match an with
|
|
|
|
| Decl d -> Clang_ast_proj.get_decl_kind_string d
|
|
|
|
| Stmt s -> Clang_ast_proj.get_stmt_kind_string s
|
|
|
|
|
|
|
|
let node_to_unique_string_id an =
|
|
|
|
match an with
|
|
|
|
| Decl d ->
|
|
|
|
let di = Clang_ast_proj.get_decl_tuple d in
|
|
|
|
(Clang_ast_proj.get_decl_kind_string d) ^ (string_of_int di.Clang_ast_t.di_pointer)
|
|
|
|
| Stmt s ->
|
|
|
|
let si, _ = Clang_ast_proj.get_stmt_tuple s in
|
|
|
|
Clang_ast_proj.get_stmt_kind_string s ^ (string_of_int si.Clang_ast_t.si_pointer)
|
|
|
|
|
|
|
|
(* true iff an ast node is a node of type among the list tl *)
|
|
|
|
let node_has_type tl an =
|
|
|
|
let an_alexp = ALVar.Const (node_to_string an) in
|
|
|
|
List.mem ~equal:ALVar.equal tl an_alexp
|
|
|
|
|
|
|
|
(* given a decl returns a stmt such that decl--->stmt via label trs *)
|
|
|
|
let transition_decl_to_stmt d trs =
|
|
|
|
let open Clang_ast_t in
|
|
|
|
let temp_res =
|
|
|
|
match trs, d with
|
|
|
|
| Body, ObjCMethodDecl (_, _, omdi) -> omdi.omdi_body
|
|
|
|
| Body, FunctionDecl (_, _, _, fdi)
|
|
|
|
| Body, CXXMethodDecl (_, _, _, fdi,_ )
|
|
|
|
| Body, CXXConstructorDecl (_, _, _, fdi, _)
|
|
|
|
| Body, CXXConversionDecl (_, _, _, fdi, _)
|
|
|
|
| Body, CXXDestructorDecl (_, _, _, fdi, _) -> fdi.fdi_body
|
|
|
|
| Body, BlockDecl (_, bdi) -> bdi.bdi_body
|
|
|
|
| InitExpr, VarDecl (_, _ ,_, vdi) -> vdi.vdi_init_expr
|
|
|
|
| InitExpr, ObjCIvarDecl (_, _, _, fldi, _)
|
|
|
|
| InitExpr, FieldDecl (_, _, _, fldi)
|
|
|
|
| InitExpr, ObjCAtDefsFieldDecl (_, _, _, fldi)-> fldi.fldi_init_expr
|
|
|
|
| InitExpr, CXXMethodDecl _
|
|
|
|
| InitExpr, CXXConstructorDecl _
|
|
|
|
| InitExpr, CXXConversionDecl _
|
|
|
|
| InitExpr, CXXDestructorDecl _ ->
|
|
|
|
assert false (* to be done. Requires extending to lists *)
|
|
|
|
| InitExpr, EnumConstantDecl (_, _, _, ecdi) -> ecdi.ecdi_init_expr
|
|
|
|
| _, _ -> None in
|
|
|
|
match temp_res with
|
|
|
|
| Some st -> [Stmt st]
|
|
|
|
| _ -> []
|
|
|
|
|
|
|
|
let transition_decl_to_decl_via_super d =
|
|
|
|
let decl_opt_to_ast_node_opt d_opt =
|
|
|
|
match d_opt with
|
|
|
|
| Some d' -> [Decl d']
|
|
|
|
| None -> [] in
|
|
|
|
let do_ObjCImplementationDecl d =
|
|
|
|
match CAst_utils.get_impl_decl_info d with
|
|
|
|
| Some idi ->
|
|
|
|
decl_opt_to_ast_node_opt (CAst_utils.get_super_ObjCImplementationDecl idi)
|
|
|
|
| None -> [] in
|
|
|
|
match d with
|
|
|
|
| Clang_ast_t.ObjCImplementationDecl _ ->
|
|
|
|
do_ObjCImplementationDecl d
|
|
|
|
| Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, idi) ->
|
|
|
|
decl_opt_to_ast_node_opt (CAst_utils.get_decl_opt_with_decl_ref idi.otdi_super)
|
|
|
|
| _ -> []
|
|
|
|
|
|
|
|
let transition_stmt_to_stmt_via_condition st =
|
|
|
|
let open Clang_ast_t in
|
|
|
|
match st with
|
|
|
|
| IfStmt (_, _ :: _ :: cond :: _)
|
|
|
|
| ConditionalOperator (_, cond:: _, _)
|
|
|
|
| ForStmt (_, [_; _; cond; _; _])
|
|
|
|
| WhileStmt (_, [_; cond; _]) -> [Stmt cond]
|
|
|
|
| _ -> []
|
|
|
|
|
|
|
|
let transition_stmt_to_decl_via_pointer stmt =
|
|
|
|
let open Clang_ast_t in
|
|
|
|
match stmt with
|
|
|
|
| ObjCMessageExpr (_, _, _, obj_c_message_expr_info) ->
|
|
|
|
(match CAst_utils.get_decl_opt obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer with
|
|
|
|
| Some decl -> [Decl decl]
|
|
|
|
| None -> [])
|
|
|
|
| DeclRefExpr (_, _, _, decl_ref_expr_info) ->
|
|
|
|
(match CAst_utils.get_decl_opt_with_decl_ref decl_ref_expr_info.Clang_ast_t.drti_decl_ref with
|
|
|
|
| Some decl -> [Decl decl]
|
|
|
|
| None -> [])
|
|
|
|
| _ -> []
|
|
|
|
|
|
|
|
let transition_decl_to_decl_via_parameters dec =
|
|
|
|
let open Clang_ast_t in
|
|
|
|
match dec with
|
|
|
|
| ObjCMethodDecl (_, _, omdi) ->
|
|
|
|
List.map ~f:(fun d -> Decl d) omdi.omdi_parameters
|
|
|
|
| _ -> []
|
|
|
|
|
|
|
|
(* 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 =
|
|
|
|
match an, trans with
|
|
|
|
| Decl d, Super -> transition_decl_to_decl_via_super d
|
|
|
|
| Decl d, Parameters -> transition_decl_to_decl_via_parameters d
|
|
|
|
| Decl d, InitExpr
|
|
|
|
| Decl d, Body -> transition_decl_to_stmt d trans
|
|
|
|
| Stmt st, Cond -> transition_stmt_to_stmt_via_condition st
|
|
|
|
| Stmt st, PointerToDecl -> transition_stmt_to_decl_via_pointer st
|
|
|
|
| _, _ -> []
|
|
|
|
|
|
|
|
(* Evaluation of formulas *)
|
|
|
|
|
|
|
|
(* evaluate an atomic formula (i.e. a predicate) on a ast node an and a
|
|
|
|
linter context lcxt. That is: an, lcxt |= pred_name(params) *)
|
|
|
|
let rec eval_Atomic _pred_name args an lcxt =
|
|
|
|
let pred_name = ALVar.formula_id_to_string _pred_name in
|
|
|
|
match pred_name, args, an with
|
|
|
|
| "call_class_method", [c; m], an -> CPredicates.call_class_method an c m
|
|
|
|
| "call_function", [m], an -> CPredicates.call_function an m
|
|
|
|
| "call_instance_method", [c; m], an -> CPredicates.call_instance_method an c m
|
|
|
|
| "call_method", [m], an -> CPredicates.call_method an m
|
|
|
|
| "captures_cxx_references", [], _ -> CPredicates.captures_cxx_references an
|
|
|
|
| "context_in_synchronized_block", [], _ -> CPredicates.context_in_synchronized_block lcxt
|
|
|
|
| "declaration_has_name", [decl_name], an -> CPredicates.declaration_has_name an decl_name
|
|
|
|
| "declaration_ref_name", [decl_name], an -> CPredicates.declaration_ref_name an decl_name
|
|
|
|
| "decl_unavailable_in_supported_ios_sdk", [], an ->
|
|
|
|
CPredicates.decl_unavailable_in_supported_ios_sdk lcxt an
|
|
|
|
| "has_cast_kind", [name], an -> CPredicates.has_cast_kind an name
|
|
|
|
| "has_type", [typ], an -> CPredicates.has_type an typ
|
|
|
|
| "isa", [classname], an -> CPredicates.isa an classname
|
|
|
|
| "is_assign_property", [], an -> CPredicates.is_assign_property an
|
|
|
|
| "is_binop_with_kind", [kind], an -> CPredicates.is_binop_with_kind an kind
|
|
|
|
| "is_class", [cname], an -> CPredicates.is_class an cname
|
|
|
|
| "is_const_var", [], an -> CPredicates.is_const_expr_var an
|
|
|
|
| "is_global_var", [], an -> CPredicates.is_syntactically_global_var an
|
|
|
|
| "is_ivar_atomic", [], an -> CPredicates.is_ivar_atomic an
|
|
|
|
| "is_method_property_accessor_of_ivar", [], an ->
|
|
|
|
CPredicates.is_method_property_accessor_of_ivar an lcxt
|
|
|
|
| "is_node", [nodename], an -> CPredicates.is_node an nodename
|
|
|
|
| "is_objc_constructor", [], _ -> CPredicates.is_objc_constructor lcxt
|
|
|
|
| "is_objc_dealloc", [], _ -> CPredicates.is_objc_dealloc lcxt
|
|
|
|
| "is_objc_extension", [], _ -> CPredicates.is_objc_extension lcxt
|
|
|
|
| "is_objc_interface_named", [name], an -> CPredicates.is_objc_interface_named an name
|
|
|
|
| "is_property_pointer_type", [], an -> CPredicates.is_property_pointer_type an
|
|
|
|
| "is_strong_property", [], an -> CPredicates.is_strong_property an
|
|
|
|
| "is_unop_with_kind", [kind], an -> CPredicates.is_unop_with_kind an kind
|
|
|
|
| "method_return_type", [typ], an -> CPredicates.method_return_type an typ
|
|
|
|
| "within_responds_to_selector_block", [], an ->
|
|
|
|
CPredicates.within_responds_to_selector_block lcxt an
|
|
|
|
| "objc_method_has_nth_parameter_of_type", [num; typ], an ->
|
|
|
|
CPredicates.objc_method_has_nth_parameter_of_type an num typ
|
|
|
|
| _ -> failwith
|
|
|
|
("ERROR: Undefined Predicate or wrong set of arguments: '"
|
|
|
|
^ pred_name ^ "'")
|
|
|
|
|
|
|
|
(* an, lcxt |= EF phi <=>
|
|
|
|
an, lcxt |= phi or exists an' in Successors(st): an', lcxt |= EF phi
|
|
|
|
|
|
|
|
That is: a (an, lcxt) satifies EF phi if and only if
|
|
|
|
either (an,lcxt) satifies phi or there is a child an' of the node an
|
|
|
|
such that (an', lcxt) satifies EF phi
|
|
|
|
*)
|
|
|
|
and eval_EF phi an lcxt trans =
|
|
|
|
match trans, an with
|
|
|
|
| Some _, _ ->
|
|
|
|
(* Using equivalence EF[->trans] phi = phi OR EX[->trans](EF[->trans] phi)*)
|
|
|
|
let phi' = Or (phi, EX (trans, EF (trans, phi))) in
|
|
|
|
eval_formula phi' an lcxt
|
|
|
|
| None, _ ->
|
|
|
|
eval_formula phi an lcxt
|
|
|
|
|| List.exists ~f:(fun an' -> eval_EF phi an' lcxt trans) (get_successor_nodes an)
|
|
|
|
|
|
|
|
(* an, lcxt |= EX phi <=> exists an' in Successors(st): an', lcxt |= phi
|
|
|
|
|
|
|
|
That is: a (an, lcxt) satifies EX phi if and only if
|
|
|
|
there exists is a child an' of the node an
|
|
|
|
such that (an', lcxt) satifies phi
|
|
|
|
*)
|
|
|
|
and eval_EX phi an lcxt trans =
|
|
|
|
let succs = match trans with
|
|
|
|
| Some l -> next_state_via_transition an l
|
|
|
|
| None -> get_successor_nodes an in
|
|
|
|
List.exists ~f:(fun an' -> eval_formula phi an' lcxt) succs
|
|
|
|
|
|
|
|
(* an, lcxt |= E(phi1 U phi2) evaluated using the equivalence
|
|
|
|
an, lcxt |= E(phi1 U phi2) <=> an, lcxt |= phi2 or (phi1 and EX(E(phi1 U phi2)))
|
|
|
|
|
|
|
|
That is: a (an,lcxt) satifies E(phi1 U phi2) if and only if
|
|
|
|
an,lcxt satifies the formula phi2 or (phi1 and EX(E(phi1 U phi2)))
|
|
|
|
*)
|
|
|
|
and eval_EU phi1 phi2 an lcxt trans =
|
|
|
|
let f = Or (phi2, And (phi1, EX (trans, (EU (trans, phi1, phi2))))) in
|
|
|
|
eval_formula f an lcxt
|
|
|
|
|
|
|
|
(* an |= A(phi1 U phi2) evaluated using the equivalence
|
|
|
|
an |= A(phi1 U phi2) <=> an |= phi2 or (phi1 and AX(A(phi1 U phi2)))
|
|
|
|
|
|
|
|
Same as EU but for the all path quantifier A
|
|
|
|
*)
|
|
|
|
and eval_AU phi1 phi2 an lcxt trans =
|
|
|
|
let f = Or (phi2, And (phi1, AX (trans, AU (trans, phi1, phi2)))) in
|
|
|
|
eval_formula f an lcxt
|
|
|
|
|
|
|
|
(* an, lcxt |= InNode[node_type_list] phi <=>
|
|
|
|
an is a node of type in node_type_list and an satifies phi
|
|
|
|
*)
|
|
|
|
and in_node node_type_list phi an lctx =
|
|
|
|
let holds_for_one_node n =
|
|
|
|
match lctx.CLintersContext.et_evaluation_node with
|
|
|
|
| Some id ->
|
|
|
|
(String.equal id (node_to_unique_string_id an)) && (eval_formula phi an lctx)
|
|
|
|
| None ->
|
|
|
|
(node_has_type [n] an) && (eval_formula phi an lctx) in
|
|
|
|
List.exists ~f:holds_for_one_node node_type_list
|
|
|
|
|
|
|
|
|
|
|
|
(* Intuitive meaning: (an,lcxt) satifies EH[Classes] phi
|
|
|
|
if the node an is among the declaration specified by the list Classes and
|
|
|
|
there exists a super class in its hierarchy whose declaration satisfy phi.
|
|
|
|
|
|
|
|
an, lcxt |= EH[Classes] phi <=>
|
|
|
|
the node an is in Classes and there exists a declaration d in Hierarchy(an)
|
|
|
|
such that d,lcxt |= phi *)
|
|
|
|
and eval_EH classes phi an lcxt =
|
|
|
|
(* Define EH[Classes] phi = ET[Classes](EF[->Super] phi) *)
|
|
|
|
let f = ET (classes, None, EX (Some Super, EF (Some Super, phi))) in
|
|
|
|
eval_formula f an lcxt
|
|
|
|
|
|
|
|
(* an, lcxt |= ET[T][->l]phi <=>
|
|
|
|
eventually we reach a node an' such that an' is among the types defined in T
|
|
|
|
and:
|
|
|
|
|
|
|
|
an'-l->an''
|
|
|
|
("an' transitions" to another node an'' via an edge labelled l)
|
|
|
|
and an'',lcxt |= phi
|
|
|
|
|
|
|
|
or l is unspecified and an,lcxt |= phi
|
|
|
|
*)
|
|
|
|
and eval_ET tl trs phi an lcxt =
|
|
|
|
let f = match trs with
|
|
|
|
| Some _ -> EF (None, (InNode (tl, EX (trs, phi))))
|
|
|
|
| None -> EF (None, (InNode (tl, phi))) in
|
|
|
|
eval_formula f an lcxt
|
|
|
|
|
|
|
|
and eval_ETX tl trs phi an lcxt =
|
|
|
|
let lcxt', tl' = match lcxt.CLintersContext.et_evaluation_node, node_has_type tl an with
|
|
|
|
| None, true ->
|
|
|
|
let an_alexp = ALVar.Const (node_to_string an) in
|
|
|
|
{lcxt with CLintersContext.et_evaluation_node = Some (node_to_unique_string_id an) }, [an_alexp]
|
|
|
|
| _, _ -> lcxt, tl in
|
|
|
|
let f = match trs with
|
|
|
|
| Some _ -> EF (None, (InNode (tl', EX (trs, phi))))
|
|
|
|
| None -> EF (None, (InNode (tl', phi))) in
|
|
|
|
eval_formula f an lcxt'
|
|
|
|
|
|
|
|
(* Formulas are evaluated on a AST node an and a linter context lcxt *)
|
|
|
|
and eval_formula f an lcxt =
|
|
|
|
debug_eval_begin (debug_create_payload an f lcxt);
|
|
|
|
let res = match f with
|
|
|
|
| True -> true
|
|
|
|
| False -> false
|
|
|
|
| Atomic (name, params) -> eval_Atomic name params an lcxt
|
|
|
|
| Not f1 -> not (eval_formula f1 an lcxt)
|
|
|
|
| And (f1, f2) -> (eval_formula f1 an lcxt) && (eval_formula f2 an lcxt)
|
|
|
|
| Or (f1, f2) -> (eval_formula f1 an lcxt) || (eval_formula f2 an lcxt)
|
|
|
|
| Implies (f1, f2) ->
|
|
|
|
not (eval_formula f1 an lcxt) || (eval_formula f2 an lcxt)
|
|
|
|
| InNode (node_type_list, f1) ->
|
|
|
|
in_node node_type_list f1 an lcxt
|
|
|
|
| AU (trans, f1, f2) -> eval_AU f1 f2 an lcxt trans
|
|
|
|
| EU (trans, f1, f2) -> eval_EU f1 f2 an lcxt trans
|
|
|
|
| EF (trans, f1) -> eval_EF f1 an lcxt trans
|
|
|
|
| AF (trans, f1) -> eval_formula (AU (trans, True, f1)) an lcxt
|
|
|
|
| AG (trans, f1) -> eval_formula (Not (EF (trans, (Not f1)))) an lcxt
|
|
|
|
| EX (trans, f1) -> eval_EX f1 an lcxt trans
|
|
|
|
| AX (trans, f1) -> eval_formula (Not (EX (trans, (Not f1)))) an lcxt
|
|
|
|
| EH (cl, phi) -> eval_EH cl phi an lcxt
|
|
|
|
| EG (trans, f1) -> (* st |= EG f1 <=> st |= f1 /\ EX EG f1 *)
|
|
|
|
eval_formula (And (f1, EX (trans, (EG (trans, f1))))) an lcxt
|
|
|
|
| ET (tl, sw, phi) -> eval_ET tl sw phi an lcxt
|
|
|
|
| ETX (tl, sw, phi) -> eval_ETX tl sw phi an lcxt in
|
|
|
|
debug_eval_end res;
|
|
|
|
res
|