From 7b52a054bf63f3b2fe85b8b91f4863c1ff8ebcaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20B=C3=BChler?= <david.buhler@cea.fr> Date: Thu, 25 Jun 2020 15:08:51 +0200 Subject: [PATCH] [Eva] Adds unit testing of the sign values semantics. Unit tests are in the new file utils/unit_tests.ml, and are run by the new test file tests/value/unit_tests.i through the exported function Eva.Unit_tests.test. --- Makefile | 1 + headers/header_spec.txt | 2 + .../abstract_interp/bottom.mli | 2 +- src/plugins/value/Eva.mli | 6 + src/plugins/value/utils/unit_tests.ml | 135 ++++++++++++++++++ src/plugins/value/utils/unit_tests.mli | 27 ++++ src/plugins/value/values/sign_value.mli | 8 +- tests/value/oracle/unit_tests.res.oracle | 2 + tests/value/unit_tests.i | 7 + tests/value/unit_tests.ml | 3 + 10 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/plugins/value/utils/unit_tests.ml create mode 100644 src/plugins/value/utils/unit_tests.mli create mode 100644 tests/value/oracle/unit_tests.res.oracle create mode 100644 tests/value/unit_tests.i create mode 100644 tests/value/unit_tests.ml diff --git a/Makefile b/Makefile index d6d044aebe2..5ff002277f9 100644 --- a/Makefile +++ b/Makefile @@ -910,6 +910,7 @@ PLUGIN_CMO:= partitioning/split_strategy domains/domain_mode value_parameters \ partitioning/partitioning_index partitioning/trace_partitioning \ engine/mem_exec engine/iterator engine/initialization \ engine/compute_functions engine/analysis register \ + utils/unit_tests \ $(APRON_CMO) $(NUMERORS_CMO) PLUGIN_CMI:= values/abstract_value values/abstract_location \ domains/abstract_domain domains/simpler_domains diff --git a/headers/header_spec.txt b/headers/header_spec.txt index 7fdecae8316..d43eeffc48a 100644 --- a/headers/header_spec.txt +++ b/headers/header_spec.txt @@ -1367,6 +1367,8 @@ src/plugins/value/utils/state_import.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/state_import.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/structure.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/structure.mli: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/utils/unit_tests.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/utils/unit_tests.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/value_perf.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/value_perf.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/value_results.ml: CEA_LGPL_OR_PROPRIETARY diff --git a/src/kernel_services/abstract_interp/bottom.mli b/src/kernel_services/abstract_interp/bottom.mli index 799ccedfc3f..0af283e70bf 100644 --- a/src/kernel_services/abstract_interp/bottom.mli +++ b/src/kernel_services/abstract_interp/bottom.mli @@ -42,7 +42,7 @@ val non_bottom: 'a or_bottom -> 'a val equal: ('a -> 'a -> bool) -> 'a or_bottom -> 'a or_bottom -> bool val compare: ('a -> 'a -> int) -> 'a or_bottom -> 'a or_bottom -> int -val is_included: ('a -> 'a -> bool) -> 'a or_bottom -> 'a or_bottom -> bool +val is_included: ('a -> 'b -> bool) -> 'a or_bottom -> 'b or_bottom -> bool val join: ('a -> 'a -> 'a) -> 'a or_bottom -> 'a or_bottom -> 'a or_bottom val join_list: ('a -> 'a -> 'a) -> 'a or_bottom list -> 'a or_bottom val narrow: ('a -> 'a -> 'a or_bottom) -> 'a or_bottom -> 'a or_bottom -> 'a or_bottom diff --git a/src/plugins/value/Eva.mli b/src/plugins/value/Eva.mli index 4de6198ed1a..a11a0dca9aa 100644 --- a/src/plugins/value/Eva.mli +++ b/src/plugins/value/Eva.mli @@ -60,3 +60,9 @@ module Eval_terms: sig @return None on either an evaluation error or on unsupported construct. *) val predicate_deps: eval_env -> Cil_types.predicate -> logic_deps option end + + +module Unit_tests: sig + (** Runs the unit tests of Eva. *) + val run: unit -> unit +end diff --git a/src/plugins/value/utils/unit_tests.ml b/src/plugins/value/utils/unit_tests.ml new file mode 100644 index 00000000000..043a2a726fa --- /dev/null +++ b/src/plugins/value/utils/unit_tests.ml @@ -0,0 +1,135 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2020 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Cil_types + +(* If true, prints each operation performed for the tests. Otherwise, only + prints wrong operations. *) +let print = false + +let report bug format = + if print || bug + then Value_parameters.result ("%s" ^^ format) (if bug then "BUG " else "") + else Format.ifprintf Format.std_formatter format + + +(* Tests the semantics of sign values, by comparison with the Ival semantics. *) +module Sign = struct + + module Sign = struct + include Sign_value + let zero = zero + let pos = one + let neg = inject_int Cil.uintType Integer.minus_one + let pos_or_zero = join zero pos + let neg_or_zero = join zero neg + let pos_neg = join pos neg + end + + module Ival = struct + include Ival + let positive = inject_range (Some Integer.one) None + let negative = inject_range None (Some Integer.minus_one) + let pos irange = + let max = Eval_typ.range_upper_bound irange in + inject_range (Some Integer.one) (Some max) + let neg irange = + let min = Eval_typ.range_lower_bound irange in + inject_range (Some min) (Some Integer.minus_one) + end + + module Cval = Main_values.CVal + + (* List of pairs (ival, sign) such that γ(ival) ⊆ γ(sign) and all values in + γ(sign) are in the range of [ikind]. *) + let test_values ikind = + let irange = Eval_typ.ik_range ikind in + let list = + [ Ival.zero, Sign.zero; + Ival.pos irange, Sign.pos; + Ival.zero, Sign.pos_or_zero; + Ival.pos irange, Sign.pos_or_zero; ] + in + if not irange.Eval_typ.i_signed + then list + else + list @ + [ Ival.neg irange, Sign.neg; + Ival.zero, Sign.neg_or_zero; + Ival.neg irange, Sign.neg_or_zero; + Ival.pos irange, Sign.pos_neg; + Ival.neg irange, Sign.pos_neg; ] + + let make_cvalue values = + List.map (fun (ival, sign) -> Cvalue.V.inject_ival ival, sign) values + + (* Checks whether γ(ival) ⊆ γ(sign). *) + let is_included cvalue sign = + try + let ival = Cvalue.V.project_ival cvalue in + (not (Ival.contains_zero ival) || sign.Sign.zero) && + (Ival.(is_bottom (narrow ival positive)) || sign.Sign.pos) && + (Ival.(is_bottom (narrow ival negative)) || sign.Sign.neg) + with Cvalue.V.Not_based_on_null -> Sign.(equal sign top) + + let test_unop unop typ values = + let test (cval, sign) = + let cval_res = Cval.forward_unop typ unop cval in + let sign_res = Sign.forward_unop typ unop sign in + let bug = not (Bottom.is_included is_included cval_res sign_res) in + report bug "%a %a = %a while %a %a = %a" + Printer.pp_unop unop Cval.pretty cval (Bottom.pretty Cval.pretty) cval_res + Printer.pp_unop unop Sign.pretty sign (Bottom.pretty Sign.pretty) sign_res + in + List.iter test values + + let test_binop binop typ values = + let test (cval1, sign1) (cval2, sign2) = + let cval_res = Cval.forward_binop typ binop cval1 cval2 in + let sign_res = Sign.forward_binop typ binop sign1 sign2 in + let bug = not (Bottom.is_included is_included cval_res sign_res) in + report bug "%a %a %a = %a while %a %a %a = %a" + Cval.pretty cval1 Printer.pp_binop binop Cval.pretty cval2 + (Bottom.pretty Cval.pretty) cval_res + Sign.pretty sign1 Printer.pp_binop binop Sign.pretty sign2 + (Bottom.pretty Sign.pretty) sign_res + in + List.iter (fun x -> List.iter (test x) values) values + + let test_typ ikind = + let typ = TInt (ikind, []) + and values = make_cvalue (test_values ikind) in + let apply f op = f op typ values in + List.iter (apply test_unop) [Neg; BNot; LNot]; + List.iter (apply test_binop) + [PlusA; MinusA; Mult; Div; BAnd; BOr; BXor; LAnd; LOr] + + let test () = List.iter test_typ [IInt; IUInt] +end + +(** Runs all tests. *) +let run () = + Value_parameters.result "Runs unit tests: %s." + (if print + then "all operations will be printed" + else "only faulty operations will be printed"); + Sign.test () diff --git a/src/plugins/value/utils/unit_tests.mli b/src/plugins/value/utils/unit_tests.mli new file mode 100644 index 00000000000..8783260b33a --- /dev/null +++ b/src/plugins/value/utils/unit_tests.mli @@ -0,0 +1,27 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2020 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(** Currently tested by this file: + - semantics of sign values. *) + +(** Runs some programmatic tests on Eva. *) +val run: unit -> unit diff --git a/src/plugins/value/values/sign_value.mli b/src/plugins/value/values/sign_value.mli index 055794019f7..94dd0faa838 100644 --- a/src/plugins/value/values/sign_value.mli +++ b/src/plugins/value/values/sign_value.mli @@ -22,6 +22,12 @@ (** Sign domain: abstraction of integer numerical values by their signs. *) -include Abstract_value.Leaf +type signs = { + pos: bool; (** true: maybe positive, false: never positive *) + zero: bool; (** true: maybe zero, false: never zero *) + neg: bool; (** true: maybe negative, false: never negative *) +} + +include Abstract_value.Leaf with type t = signs val pretty_debug: t Pretty_utils.formatter diff --git a/tests/value/oracle/unit_tests.res.oracle b/tests/value/oracle/unit_tests.res.oracle new file mode 100644 index 00000000000..35ce0d1ac58 --- /dev/null +++ b/tests/value/oracle/unit_tests.res.oracle @@ -0,0 +1,2 @@ +[kernel] Parsing tests/value/unit_tests.i (no preprocessing) +[eva] Runs unit tests: only faulty operations will be printed. diff --git a/tests/value/unit_tests.i b/tests/value/unit_tests.i new file mode 100644 index 00000000000..cabb2876892 --- /dev/null +++ b/tests/value/unit_tests.i @@ -0,0 +1,7 @@ +/* run.config + EXECNOW: make -s @PTEST_DIR@/@PTEST_NAME@.cmxs + OPT: -load-module @PTEST_DIR@/@PTEST_NAME@ +*/ +/* run.config* + DONTRUN: +*/ diff --git a/tests/value/unit_tests.ml b/tests/value/unit_tests.ml new file mode 100644 index 00000000000..48e3b3405bc --- /dev/null +++ b/tests/value/unit_tests.ml @@ -0,0 +1,3 @@ +(* Programmatic tests of Eva, run by unit_tests.i. *) + +let () = Db.Main.extend Eva.Unit_tests.run -- GitLab