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

(** This file contains attribute related types/functions/values. *)

open Cil_types

let bitfield_attribute_name = "FRAMA_C_BITFIELD_SIZE"

let anonymous_attribute_name = "fc_anonymous"

let anonymous_attribute = Attr(anonymous_attribute_name, [])

let qualifier_attributes = [ "const"; "restrict"; "volatile"; "ghost" ]

let fc_internal_attributes = ["declspec"; "arraylen"; "fc_stdlib"]

let cast_irrelevant_attributes = ["visibility"]

let spare_attributes_for_c_cast = fc_internal_attributes @ qualifier_attributes

let spare_attributes_for_logic_cast = spare_attributes_for_c_cast

(** Construct sorted lists of attributes ***)
let attribute_name (Attr(an, _) | AttrAnnot an) =
  Extlib.strip_underscore an

let has_attribute an al =
  let an = Extlib.strip_underscore an in
  List.exists (fun a -> attribute_name a = an) al

(* Attributes are added as they are (e.g. if we add ["__attr"] and then ["attr"]
   both are added). When checking for the presence of an attribute [x] or trying
   to remove it, underscores are removed at the beginning and the end of the
   attribute for both the [x] attribute and the attributes of the list. For
   example, if have a call:

   dropAttribute "__const" [ Attr("const", []) ; Attr("__const", []) ; Attr("__const__", []) ]

   The result is [].
*)

let add_attribute (Attr(an, _) | AttrAnnot an as a) al =
  let rec insert_sorted = function
    | [] -> [a]
    | ((Attr(an0, _) | AttrAnnot an0 as a0) :: rest) as l ->
      if an < an0 then a :: l
      else if Cil_datatype.Attribute.equal a a0 then l (* Do not add if already in there *)
      else a0 :: insert_sorted rest (* Make sure we see all attributes with
                                     * this name *)
  in
  insert_sorted al

(** The second attribute list is sorted *)
let add_attributes al0 al =
  if al0 == [] then
    al
  else
    List.fold_left (fun acc a -> add_attribute a acc) al al0

let drop_attribute an al =
  let an = Extlib.strip_underscore an in
  List.filter (fun a -> attribute_name a <> an) al

let rec drop_attributes anl al =
  match al with
  | [] -> []
  | a :: q ->
    let q' = drop_attributes anl q in
    if List.mem (attribute_name a) anl then
      q' (* drop this attribute *)
    else
    if q' == q then al (* preserve sharing *) else a :: q'

let find_attribute an al =
  let an = Extlib.strip_underscore an in
  List.fold_left (fun acc a ->
      match a with
      | Attr (_, param) as a0 when attribute_name a0 = an -> param @ acc
      | _ -> acc
    ) [] al

let filter_attributes an al =
  let an = Extlib.strip_underscore an in
  List.filter (fun a -> attribute_name a = an) al

let filter_qualifier_attributes al =
  List.filter (fun a -> List.mem (attribute_name a) qualifier_attributes) al

let split_array_attributes al =
  List.partition (fun a -> List.mem (attribute_name a) qualifier_attributes) al

(**************************************)
(* Attribute registration and classes *)
(**************************************)

type attribute_class =
  (** Attribute of a name. If argument is true and we are on MSVC then
      the attribute is printed using __declspec as part of the storage
      specifier  *)
  | AttrName of bool

  (** Attribute of a function type. If argument is true and we are on
      MSVC then the attribute is printed just before the function name *)
  | AttrFunType of bool

  (** Attribute of a type *)
  | AttrType

  (** Attribute of a statement or a block *)
  | AttrStmt

  (** Attribute that does not correspond to either of the above classes and is
      ignored by functions {!get_attribute_class} and {!partition_attributes}. *)
  | AttrIgnored

(* This table contains the mapping of predefined attributes to classes.
 * Extend this table with more attributes as you need. This table is used to
 * determine how to associate attributes with names or type during cabs2cil
 * conversion *)
