Skip to content

Commit cc55edd

Browse files
committed
Refactor Exception analysis to eliminate global mutable state
- Replace global Values.valueBindingsTable with per-file values_builder - Replace global Checks.checks with per-file checks_builder - processCmt now returns file_result with per-file data - Add runChecks to process all checks after merging values tables - Update Reanalyze.ml to collect exception results and run checks at end - This enables future parallelization of AST processing
1 parent d63aea1 commit cc55edd

File tree

3 files changed

+167
-86
lines changed

3 files changed

+167
-86
lines changed

analysis/reanalyze/src/Exception.ml

Lines changed: 114 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,54 @@
11
open DeadCommon
22

3-
module Values = struct
4-
let valueBindingsTable =
5-
(Hashtbl.create 15 : (string, (Name.t, Exceptions.t) Hashtbl.t) Hashtbl.t)
3+
(** Per-file mutable builder for exception values during AST processing *)
4+
type values_builder = (Name.t, Exceptions.t) Hashtbl.t
5+
6+
(** Merged immutable table for cross-file lookups *)
7+
type values_table = (string, (Name.t, Exceptions.t) Hashtbl.t) Hashtbl.t
8+
9+
let create_values_builder () : values_builder = Hashtbl.create 15
610

7-
let currentFileTable = ref (Hashtbl.create 1)
11+
let values_builder_add (builder : values_builder) ~modulePath ~name exceptions =
12+
let path = (name |> Name.create) :: modulePath.ModulePath.path in
13+
Hashtbl.replace builder (path |> DcePath.toName) exceptions
814

9-
let add ~modulePath ~name exceptions =
10-
let path = (name |> Name.create) :: modulePath.ModulePath.path in
11-
Hashtbl.replace !currentFileTable (path |> DcePath.toName) exceptions
15+
(** Merge all per-file builders into a single lookup table *)
16+
let merge_values_builders (builders : (string * values_builder) list) :
17+
values_table =
18+
let table = Hashtbl.create 15 in
19+
builders
20+
|> List.iter (fun (moduleName, builder) ->
21+
Hashtbl.replace table moduleName builder);
22+
table
1223

13-
let getFromModule ~moduleName ~modulePath (path_ : DcePath.t) =
24+
module Values = struct
25+
let getFromModule (table : values_table) ~moduleName ~modulePath
26+
(path_ : DcePath.t) =
1427
let name = path_ @ modulePath |> DcePath.toName in
15-
match
16-
Hashtbl.find_opt valueBindingsTable (String.capitalize_ascii moduleName)
17-
with
28+
match Hashtbl.find_opt table (String.capitalize_ascii moduleName) with
1829
| Some tbl -> Hashtbl.find_opt tbl name
1930
| None -> (
20-
match
21-
Hashtbl.find_opt valueBindingsTable
22-
(String.uncapitalize_ascii moduleName)
23-
with
31+
match Hashtbl.find_opt table (String.uncapitalize_ascii moduleName) with
2432
| Some tbl -> Hashtbl.find_opt tbl name
2533
| None -> None)
2634

27-
let rec findLocal ~moduleName ~modulePath path =
28-
match path |> getFromModule ~moduleName ~modulePath with
35+
let rec findLocal (table : values_table) ~moduleName ~modulePath path =
36+
match path |> getFromModule table ~moduleName ~modulePath with
2937
| Some exceptions -> Some exceptions
3038
| None -> (
3139
match modulePath with
3240
| [] -> None
3341
| _ :: restModulePath ->
34-
path |> findLocal ~moduleName ~modulePath:restModulePath)
42+
path |> findLocal table ~moduleName ~modulePath:restModulePath)
3543

36-
let findPath ~moduleName ~modulePath path =
44+
let findPath (table : values_table) ~moduleName ~modulePath path =
3745
let findExternal ~externalModuleName ~pathRev =
3846
pathRev |> List.rev
39-
|> getFromModule
47+
|> getFromModule table
4048
~moduleName:(externalModuleName |> Name.toString)
4149
~modulePath:[]
4250
in
43-
match path |> findLocal ~moduleName ~modulePath with
51+
match path |> findLocal table ~moduleName ~modulePath with
4452
| None -> (
4553
(* Search in another file *)
4654
match path |> List.rev with
@@ -54,10 +62,6 @@ module Values = struct
5462
| None, _ -> None)
5563
| [] -> None)
5664
| Some exceptions -> Some exceptions
57-
58-
let newCmt ~moduleName =
59-
currentFileTable := Hashtbl.create 15;
60-
Hashtbl.replace valueBindingsTable moduleName !currentFileTable
6165
end
6266

