write_only.ml 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. (** Check all the write_only variables *)
  2. open StdLabels
  3. (** Identifier for the module *)
  4. let identifier = "write_only"
  5. (** Short description*)
  6. let description = "Check variables never read"
  7. (** Is the test active or not *)
  8. let active = ref false
  9. let is_global = true
  10. module Key = struct
  11. type t = string
  12. let equal = String.equal
  13. let hash = Hashtbl.hash
  14. let compare = String.compare
  15. end
  16. module StringMap = Hashtbl.Make (Key)
  17. module Set = Set.Make (Key)
  18. type data = { write : bool; read : bool; position : S.pos list }
  19. type context = (string * data) StringMap.t
  20. let initialize () = StringMap.create 16
  21. let keywords =
  22. [
  23. "BACKIMAGE";
  24. "$BACKIMAGE";
  25. "BCOLOR";
  26. "DEBUG";
  27. "DISABLESCROLL";
  28. "DISABLESUBEX";
  29. "FCOLOR";
  30. "$FNAME";
  31. "FSIZE";
  32. "GC";
  33. "LCOLOR";
  34. "NOSAVE";
  35. ]
  36. |> Set.of_list
  37. let set_readed :
  38. ?update_only:bool -> S.pos -> string -> string -> context -> unit =
  39. fun ?(update_only = false) pos identifier filename map ->
  40. if not (Set.mem identifier keywords) then
  41. match (update_only, StringMap.find_opt map identifier) with
  42. | false, None ->
  43. StringMap.add map identifier
  44. (filename, { write = false; read = true; position = [] })
  45. | _, Some (filename, v) ->
  46. StringMap.replace map identifier
  47. (filename, { v with read = true; position = pos :: v.position })
  48. | true, None -> ()
  49. let set_write : S.pos -> string -> string -> context -> unit =
  50. fun pos identifier filename map ->
  51. if not (Set.mem identifier keywords) then
  52. match StringMap.find_opt map identifier with
  53. | None ->
  54. StringMap.add map identifier
  55. (filename, { write = true; read = false; position = pos :: [] })
  56. | Some (filename, v) ->
  57. StringMap.replace map identifier
  58. (filename, { v with write = true; position = pos :: v.position })
  59. module Expression = struct
  60. type t = string -> context -> unit
  61. let v : t -> t = Fun.id
  62. include Default.Expression (struct
  63. type nonrec t = t
  64. let default _ map = ignore map
  65. end)
  66. let ident : (S.pos, t) S.variable -> t =
  67. fun variable filename map ->
  68. (* Update the map and set the read flag *)
  69. set_readed variable.pos variable.name filename map
  70. let literal : S.pos -> t T.literal list -> t =
  71. fun pos l filename map ->
  72. List.iter l ~f:(function
  73. | T.Text t ->
  74. set_readed pos ~update_only:true (String.uppercase_ascii t) filename
  75. map
  76. | T.Expression exprs ->
  77. (* When the string contains an expression evaluate it *)
  78. exprs filename map)
  79. let function_ : S.pos -> T.function_ -> t list -> t =
  80. fun _ _ exprs filename map -> List.iter ~f:(fun v -> v filename map) exprs
  81. let uoperator : S.pos -> T.uoperator -> t -> t = fun _ _ t map -> t map
  82. let boperator : S.pos -> T.boperator -> t -> t -> t =
  83. fun _ _ t1 t2 filename map ->
  84. t1 filename map;
  85. t2 filename map
  86. end
  87. module Instruction = struct
  88. type t = Expression.t
  89. (** Internal type used in the evaluation *)
  90. type t' = t
  91. let v : t -> t' = Fun.id
  92. type expression = Expression.t
  93. let location : S.pos -> string -> t = fun _pos _ _ _ -> ()
  94. let call : S.pos -> T.keywords -> expression list -> t =
  95. fun _ op exprs filename map ->
  96. match op with
  97. | T.KillVar ->
  98. (* Killing a variable does not count as reading it *)
  99. ()
  100. | _ -> List.iter ~f:(fun v -> v filename map) exprs
  101. let comment : S.pos -> t = fun _ _ _ -> ()
  102. let expression : expression -> t = fun expression map -> expression map
  103. let fold_clause : (expression, t) S.clause -> t =
  104. fun clause filename map ->
  105. let _, expr, exprs = clause in
  106. let () = expr filename map in
  107. let () = List.iter ~f:(fun v -> v filename map) exprs in
  108. ()
  109. let if_ :
  110. S.pos ->
  111. (expression, t) S.clause ->
  112. elifs:(expression, t) S.clause list ->
  113. else_:(S.pos * t list) option ->
  114. t =
  115. fun pos clauses ~elifs ~else_ filename map ->
  116. ignore pos;
  117. let () = fold_clause clauses filename map in
  118. let () = List.iter ~f:(fun v -> fold_clause v filename map) elifs in
  119. Option.iter
  120. (fun (_, exprs) -> List.iter exprs ~f:(fun v -> v filename map))
  121. else_;
  122. ()
  123. let act : S.pos -> label:expression -> t list -> t =
  124. fun pos ~label exprs filename map ->
  125. ignore pos;
  126. ignore label;
  127. List.iter ~f:(fun v -> v filename map) exprs
  128. let assign :
  129. S.pos ->
  130. (S.pos, expression) S.variable ->
  131. T.assignation_operator ->
  132. expression ->
  133. t =
  134. fun pos variable op expr filename map ->
  135. ignore op;
  136. ignore expr;
  137. Option.iter (fun v -> v filename map) variable.index;
  138. expr filename map;
  139. set_write pos variable.name filename map
  140. end
  141. module Location = struct
  142. type t = unit
  143. type instruction = string -> context -> unit
  144. let v : t -> Report.t list = fun _ -> []
  145. let location : context -> S.pos -> instruction list -> t =
  146. fun context pos instructions ->
  147. let file_name = (fst pos).Lexing.pos_fname in
  148. ignore pos;
  149. ignore context;
  150. let () = List.iter ~f:(fun v -> v file_name context) instructions in
  151. ()
  152. end
  153. (** Extract the results from the whole parsing *)
  154. let finalize : context -> (string * Report.t) list =
  155. fun map ->
  156. let () =
  157. StringMap.filter_map_inplace
  158. (fun _ (loc, value) ->
  159. match value.read && value.write with
  160. | true -> None
  161. | false -> Some (loc, value))
  162. map
  163. in
  164. let report =
  165. StringMap.fold
  166. (fun ident (loc, value) report ->
  167. match value.read with
  168. | false ->
  169. List.fold_left value.position ~init:report ~f:(fun report pos ->
  170. let msg =
  171. Report.debug pos
  172. (String.concat ~sep:" "
  173. [ "The variable"; ident; "is never read" ])
  174. in
  175. (loc, msg) :: report)
  176. | true -> report)
  177. map []
  178. in
  179. report