Reviewed By: dkgi Differential Revision: D2997610 fb-gh-sync-id: 733b45e shipit-source-id: 733b45emaster
parent
450b97b9d7
commit
ddfd35107c
@ -0,0 +1,167 @@
|
||||
(*
|
||||
* Copyright (c) 2015 - 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.
|
||||
*)
|
||||
|
||||
module F = Format
|
||||
|
||||
(** Test the generic abstract interpreter by using a simple path counting domain. Path counting is
|
||||
actually a decent stress test--if you join too much/too little, you'll over/under-count, and
|
||||
you'll diverge at loops if you don't widen *)
|
||||
|
||||
module PathCountDomain = struct
|
||||
|
||||
type astate =
|
||||
| PathCount of int
|
||||
| Top
|
||||
|
||||
let make_path_count c =
|
||||
(* guarding against overflow *)
|
||||
if c < 0
|
||||
then Top
|
||||
else PathCount c
|
||||
|
||||
let bot = make_path_count 0
|
||||
|
||||
let init = make_path_count 1
|
||||
|
||||
let is_bot = function
|
||||
| PathCount c -> c = 0
|
||||
| Top -> false
|
||||
|
||||
let lteq ~lhs ~rhs = match lhs, rhs with
|
||||
| PathCount c1, PathCount c2 -> c1 <= c2
|
||||
| _, Top -> true
|
||||
| Top, PathCount _ -> false
|
||||
|
||||
let join a1 a2 = match a1, a2 with
|
||||
| PathCount c1, PathCount c2 -> make_path_count (c1 + c2)
|
||||
| Top, _ | PathCount _, Top -> Top
|
||||
|
||||
let widen ~prev:_ ~next:_ ~num_iters:_ = Top
|
||||
|
||||
let pp fmt = function
|
||||
| PathCount c -> F.fprintf fmt "%d" c
|
||||
| Top -> F.fprintf fmt "T"
|
||||
|
||||
end
|
||||
|
||||
module PathCountTransferFunctions = struct
|
||||
type astate = PathCountDomain.astate
|
||||
|
||||
(* just propagate the current path count *)
|
||||
let exec_instr astate _ = astate
|
||||
end
|
||||
|
||||
|
||||
module TestInterpreter = AnalyzerTester.Make
|
||||
(ProcCfg.Forward)
|
||||
(Scheduler.ReversePostorder)
|
||||
(PathCountDomain)
|
||||
(PathCountTransferFunctions)
|
||||
|
||||
let tests =
|
||||
let open OUnit2 in
|
||||
let open AnalyzerTester.StructuredSil in
|
||||
let test_list = [
|
||||
"straightline",
|
||||
[
|
||||
invariant "1";
|
||||
invariant "1"
|
||||
];
|
||||
"if",
|
||||
[
|
||||
invariant "1";
|
||||
If (unknown_exp, [], []);
|
||||
invariant "2";
|
||||
];
|
||||
"if_then",
|
||||
[
|
||||
If (unknown_exp,
|
||||
[invariant "1"],
|
||||
[]
|
||||
);
|
||||
invariant "2"
|
||||
];
|
||||
"if_else",
|
||||
[
|
||||
If (unknown_exp,
|
||||
[],
|
||||
[invariant "1"]
|
||||
);
|
||||
invariant "2"
|
||||
];
|
||||
"if_then_else",
|
||||
[
|
||||
If (unknown_exp,
|
||||
[invariant "1"],
|
||||
[invariant "1"];
|
||||
);
|
||||
invariant "2"
|
||||
];
|
||||
"nested_if_then",
|
||||
[
|
||||
If (unknown_exp,
|
||||
[If (unknown_exp, [], []);
|
||||
invariant "2"],
|
||||
[]
|
||||
);
|
||||
invariant "3"
|
||||
];
|
||||
"nested_if_else",
|
||||
[
|
||||
If (unknown_exp,
|
||||
[],
|
||||
[If (unknown_exp, [], []);
|
||||
invariant "2"]
|
||||
);
|
||||
invariant "3"
|
||||
];
|
||||
"nested_if_then_else",
|
||||
[
|
||||
If (unknown_exp,
|
||||
[If (unknown_exp, [], []);
|
||||
invariant "2"],
|
||||
[If (unknown_exp, [], []);
|
||||
invariant "2"]
|
||||
);
|
||||
invariant "4"
|
||||
];
|
||||
"if_diamond",
|
||||
[
|
||||
invariant "1";
|
||||
If (unknown_exp, [], []);
|
||||
invariant "2";
|
||||
If (unknown_exp, [], []);
|
||||
invariant "4"
|
||||
];
|
||||
"loop",
|
||||
[
|
||||
invariant "1";
|
||||
While (unknown_exp, [invariant "T"]);
|
||||
invariant "T"
|
||||
];
|
||||
"if_in_loop",
|
||||
[
|
||||
While (unknown_exp,
|
||||
[If (unknown_exp, [], []);
|
||||
invariant "T"]
|
||||
);
|
||||
invariant "T";
|
||||
];
|
||||
"nested_loop_visit",
|
||||
[
|
||||
invariant "1";
|
||||
While (unknown_exp,
|
||||
[invariant "T";
|
||||
While (unknown_exp,
|
||||
[invariant "T"]);
|
||||
invariant "T"]);
|
||||
invariant "T";
|
||||
];
|
||||
] |> TestInterpreter.create_tests in
|
||||
"analyzer_tests_suite">:::test_list
|
@ -0,0 +1,189 @@
|
||||
(*
|
||||
* 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.
|
||||
*)
|
||||
|
||||
module F = Format
|
||||
module L = Logging
|
||||
|
||||
open AbstractDomain
|
||||
open TransferFunctions
|
||||
|
||||
(** utilities for writing abstract domains/transfer function tests *)
|
||||
|
||||
(** structured language that makes it easy to write small test programs in OCaml *)
|
||||
module StructuredSil = struct
|
||||
type assertion = string
|
||||
type label = int
|
||||
|
||||
type structured_instr =
|
||||
| Cmd of Sil.instr
|
||||
| If of Sil.exp * structured_instr list * structured_instr list
|
||||
| While of Sil.exp * structured_instr list
|
||||
| Invariant of assertion * label (* gets autotranslated into assertions about abstract state *)
|
||||
|
||||
type structured_program = structured_instr list
|
||||
|
||||
let rec pp_structured_instr fmt = function
|
||||
| Cmd instr -> (Sil.pp_instr pe_text) fmt instr
|
||||
| If (exp, then_instrs, else_instrs) ->
|
||||
(* TODO (t10287763): indent bodies of if/while *)
|
||||
F.fprintf fmt "if (%a) {@.%a@.} else {@.%a@.}"
|
||||
(Sil.pp_exp pe_text) exp
|
||||
pp_structured_instr_list then_instrs
|
||||
pp_structured_instr_list else_instrs
|
||||
| While (exp, instrs) ->
|
||||
F.fprintf fmt "while (%a) {@.%a@.}" (Sil.pp_exp pe_text) exp pp_structured_instr_list instrs
|
||||
| Invariant (inv_str, label) ->
|
||||
F.fprintf fmt "invariant %d: %s" label inv_str
|
||||
|
||||
and pp_structured_instr_list fmt instrs =
|
||||
F.pp_print_list
|
||||
~pp_sep:F.pp_print_newline
|
||||
(fun fmt instr -> F.fprintf fmt "%a" pp_structured_instr instr)
|
||||
fmt
|
||||
instrs
|
||||
|
||||
let pp_structured_program = pp_structured_instr_list
|
||||
|
||||
let dummy_typ = Sil.Tvoid
|
||||
let dummy_loc = Location.dummy
|
||||
let dummy_procname = Procname.empty
|
||||
|
||||
let label_counter = ref 0
|
||||
|
||||
let fresh_label () =
|
||||
incr label_counter;
|
||||
!label_counter
|
||||
|
||||
let invariant inv_str =
|
||||
Invariant (inv_str, fresh_label ())
|
||||
|
||||
let var_of_str str =
|
||||
Sil.Lvar (Sil.mk_pvar (Mangled.from_string str) dummy_procname)
|
||||
|
||||
let unknown_exp =
|
||||
var_of_str "__unknown__"
|
||||
|
||||
end
|
||||
|
||||
module Make
|
||||
(C : ProcCfg.Wrapper with type node = Cfg.Node.t)
|
||||
(S : Scheduler.S)
|
||||
(A : AbstractDomain)
|
||||
(T : TransferFunctions with type astate = A.astate) = struct
|
||||
|
||||
open StructuredSil
|
||||
|
||||
module I = AbstractInterpreter.Make (C) (S) (A) (T)
|
||||
module M = ProcCfg.NodeIdMap (C)
|
||||
|
||||
type assert_map = string M.t
|
||||
|
||||
let structured_program_to_cfg program =
|
||||
let cfg = Cfg.Node.create_cfg () in
|
||||
let pdesc =
|
||||
Cfg.Procdesc.create cfg (ProcAttributes.default dummy_procname !Config.curr_language) in
|
||||
|
||||
let create_node kind cmds =
|
||||
let no_tmp_idents = [] in
|
||||
Cfg.Node.create cfg dummy_loc kind cmds pdesc no_tmp_idents in
|
||||
let set_succs cur_node succs =
|
||||
let no_exc_succs = [] in
|
||||
Cfg.Node.set_succs_exn cur_node succs no_exc_succs in
|
||||
let mk_prune_nodes_for_cond cond_exp if_kind =
|
||||
let mk_prune_node cond_exp if_kind true_branch =
|
||||
let prune_instr = Sil.Prune (cond_exp, dummy_loc, true_branch, if_kind) in
|
||||
create_node (Cfg.Node.Prune_node (true_branch, if_kind, "")) [prune_instr] in
|
||||
let true_prune_node = mk_prune_node cond_exp if_kind true in
|
||||
let false_prune_node =
|
||||
let negated_cond_exp = Sil.UnOp (Sil.LNot, cond_exp, None) in
|
||||
mk_prune_node negated_cond_exp if_kind false in
|
||||
true_prune_node, false_prune_node in
|
||||
|
||||
let rec structured_instr_to_node (last_node, assert_map) = function
|
||||
| Cmd cmd ->
|
||||
let node = create_node (Cfg.Node.Stmt_node "") [cmd] in
|
||||
set_succs last_node [node];
|
||||
node, assert_map
|
||||
| If (exp, then_instrs, else_instrs) ->
|
||||
let then_prune_node, else_prune_node = mk_prune_nodes_for_cond exp Sil.Ik_if in
|
||||
set_succs last_node [then_prune_node; else_prune_node];
|
||||
let then_branch_end_node, assert_map' =
|
||||
structured_instrs_to_node then_prune_node assert_map then_instrs in
|
||||
let else_branch_end_node, assert_map'' =
|
||||
structured_instrs_to_node else_prune_node assert_map' else_instrs in
|
||||
let join_node = create_node Cfg.Node.Join_node [] in
|
||||
set_succs then_branch_end_node [join_node];
|
||||
set_succs else_branch_end_node [join_node];
|
||||
join_node, assert_map''
|
||||
| While (exp, body_instrs) ->
|
||||
let loop_head_join_node = create_node Cfg.Node.Join_node [] in
|
||||
set_succs last_node [loop_head_join_node];
|
||||
let true_prune_node, false_prune_node = mk_prune_nodes_for_cond exp Sil.Ik_while in
|
||||
set_succs loop_head_join_node [true_prune_node; false_prune_node];
|
||||
let loop_body_end_node, assert_map' =
|
||||
structured_instrs_to_node true_prune_node assert_map body_instrs in
|
||||
let loop_exit_node = create_node (Cfg.Node.Skip_node "") [] in
|
||||
set_succs loop_body_end_node [loop_head_join_node];
|
||||
set_succs false_prune_node [loop_exit_node];
|
||||
loop_exit_node, assert_map'
|
||||
| Invariant (inv_str, inv_label) ->
|
||||
let n_id = C.node_id last_node in
|
||||
(* add the assertion to be checked after analysis converges *)
|
||||
last_node, M.add n_id (inv_str, inv_label) assert_map
|
||||
and structured_instrs_to_node last_node assert_map instrs =
|
||||
IList.fold_left
|
||||
(fun acc instr -> structured_instr_to_node acc instr) (last_node, assert_map) instrs in
|
||||
|
||||
let start_node = create_node (Cfg.Node.Start_node pdesc) [] in
|
||||
Cfg.Procdesc.set_start_node pdesc start_node;
|
||||
let last_node, assert_map = structured_instrs_to_node start_node M.empty program in
|
||||
let exit_node = create_node (Cfg.Node.Exit_node pdesc) [] in
|
||||
set_succs last_node [exit_node];
|
||||
Cfg.Procdesc.set_exit_node pdesc exit_node;
|
||||
pdesc, assert_map
|
||||
|
||||
let create_test test_program _ =
|
||||
let pdesc, assert_map = structured_program_to_cfg test_program in
|
||||
let inv_map = I.exec_pdesc pdesc in
|
||||
|
||||
let collect_invariant_mismatches node_id (inv_str, inv_label) error_msgs_acc =
|
||||
let node_id_post =
|
||||
try
|
||||
let state = M.find node_id inv_map in
|
||||
state.post
|
||||
with Not_found -> A.bot in
|
||||
let post_str = pp_to_string A.pp node_id_post in
|
||||
if inv_str <> post_str then
|
||||
let error_msg =
|
||||
F.fprintf F.str_formatter
|
||||
"> Expected state %s at invariant %d, but found state %s"
|
||||
inv_str inv_label post_str
|
||||
|> F.flush_str_formatter in
|
||||
error_msg :: error_msgs_acc
|
||||
else error_msgs_acc in
|
||||
|
||||
match M.fold collect_invariant_mismatches assert_map [] with
|
||||
| [] -> () (* no mismatches, test passed *)
|
||||
| error_msgs ->
|
||||
let mismatches_str =
|
||||
F.pp_print_list
|
||||
(fun fmt error_msg -> F.fprintf fmt "%s" error_msg) F.str_formatter
|
||||
(IList.rev error_msgs)
|
||||
|> F.flush_str_formatter in
|
||||
let assert_fail_message =
|
||||
F.fprintf F.str_formatter "Error while analyzing@.%a:@.%s@."
|
||||
pp_structured_program test_program mismatches_str
|
||||
|> F.flush_str_formatter in
|
||||
OUnit2.assert_failure assert_fail_message
|
||||
|
||||
let create_tests tests =
|
||||
let open OUnit2 in
|
||||
IList.map (fun (name, test_program) -> name>::create_test test_program) tests
|
||||
|
||||
end
|
Loading…
Reference in new issue