6367
module Event = struct
@@ -99,7 +103,7 @@ module Event = struct
99103
nestedEvents |> List.iter (fun e -> Format.fprintf ppf "%a " print e))
100104
()
101105

102-
let combine ~config ~moduleName events =
106+
let combine ~(values_table : values_table) ~config ~moduleName events =
103107
if config.DceConfig.cli.debug then (
104108
Log_.item "@.";
105109
Log_.item "Events combine: #events %d@." (events |> List.length));
@@ -123,7 +127,9 @@ module Event = struct
123127
| ({kind = Call {callee; modulePath}; loc} as ev) :: rest ->
124128
if config.DceConfig.cli.debug then Log_.item "%a@." print ev;
125129
let exceptions =
126-
match callee |> Values.findPath ~moduleName ~modulePath with
130+
match
131+
callee |> Values.findPath values_table ~moduleName ~modulePath
132+
with
127133
| Some exceptions -> exceptions
128134
| _ -> (
129135
match ExnLib.find callee with
@@ -168,25 +174,33 @@ module Event = struct
168174
(exnSet, exnTable)
169175
end
170176

171-
module Checks = struct
172-
type check = {
173-
events: Event.t list;
174-
loc: Location.t;
175-
locFull: Location.t;
176-
moduleName: string;
177-
exnName: string;
178-
exceptions: Exceptions.t;
179-
}
177+
(** Per-file mutable builder for checks during AST processing *)
178+
type checks_builder = check list ref
179+
180+
and check = {
181+
events: Event.t list;
182+
loc: Location.t;
183+
locFull: Location.t;
184+
moduleName: string;
185+
exnName: string;
186+
exceptions: Exceptions.t;
187+
}
180188

181-
type t = check list
189+
let create_checks_builder () : checks_builder = ref []
182190

183-
let checks = (ref [] : t ref)
191+
let checks_builder_add (builder : checks_builder) ~events ~exceptions ~loc
192+
?(locFull = loc) ~moduleName exnName =
193+
builder := {events; exceptions; loc; locFull; moduleName; exnName} :: !builder
184194

185-
let add ~events ~exceptions ~loc ?(locFull = loc) ~moduleName exnName =
186-
checks := {events; exceptions; loc; locFull; moduleName; exnName} :: !checks
195+
let checks_builder_to_list (builder : checks_builder) : check list =
196+
!builder |> List.rev
187197

188-
let doCheck ~config {events; exceptions; loc; locFull; moduleName; exnName} =
189-
let throwSet, exnTable = events |> Event.combine ~config ~moduleName in
198+
module Checks = struct
199+
let doCheck ~(values_table : values_table) ~config
200+
{events; exceptions; loc; locFull; moduleName; exnName} =
201+
let throwSet, exnTable =
202+
events |> Event.combine ~values_table ~config ~moduleName
203+
in
190204
let missingAnnotations = Exceptions.diff throwSet exceptions in
191205
let redundantAnnotations = Exceptions.diff exceptions throwSet in
192206
(if not (Exceptions.isEmpty missingAnnotations) then
@@ -215,13 +229,28 @@ module Checks = struct
215229
redundantAnnotations);
216230
})
217231

218-
let doChecks ~config = !checks |> List.rev |> List.iter (doCheck ~config)
232+
let doChecks ~values_table ~config (checks : check list) =
233+
checks |> List.iter (doCheck ~values_table ~config)
219234
end
220235