let attribute_hash : (string, attribute_class) Hashtbl.t =
  let table = Hashtbl.create 13 in
  List.iter (fun a -> Hashtbl.add table a (AttrName false))
    [ "section"; "constructor"; "destructor"; "unused"; "used"; "weak";
      "no_instrument_function"; "alias"; "no_check_memory_usage";
      "exception"; "model"; (* "restrict"; *)
      "aconst"; "asm" (* Gcc uses this to specify the name to be used in
                       * assembly for a global  *)];
  (* Now come the MSVC declspec attributes *)
  List.iter (fun a -> Hashtbl.add table a (AttrName true))
    [ "thread"; "naked"; "dllimport"; "dllexport";
      "selectany"; "allocate"; "nothrow"; "novtable"; "property";
      "uuid"; "align" ];
  List.iter (fun a -> Hashtbl.add table a (AttrFunType false))
    [ "format"; "regparm"; "longcall"; "noinline"; "always_inline"; ];
  List.iter (fun a -> Hashtbl.add table a (AttrFunType true))
    [ "stdcall";"cdecl"; "fastcall"; "noreturn"];
  List.iter (fun a -> Hashtbl.add table a AttrType)
    ("mode" :: qualifier_attributes);
  (* GCC label and statement attributes. *)
  List.iter (fun a -> Hashtbl.add table a AttrStmt)
    [ "hot"; "cold"; "fallthrough"; "assume"; "musttail" ];
  table

let register_attribute an ac =
  if Hashtbl.mem attribute_hash an then begin
    let pp fmt c =
      match c with
      | AttrName b -> Format.fprintf fmt "(AttrName %B)" b
      | AttrFunType b -> Format.fprintf fmt "(AttrFunType %B)" b
      | AttrType -> Format.fprintf fmt "AttrType"
      | AttrIgnored -> Format.fprintf fmt "AttrIgnored"
      | AttrStmt -> Format.fprintf fmt "AttrStmt"
    in
    Kernel.warning
      "Replacing existing class for attribute %s: was %a, now %a"
      an pp (Hashtbl.find attribute_hash an) pp ac
  end;
  Hashtbl.replace attribute_hash an ac

let remove_attribute = Hashtbl.remove attribute_hash

let get_attribute_class ~default name =
  match Hashtbl.find attribute_hash name with
  | exception Not_found -> default
  | AttrIgnored -> default
  | ac -> ac

let is_known_attribute = Hashtbl.mem attribute_hash

let partition_attributes
    ~(default:attribute_class)
    (attrs:  attribute list) :
  attribute list * attribute list * attribute list =
  let rec loop (n,f,t) = function
      [] -> n, f, t
    | (Attr(an, _) | AttrAnnot an as a) :: rest ->
      match get_attribute_class ~default an with
        AttrName _ -> loop (add_attribute a n, f, t) rest
      | AttrFunType _ ->
        loop (n, add_attribute a f, t) rest
      | AttrType -> loop (n, f, add_attribute a t) rest
      | AttrStmt ->
        Kernel.warning "unexpected statement attribute %s" an;
        loop (n,f,t) rest
      | AttrIgnored -> loop (n, f, t) rest
  in
  loop ([], [], []) attrs

let split_storage_modifier al =
  let isstoragemod (Attr(an, _) | AttrAnnot an : attribute) : bool =
    try
      match Hashtbl.find attribute_hash an with
      | AttrName issm -> issm
      | _ -> false
    with Not_found -> false
  in
  let stom, rest = List.partition isstoragemod al in
  if not (Machine.msvcMode ()) then stom, rest
  else
    (* Put back the declspec. Put it without the leading __ since these will
     * be added later *)
    let stom' =
      List.map (fun a ->
          match a with
          | Attr(an, args) -> Attr("declspec", [ACons(an, args)])
          | AttrAnnot _ -> assert false
        ) stom
    in
    stom', rest

let frama_c_ghost_else = "fc_ghost_else"
let () = register_attribute frama_c_ghost_else (AttrStmt)

let frama_c_ghost_formal = "fc_ghost_formal"
let () = register_attribute frama_c_ghost_formal (AttrName false)

let frama_c_init_obj = "fc_initialized_object"
let () = register_attribute frama_c_init_obj (AttrName false)

let frama_c_mutable = "fc_mutable"
let () = register_attribute frama_c_mutable (AttrName false)

let frama_c_inlined = "fc_inlined"
let () = register_attribute frama_c_inlined (AttrFunType false)

let () =
  register_attribute bitfield_attribute_name AttrType;
  register_attribute anonymous_attribute_name AttrIgnored;
  List.iter
    (fun a -> register_attribute a AttrIgnored)
    fc_internal_attributes;
  List.iter
    (fun a -> register_attribute a AttrType)
    cast_irrelevant_attributes

let () =
  Cil_datatype.drop_non_logic_attributes :=
    drop_attributes spare_attributes_for_logic_cast

let () =
  Cil_datatype.drop_fc_internal_attributes :=
    drop_attributes fc_internal_attributes

let () =
  Cil_datatype.drop_unknown_attributes :=
    let is_annot_or_known_attr = function
      | Attr (name, _) -> is_known_attribute name
      (* Attribute annotations are always known. *)
      | AttrAnnot _ -> true
    in
    (fun attributes ->
       List.filter is_annot_or_known_attr attributes)