Skip to content
Snippets Groups Projects
alarmset.ml 15.60 KiB
(**************************************************************************)
(*                                                                        *)
(*  This file is part of Frama-C.                                         *)
(*                                                                        *)
(*  Copyright (C) 2007-2021                                               *)
(*    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

module M = Alarms.Map

type alarm = Alarms.t
type status = Abstract_interp.Comp.result = True | False | Unknown
type s = status M.t
type t = Just of s | AllBut of s

type 'a if_consistent = [ `Value of 'a | `Inconsistent ]

let string_of_predicate_status = function
  | Unknown -> "unknown"
  | True -> "valid"
  | False -> "invalid"

let pretty_status fmt v =
  Format.fprintf fmt "%s" (string_of_predicate_status v)

module Status = struct
  include Datatype.Make_with_collections
      (struct
        type t = status
        include Datatype.Serializable_undefined
        let name = "Alarmset.status"
        let reprs = [ True; False; False; Unknown ]
        let mem_project = Datatype.never_any_project
        let pretty = pretty_status
        let compare (s1:t) (s2:t) = Stdlib.compare s1 s2
        let equal (s1:t) (s2:t) = s1 = s2
        let hash (s:t) = Hashtbl.hash s
      end)

  let join x y = match x, y with
    | True, True -> True
    | False, False -> False
    | True, False | False, True
    | Unknown, _ | _, Unknown -> Unknown

  let inter x y = match x, y with
    | Unknown, _ -> `Value y
    | _, Unknown -> `Value x
    | True, True -> `Value True
    | False, False -> `Value False
    | True, False | False, True -> `Inconsistent

  exception Stop
  let join_list l =
    try
      let r =
        List.fold_left
          (fun acc e ->
             match e, acc with
             | Unknown, _ -> raise Stop
             | e, None -> Some e
             | e, Some eacc -> Some (join eacc e)
          ) None l
      in
      match r with
      | None -> True
      | Some r -> r
    with Stop -> Unknown
end

module D = Alarms.Map.Make (Status)

let pretty fmt = function
  | Just m   -> Format.fprintf fmt "Just %a" D.pretty m
  | AllBut m -> Format.fprintf fmt "AllBut %a" D.pretty m

let none = Just M.empty
let all = AllBut M.empty

let equal a b = match a, b with
  | Just m, Just n
  | AllBut m, AllBut n -> M.equal Status.equal m n
  | _, _ -> false

let get = function Just m | AllBut m -> m

let is_empty = function
  | AllBut _ -> false
  | Just m -> M.is_empty m

let singleton ?(status=Unknown) a =
  if status = True then Just M.empty else Just (M.singleton a status)

let set alarm status = function
  | Just m   ->
    if status = True
    then Just (M.remove alarm m)
    else Just (M.add alarm status m)
  | AllBut m ->
    if status = Unknown
    then AllBut (M.remove alarm m)
    else AllBut (M.add alarm status m)

let default = function
  | Just _ -> True
  | AllBut _ -> Unknown

let find alarm set =
  try match set with
    | Just m
    | AllBut m -> M.find alarm m
  with Not_found -> default set

(* Merges two alarm maps [s1] and [s2], using [merge_status] to merge merge the
   statuses bound to a same alarm.

   If [combine] is true, both alarm maps [s1] and [s2] have been produced in
   the same state but for different expressions [e1] and [e2]: they may contain
   different alarms, but should always have the same status bound to a same
   alarm. Do not use the default status for alarms in [s1] missing in [s2] (and
   vice versa): such alarms may have no meaning for [e2], and should have the
   same status in [s2] than in [s1] anyway.
   If [combine] is false, both maps come from the evaluation of the same
   expression in different states. In this case, use the default status for any
   alarm missing in one side. *)
let merge ~combine merge_status s1 s2 =
  let d1 = default s1 and d2 = default s2 in
  let m1 = get s1 and m2 = get s2 in
  let closed_set = match merge_status d1 d2 with
    | True -> true
    | Unknown -> false
    | False -> assert false
  in
  let return = if closed_set
    then function True -> None | p -> Some p
    else function Unknown -> None | p -> Some p
  in
  let merge _ p1 p2 = match p1, p2 with
    | None, None -> assert false
    | Some p, None -> if combine then Some p else return (merge_status p d2)
    | None, Some p -> if combine then Some p else return (merge_status d1 p)
    | Some p1, Some p2 -> return (merge_status p1 p2)
  in
  if closed_set
  then Just (M.merge merge m1 m2)
  else AllBut (M.merge merge m1 m2)

let union = merge ~combine:false Status.join

exception Inconsistent
let intersect status1 status2 = match Status.inter status1 status2 with
  | `Value status -> status
  | `Inconsistent -> raise Inconsistent

let inter s1 s2 =
  try `Value (merge ~combine:false intersect s1 s2)
  with Inconsistent -> `Inconsistent

let combine s1 s2 =
  (* [intersect] is only applied if both maps explicitely contain the same
     alarm. As both maps have been produced in the same state, the statuses for
     this alarm should be consistent. *)
  try merge ~combine:true intersect s1 s2
  with Inconsistent ->
    Value_parameters.fatal ~current:true
      "Inconsistent combination of two alarm maps %a and %a."
      pretty s1 pretty s2

let iter f = function
  | Just m -> M.iter f m
  | AllBut _ -> assert false

let exists test ~default = function
  | Just m -> M.exists test m || default True
  | AllBut m -> M.exists test m || default Unknown

let for_all test ~default = function
  | Just m -> M.for_all test m && default True
  | AllBut m -> M.for_all test m && default Unknown


(* --------------------------------------------------------------------------
                                 Alarms
     ------------------------------------------------------------------------ *)

open CilE

let emitter = Value_util.emitter

(* Printer that shows additional information about temporaries *)
let local_printer: Printer.extensible_printer =
  let open Cil_types in object (self)
    inherit Printer.extensible_printer () as super

    (* Temporary variables for which we want to print more information *)
    val mutable temporaries = Cil_datatype.Varinfo.Set.empty

    method! code_annotation fmt ca =
      temporaries <- Cil_datatype.Varinfo.Set.empty;
      match ca.annot_content with
      | AAssert (_, p) ->
        (* ignore the ACSL name *)
        let p = p.tp_statement.pred_content in
        Format.fprintf fmt "@[<v>@[assert@ %a;@]" self#predicate_node p;
        (* print temporary variables information *)
        if not (Cil_datatype.Varinfo.Set.is_empty temporaries) then begin
          Format.fprintf fmt "@ @[(%t)@]" self#pp_temporaries
        end;
        Format.fprintf fmt "@]";
      | _ -> assert false

    method private pp_temporaries fmt =
      let pp_var fmt vi =
        Format.fprintf fmt "%s from@ @[%s@]" vi.vname (Option.get vi.vdescr)
      in
      Pretty_utils.pp_iter Cil_datatype.Varinfo.Set.iter
        ~pre:"" ~suf:"" ~sep:",@ " pp_var fmt temporaries

    method! logic_var fmt lvi =
      (match lvi.lv_origin with
       | None | Some { vdescr = None }-> ()
       | Some ({ vdescr = Some _ } as vi) ->
         temporaries <- Cil_datatype.Varinfo.Set.add vi temporaries
      );
      super#logic_var fmt lvi
  end

let pr_annot = local_printer#code_annotation

(* Default behaviour: print one alarm per kinstr. *)
module Alarm_key =
  Datatype.Pair_with_collections (Cil_datatype.Kinstr) (Alarms)
    (struct
      let module_name = "Alarm_key"
    end)

module Alarm_cache =
  State_builder.Hashtbl (Alarm_key.Hashtbl) (Datatype.Unit)
    (struct
      let name = "Value_messages.Alarm_cache"
      let dependencies = [Db.Value.self]
      let size = 35
    end)

let loc = function
  | Cil_types.Kglobal -> (* can occur in case of obscure bugs (already happened)
                            with wacky initializers. Module Initial_state of
                            value analysis correctly positions the loc *)
    Cil.CurrentLoc.get ()
  | Cil_types.Kstmt s -> Cil_datatype.Stmt.loc s

let report_alarm ki annot msg =
  Value_util.alarm_report ~source:(fst (loc ki)) "@[%s.@ @[<hov 2>%a@]@]%t"
    msg pr_annot annot Value_util.pp_callstack

let string_fkind = function
  | Cil_types.FFloat -> "float"
  | Cil_types.FDouble -> "double"
  | Cil_types.FLongDouble -> "long double"

(** Emitting alarms *)
let register_alarm ki alarm status str =
  let status = match status with
    | True -> Property_status.True
    | False ->
      (* We cannot soundly emit a red status on an alarm, because we may have
         emitted a green status earlier on. Thus we store the information
         for logging purposes, and emit an Unknown status. *)
      Red_statuses.add_red_alarm ki alarm;
      Property_status.Dont_know
    | Unknown -> Property_status.Dont_know
  in
  let annot, _is_new =
    Alarms.register ~loc:(loc ki) ~status emitter ki alarm
  in
  (* Report each alarm only once per analysis. The boolean [is_new] returned
     by {{Alarms.register}} is inadequate, as an alarm emitted by another
     plugin or by a previous run of Eva would be considered as not new. *)
  Alarm_cache.memo (fun (_ki,_alarm) -> report_alarm ki annot str) (ki, alarm)

let emit_alarm kinstr alarm (status:status) =
  let register_alarm = register_alarm kinstr alarm status in
  match alarm with
  | Alarms.Pointer_comparison (_, _) -> register_alarm "pointer comparison"

  | Alarms.Division_by_zero _ -> register_alarm "division by zero"

  | Alarms.Overflow (kind, _, _, _) ->
    let str = match kind with
      | Alarms.Signed -> "signed overflow"
      | Alarms.Unsigned -> "unsigned overflow"
      | Alarms.Signed_downcast -> "signed downcast"
      | Alarms.Unsigned_downcast -> "unsigned downcast"
      | Alarms.Pointer_downcast -> "pointer downcast"
    in
    register_alarm str

  | Alarms.Float_to_int _ ->
    register_alarm "overflow in conversion from floating-point to integer"

  | Alarms.Invalid_shift (_, Some _) ->
    register_alarm "invalid RHS operand for shift"

  | Alarms.Invalid_shift (_, None) ->
    register_alarm "invalid LHS operand for left shift"

  | Alarms.Memory_access (_, access_kind) ->
    let access = match access_kind with
      | Alarms.For_reading -> "read"
      | Alarms.For_writing -> "write"
    in
    register_alarm (Format.sprintf "out of bounds %s" access)

  | Alarms.Index_out_of_bound _ ->
    register_alarm "accessing out of bounds index"

  | Alarms.Invalid_pointer _ ->
    register_alarm "invalid pointer creation"

  | Alarms.Differing_blocks _ ->
    register_alarm "pointer subtraction"

  | Alarms.Is_nan_or_infinite (_, fkind) ->
    let sfkind = string_fkind fkind in
    register_alarm (Format.sprintf "non-finite %s value" sfkind)

  | Alarms.Is_nan (_, fkind) ->
    let sfkind = string_fkind fkind in
    register_alarm (Format.sprintf "NaN %s value" sfkind)

  | Alarms.Uninitialized _ ->
    register_alarm "accessing uninitialized left-value"

  | Alarms.Dangling _ ->
    register_alarm "accessing left-value that contains escaping addresses"

  | Alarms.Not_separated _ ->
    register_alarm "undefined multiple accesses in expression"

  | Alarms.Overlap _ ->
    register_alarm "partially overlapping lvalue assignment"

  | Alarms.Function_pointer _ ->
    register_alarm  "pointer to function with incompatible type"

  | Alarms.Uninitialized_union _ ->
    register_alarm "accessing uninitialized union"

  | Alarms.Invalid_bool _ ->
    register_alarm "trap representation of a _Bool lvalue"

let height_alarm = let open Value_util in function
    | Alarms.Division_by_zero e
    | Alarms.Index_out_of_bound (e,_)
    | Alarms.Invalid_pointer e
    | Alarms.Invalid_shift (e,_)
    | Alarms.Overflow (_,e,_,_)
    | Alarms.Float_to_int (e,_,_)
    | Alarms.Function_pointer (e, _)
    | Alarms.Pointer_comparison (None,e) -> height_expr e + 2
    | Alarms.Memory_access (lv,_)
    | Alarms.Dangling lv
    | Alarms.Invalid_bool lv -> height_lval lv + 1
    | Alarms.Uninitialized lv -> height_lval lv
    | Alarms.Pointer_comparison (Some e1, e2) ->
      max (height_expr e1) (height_expr e2) + 2
    | Alarms.Differing_blocks (e1, e2) ->
      max (height_expr e1) (height_expr e2) + 1
    | Alarms.Not_separated (lv1,lv2)
    | Alarms.Overlap (lv1,lv2) -> max (height_lval lv1) (height_lval lv2) + 1
    | Alarms.Is_nan_or_infinite (e, fkind)
    | Alarms.Is_nan (e, fkind) ->
      let trivial = match Cil.typeOf e with
        | TFloat (fk, _) -> fk = fkind
        | _ -> false
      in
      if trivial then height_expr e else height_expr e + 1
    | Alarms.Uninitialized_union llv ->
      List.fold_left max 0 (List.map height_lval llv)

let cmp a1 a2 =
  Datatype.Int.compare (height_alarm (fst a1)) (height_alarm (fst a2))

let remove_redundant_alarms map =
  let filter alarm status =
    match alarm with
    | Alarms.Invalid_pointer expr ->
      let lval = Mem expr, NoOffset in
      let implies alarm s =
        status = s &&
        match alarm with
        | Alarms.Memory_access (lv, _access_kind) ->
          Cil_datatype.LvalStructEq.equal lv lval
        | _ -> false
      in
      not (M.exists implies map)
    | _ -> true
  in
  M.filter filter map

let emit_alarms kinstr map =
  let map = remove_redundant_alarms map in
  let list = M.bindings map in
  let sorted_list = List.sort cmp list in
  List.iter (fun (alarm, status) -> emit_alarm kinstr alarm status) sorted_list;
  if Alarm_cache.length () >= Value_parameters.StopAtNthAlarm.get ()
  then begin
    Value_parameters.log "Stopping at nth alarm" ;
    raise Db.Value.Aborted
  end

let emit kinstr = function
  | Just map -> if not (M.is_empty map) then emit_alarms kinstr map
  (* TODO: use GADT to avoid this assert false ? *)
  | AllBut  _ ->
    Value_parameters.abort ~current:true ~once:true
      "All alarms may arise: \
       abstract state too imprecise to continue the analysis."

let warn_alarm warn_mode = function
  | Alarms.Uninitialized _
  | Alarms.Dangling _
    -> warn_mode.unspecified ()
  | Alarms.Pointer_comparison _
  | Alarms.Differing_blocks _
    -> warn_mode.defined_logic ()
  | Alarms.Division_by_zero _
  | Alarms.Overflow _
  | Alarms.Float_to_int _
  | Alarms.Invalid_shift _
  | Alarms.Memory_access _
  | Alarms.Index_out_of_bound _
  | Alarms.Invalid_pointer _
  | Alarms.Is_nan_or_infinite _
  | Alarms.Is_nan _
  | Alarms.Not_separated _
  | Alarms.Overlap _
  | Alarms.Function_pointer _
  | Alarms.Uninitialized_union _
  | Alarms.Invalid_bool _
    -> warn_mode.others ()

let notify warn_mode alarms =
  iter (fun alarm _status -> warn_alarm warn_mode alarm) alarms


(*
Local Variables:
compile-command: "make -C ../../.."
End:
*)