(** Check all the write_only variables *) open StdLabels (** Identifier for the module *) let identifier = "write_only" (** Short description*) let description = "Check variables never read" (** Is the test active or not *) let active = ref false let is_global = true module Key = struct type t = string let equal = String.equal let hash = Hashtbl.hash let compare = String.compare end module StringMap = Hashtbl.Make (Key) module Set = Set.Make (Key) type data = { write : bool; read : bool; position : S.pos list } type context = (string * data) StringMap.t let initialize () = StringMap.create 16 let keywords = [ "BACKIMAGE"; "$BACKIMAGE"; "BCOLOR"; "DEBUG"; "DISABLESCROLL"; "DISABLESUBEX"; "FCOLOR"; "$FNAME"; "FSIZE"; "GC"; "LCOLOR"; "NOSAVE"; ] |> Set.of_list let set_readed : ?update_only:bool -> S.pos -> string -> string -> context -> unit = fun ?(update_only = false) pos identifier filename map -> if not (Set.mem identifier keywords) then match (update_only, StringMap.find_opt map identifier) with | false, None -> StringMap.add map identifier (filename, { write = false; read = true; position = [] }) | _, Some (filename, v) -> StringMap.replace map identifier (filename, { v with read = true; position = pos :: v.position }) | true, None -> () let set_write : S.pos -> string -> string -> context -> unit = fun pos identifier filename map -> if not (Set.mem identifier keywords) then match StringMap.find_opt map identifier with | None -> StringMap.add map identifier (filename, { write = true; read = false; position = pos :: [] }) | Some (filename, v) -> StringMap.replace map identifier (filename, { v with write = true; position = pos :: v.position }) module Expression = struct type t = string -> context -> unit let v : t -> t = Fun.id include Default.Expression (struct type nonrec t = t let default _ map = ignore map end) let ident : (S.pos, t) S.variable -> t = fun variable filename map -> (* Update the map and set the read flag *) set_readed variable.pos variable.name filename map let literal : S.pos -> t T.literal list -> t = fun pos l filename map -> List.iter l ~f:(function | T.Text t -> set_readed pos ~update_only:true (String.uppercase_ascii t) filename map | T.Expression exprs -> (* When the string contains an expression evaluate it *) exprs filename map) let function_ : S.pos -> T.function_ -> t list -> t = fun _ _ exprs filename map -> List.iter ~f:(fun v -> v filename map) exprs let uoperator : S.pos -> T.uoperator -> t -> t = fun _ _ t map -> t map let boperator : S.pos -> T.boperator -> t -> t -> t = fun _ _ t1 t2 filename map -> t1 filename map; t2 filename map end module Instruction = struct type t = Expression.t (** Internal type used in the evaluation *) type t' = t let v : t -> t' = Fun.id type expression = Expression.t let location : S.pos -> string -> t = fun _pos _ _ _ -> () let call : S.pos -> T.keywords -> expression list -> t = fun _ op exprs filename map -> match op with | T.KillVar -> (* Killing a variable does not count as reading it *) () | _ -> List.iter ~f:(fun v -> v filename map) exprs let comment : S.pos -> t = fun _ _ _ -> () let expression : expression -> t = fun expression map -> expression map let fold_clause : (expression, t) S.clause -> t = fun clause filename map -> let _, expr, exprs = clause in let () = expr filename map in let () = List.iter ~f:(fun v -> v filename map) exprs in () let if_ : S.pos -> (expression, t) S.clause -> elifs:(expression, t) S.clause list -> else_:(S.pos * t list) option -> t = fun pos clauses ~elifs ~else_ filename map -> ignore pos; let () = fold_clause clauses filename map in let () = List.iter ~f:(fun v -> fold_clause v filename map) elifs in Option.iter (fun (_, exprs) -> List.iter exprs ~f:(fun v -> v filename map)) else_; () let act : S.pos -> label:expression -> t list -> t = fun pos ~label exprs filename map -> ignore pos; ignore label; List.iter ~f:(fun v -> v filename map) exprs let assign : S.pos -> (S.pos, expression) S.variable -> T.assignation_operator -> expression -> t = fun pos variable op expr filename map -> ignore op; ignore expr; Option.iter (fun v -> v filename map) variable.index; expr filename map; set_write pos variable.name filename map end module Location = struct type t = unit type instruction = string -> context -> unit let v : t -> Report.t list = fun _ -> [] let location : context -> S.pos -> instruction list -> t = fun context pos instructions -> let file_name = (fst pos).Lexing.pos_fname in ignore pos; ignore context; let () = List.iter ~f:(fun v -> v file_name context) instructions in () end (** Extract the results from the whole parsing *) let finalize : context -> (string * Report.t) list = fun map -> let () = StringMap.filter_map_inplace (fun _ (loc, value) -> match value.read && value.write with | true -> None | false -> Some (loc, value)) map in let report = StringMap.fold (fun ident (loc, value) report -> match value.read with | false -> List.fold_left value.position ~init:report ~f:(fun report pos -> let msg = Report.debug pos (String.concat ~sep:" " [ "The variable"; ident; "is never read" ]) in (loc, msg) :: report) | true -> report) map [] in report