dead_end.ml 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. open StdLabels
  2. let identifier = "dead_end"
  3. let description = "Check for dead end in the code"
  4. let is_global = false
  5. let active = ref false
  6. type context = unit
  7. let initialize = Fun.id
  8. let finalize () = []
  9. module Expression = struct
  10. type t = unit
  11. include Default.Expression (struct
  12. type nonrec t = t
  13. let default = ()
  14. end)
  15. let v : t -> t' = fun () -> ()
  16. end
  17. module Instruction = struct
  18. type cause = Missing_else | Unchecked_path
  19. type state = {
  20. block_pos : S.pos;
  21. has_gt : bool;
  22. is_gt : bool;
  23. pos : (cause * S.pos) option;
  24. }
  25. type t = state
  26. type t' = state
  27. (** For each instruction, return thoses two informations :
  28. - the intruction contains at [gt]
  29. - the last instruction is a [gt]
  30. *)
  31. let v : t -> t' = fun t -> t
  32. let default =
  33. {
  34. block_pos = (Lexing.dummy_pos, Lexing.dummy_pos);
  35. has_gt = false;
  36. is_gt = false;
  37. pos = None;
  38. }
  39. (** Call for an instruction like [GT] or [*CLR] *)
  40. let call : S.pos -> T.keywords -> Expression.t' list -> t =
  41. fun pos f _ ->
  42. ignore pos;
  43. match f with
  44. | T.Goto | T.XGoto ->
  45. { block_pos = pos; has_gt = true; is_gt = true; pos = None }
  46. | T.Gosub -> { block_pos = pos; has_gt = false; is_gt = true; pos = None }
  47. | _ -> default
  48. (** Label for a loop *)
  49. let location : S.pos -> string -> t = fun _ _ -> default
  50. (** Comment *)
  51. let comment : S.pos -> t = fun _ -> default
  52. (** Raw expression *)
  53. let expression : Expression.t' -> t = fun _ -> default
  54. (** The content of a block is very linear, I only need to check the last element *)
  55. let check_block : S.pos -> t list -> t =
  56. fun pos instructions ->
  57. let last_element =
  58. List.fold_left instructions ~init:default ~f:(fun t instruction ->
  59. let result = instruction in
  60. let has_gt = result.has_gt || t.has_gt in
  61. let is_gt = result.is_gt || t.is_gt in
  62. { result with block_pos = pos; is_gt; has_gt })
  63. in
  64. last_element
  65. let if_ :
  66. S.pos ->
  67. (Expression.t', t) S.clause ->
  68. elifs:(Expression.t', t) S.clause list ->
  69. else_:(S.pos * t list) option ->
  70. t =
  71. fun pos clause ~elifs ~else_ ->
  72. (* For each block, evaluate the instructions *)
  73. let res, has_gt, is_gt =
  74. List.fold_left ~init:([], false, false) (clause :: elifs)
  75. ~f:(fun (acc, has_gt, is_gt) clause ->
  76. let pos, _, instructions = clause in
  77. let clause_t = check_block pos instructions in
  78. let has_gt = has_gt || clause_t.has_gt
  79. and is_gt = is_gt || clause_t.is_gt in
  80. ((clause_t, pos) :: acc, has_gt, is_gt))
  81. in
  82. let else_pos, else_block =
  83. match else_ with
  84. | Some (pos, instructions) ->
  85. let block = check_block pos instructions in
  86. (pos, block)
  87. | None -> (pos, default)
  88. in
  89. let has_gt = has_gt || else_block.has_gt
  90. and is_gt = is_gt || else_block.is_gt in
  91. let blocks = (else_block, else_pos) :: res in
  92. (* Check if one of the clauses already holds a dead end*)
  93. match List.find_opt res ~f:(fun (res, _) -> res.pos != None) with
  94. | Some (v, _) -> v
  95. | None -> (
  96. match (is_gt, has_gt) with
  97. | _, true -> (
  98. (* There is gt intruction in one of the branch, we need to checks
  99. the others *)
  100. match List.find_opt blocks ~f:(fun (f, _) -> not f.is_gt) with
  101. | None ->
  102. (* Every branch in the if is covered. It’s ok. *)
  103. { default with block_pos = pos; is_gt; has_gt }
  104. | Some (_, pos) ->
  105. (* TODO check if [pos] is the whole block *)
  106. let cause =
  107. match else_ with None -> Missing_else | _ -> Unchecked_path
  108. in
  109. { default with block_pos = pos; pos = Some (cause, pos) })
  110. | _, _ -> { default with block_pos = pos; has_gt; is_gt })
  111. let act : S.pos -> label:Expression.t' -> t list -> t =
  112. fun pos ~label expressions ->
  113. ignore label;
  114. check_block pos expressions
  115. let assign :
  116. S.pos ->
  117. (S.pos, Expression.t') S.variable ->
  118. T.assignation_operator ->
  119. Expression.t' ->
  120. t =
  121. fun _ _ _ _ -> default
  122. end
  123. module Location = struct
  124. type t = Report.t list
  125. let v = Fun.id
  126. let location : unit -> S.pos -> Instruction.t' list -> t =
  127. fun () _pos instructions ->
  128. List.fold_left instructions ~init:[] ~f:(fun report t ->
  129. match (t.Instruction.is_gt, t.Instruction.pos) with
  130. | false, Some (cause, value) ->
  131. ignore cause;
  132. if t.Instruction.block_pos != value then
  133. match cause with
  134. | Missing_else ->
  135. Report.debug value "Possible dead end (no else fallback)"
  136. :: report
  137. | Unchecked_path ->
  138. Report.warn value "Possible dead end (unmatched path)"
  139. :: report
  140. else report
  141. | _ -> report)
  142. end