221-
let traverseAst ~file () =
236+
let traverseAst ~file ~values_builder ~checks_builder () =
222237
let super = Tast_mapper.default in
223238
let currentId = ref "" in
224239
let currentEvents = ref [] in
240+
(* For local lookups during AST processing, we look up in the current file's builder *)
241+
let findLocalExceptions ~modulePath path =
242+
let name = path @ modulePath |> DcePath.toName in
243+
Hashtbl.find_opt values_builder name
244+
in
245+
let rec findLocalPath ~modulePath path =
246+
match path |> findLocalExceptions ~modulePath with
247+
| Some exceptions -> Some exceptions
248+
| None -> (
249+
match modulePath with
250+
| [] -> None
251+
| _ :: restModulePath ->
252+
path |> findLocalPath ~modulePath:restModulePath)
253+
in
225254
let exceptionsOfPatterns patterns =
226255
patterns
227256
|> List.fold_left
@@ -394,7 +423,7 @@ let traverseAst ~file () =
394423
currentEvents := [];
395424
let moduleName = file.FileContext.module_name in
396425
self.expr self expr |> ignore;
397-
Checks.add ~events:!currentEvents
426+
checks_builder_add checks_builder ~events:!currentEvents
398427
~exceptions:(getExceptionsFromAnnotations attributes)
399428
~loc:expr.exp_loc ~moduleName name;
400429
currentId := oldId;
@@ -416,19 +445,18 @@ let traverseAst ~file () =
416445
let exceptionsFromAnnotations =
417446
getExceptionsFromAnnotations vb.vb_attributes
418447
in
419-
exceptionsFromAnnotations |> Values.add ~modulePath ~name;
448+
values_builder_add values_builder ~modulePath ~name
449+
exceptionsFromAnnotations;
420450
let res = super.value_binding self vb in
421451
let moduleName = file.FileContext.module_name in
422452
let path = [name |> Name.create] in
423453
let exceptions =
424-
match
425-
path |> Values.findPath ~moduleName ~modulePath:modulePath.path
426-
with
454+
match path |> findLocalPath ~modulePath:modulePath.path with
427455
| Some exceptions -> exceptions
428456
| _ -> Exceptions.empty
429457
in
430-
Checks.add ~events:!currentEvents ~exceptions ~loc:vb.vb_pat.pat_loc
431-
~locFull:vb.vb_loc ~moduleName name;
458+
checks_builder_add checks_builder ~events:!currentEvents ~exceptions
459+
~loc:vb.vb_pat.pat_loc ~locFull:vb.vb_loc ~moduleName name;
432460
currentId := oldId;
433461
currentEvents := oldEvents;
434462
res
@@ -509,14 +537,42 @@ let traverseAst ~file () =
509537
fun (structure : Typedtree.structure) ->
510538
process_structure ModulePath.initial structure
511539

512-
let processStructure ~file (structure : Typedtree.structure) =
513-
let process = traverseAst ~file () in
540+
(** Result of processing a single file *)
541+
type file_result = {
542+
module_name: string;
543+
values_builder: values_builder;
544+
checks: check list;
545+
}
546+
547+
let processStructure ~file ~values_builder ~checks_builder
548+
(structure : Typedtree.structure) =
549+
let process = traverseAst ~file ~values_builder ~checks_builder () in
514550
process structure
515551

516-
let processCmt ~file (cmt_infos : Cmt_format.cmt_infos) =
552+
let processCmt ~file (cmt_infos : Cmt_format.cmt_infos) : file_result option =
517553
match cmt_infos.cmt_annots with
518-
| Interface _ -> ()
554+
| Interface _ -> None
519555
| Implementation structure ->
520-
Values.newCmt ~moduleName:file.FileContext.module_name;
521-
structure |> processStructure ~file
522-
| _ -> ()
556+
let values_builder = create_values_builder () in
557+
let checks_builder = create_checks_builder () in
558+
structure |> processStructure ~file ~values_builder ~checks_builder;
559+
Some
560+
{
561+
module_name = file.FileContext.module_name;
562+
values_builder;
563+
checks = checks_builder_to_list checks_builder;
564+
}
565+
| _ -> None
566+
567+
(** Process all accumulated checks using merged values table *)
568+
let runChecks ~config (all_results : file_result list) =
569+
(* Merge all values builders *)
570+
let values_table =
571+
all_results
572+
|> List.map (fun r -> (r.module_name, r.values_builder))
573+
|> merge_values_builders
574+
in
575+
(* Collect all checks *)
576+
let all_checks = all_results |> List.concat_map (fun r -> r.checks) in
577+
(* Run checks with merged table *)
578+
Checks.doChecks ~values_table ~config all_checks

0 commit comments

Comments
 (0)