package frama-c
Platform dedicated to the analysis of source code written in C
Install
Dune Dependency
Authors
-
MMichele Alberti
-
TThibaud Antignac
-
GGergö Barany
-
PPatrick Baudin
-
NNicolas Bellec
-
TThibaut Benjamin
-
AAllan Blanchard
-
LLionel Blatter
-
FFrançois Bobot
-
RRichard Bonichon
-
VVincent Botbol
-
QQuentin Bouillaguet
-
DDavid Bühler
-
ZZakaria Chihani
-
SSylvain Chiron
-
LLoïc Correnson
-
JJulien Crétin
-
PPascal Cuoq
-
ZZaynah Dargaye
-
BBasile Desloges
-
JJean-Christophe Filliâtre
-
PPhilippe Herrmann
-
MMaxime Jacquemin
-
BBenjamin Jorge
-
FFlorent Kirchner
-
AAlexander Kogtenkov
-
RRemi Lazarini
-
TTristan Le Gall
-
KKilyan Le Gallic
-
JJean-Christophe Léchenet
-
MMatthieu Lemerre
-
DDara Ly
-
DDavid Maison
-
CClaude Marché
-
AAndré Maroneze
-
TThibault Martin
-
FFonenantsoa Maurica
-
MMelody Méaulle
-
BBenjamin Monate
-
YYannick Moy
-
PPierre Nigron
-
AAnne Pacalet
-
VValentin Perrelle
-
GGuillaume Petiot
-
DDario Pinto
-
VVirgile Prevosto
-
AArmand Puccetti
-
FFélix Ridoux
-
VVirgile Robles
-
JJan Rochel
-
MMuriel Roger
-
CCécile Ruet-Cros
-
JJulien Signoles
-
NNicolas Stouls
-
KKostyantyn Vorobyov
-
BBoris Yakobowski
Maintainers
Sources
frama-c-31.0-Gallium.tar.gz
sha256=a94384f00d53791cbb4b4d83ab41607bc71962d42461f02d71116c4ff6dca567
doc/src/mthread/mt_analysis_fixpoint.ml.html
Source file mt_analysis_fixpoint.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
(**************************************************************************) (* *) (* 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). *) (* *) (**************************************************************************) open Mt_types open Mt_shared_vars_types open Mt_mutexes_types open Mt_thread (* If the thread sends more messages than before, we flag all the threads receiving messages on those queues as needed to be recomputed *) let mark_new_messages_received analysis = let th = analysis.curr_thread in let is_send = function SendMsg _ -> true | _ -> false in let send_before = Trace.find_events is_send th.th_amap and send_after = Trace.find_events is_send (curr_events analysis) in (* YYY Not monotonic *) let diff = EventsSet.diff send_after send_before in if not (EventsSet.is_empty diff) then let queues = EventsSet.fold (fun evt queues -> match evt with | SendMsg (q, _) -> Mqueue.Set.add q queues | _ -> queues) diff Mqueue.Set.empty in Mt_options.debug "@[New message(s) sent@ on@ queue(s) %a@]" (Pretty_utils.pp_iter Mqueue.Set.iter Mqueue.pretty) queues; iter_threads analysis (fun th -> let should_recompute _stack = function | ReceiveMsg (q, _, _) -> Mqueue.Set.mem q queues | _ -> false in if Trace.exists th.th_amap should_recompute then (Mt_options.debug "Marking %a as having received new message(s)" ThreadState.pretty th; ThreadState.recompute_because th NewMsgReceived) ); ;; let record_end_of_thread_analysis analysis = let th = analysis.curr_thread in (* We save the state of the analysis *) Mt_options.feedback ~level:2 "* Starting to save the state of the value analysis"; let results = Eva_results.get_results () in th.th_value_results <- Some results; if Mt_options.ToDisk.get () then let th = ThreadState.label th |> Mt_lib.sanitize_filename in let name = Format.sprintf "%s%s_iteration_%d.sav" (Mt_options.ToDiskPrefix.get ()) th analysis.iteration in Project.save (Filepath.of_string name) else begin let p = lazy( let pname = Format.asprintf "%a, iteration %d" ThreadState.pretty th analysis.iteration in Project.create_by_copy ~last:false pname) in match Mt_options.KeepProjects.get () with | "all" -> th.th_projects <- Lazy.force p :: th.th_projects | "last" -> List.iter (fun project -> Project.remove ~project ()) th.th_projects; th.th_projects <- [Lazy.force p] | "none" -> () | _ -> assert false (* checked by the command-line *) end; Mt_options.feedback ~level:2 "* state saved"; mark_new_messages_received analysis; (* We compute the globals variables accessed by the thread *) Mt_options.feedback ~level:2 "* Computing shared variables"; let state_accesser = Mt_memory.Types.Global in let read_written = Mt_shared_vars.read_written_by_thread (Mt_shared_vars.stmt_is_multithreaded analysis state_accesser) th.th_eva_thread in th.th_read_written <- read_written; Mt_options.result ~level:3 "@[<v 0>Globals accessed by thread:@ %a@]" AccessesByZone.pretty_map read_written; Mt_options.feedback ~level:2 "* shared variables computed"; (* We compute interferences *) Mt_interferences.add_last_analysis analysis; (* We update the multithread events of the thread for its next iteration *) th.th_amap <- curr_events analysis; (* Compute the concurrent graph of this thread *) Mt_options.feedback ~level:2 "* Computing cfg"; th.th_cfg <- Mt_cfg.make_cfg th; th.th_read_written_cfg <- Mt_cfg.cfg_accesses th.th_eva_thread th.th_cfg; Mt_options.feedback ~level:2 "* Cfg computed"; ;; (* We compute a value analysis for the given thread *) let compute_thread analysis th = let time = Sys.time () in Project.clear ~selection:(State_selection.with_dependencies Messages.self) (); Messages.reset_once_flag (); Mt_options.feedback ~level:2 "* Computing value analysis for thread %a" Thread.pretty th.th_eva_thread; Mt_options.debug "@[<hov>Arguments@ %a@]" (Pretty_utils.pp_list Cvalue.V.pretty) th.th_params; Mt_options.debug ~level:2 "Initial state %a" Cvalue.Model.pretty th.th_init_state; (* We set the values that depend on the thread analysed *) analysis.curr_thread <- th; analysis.curr_events_stack <- []; Datatype.Int.Hashtbl.clear analysis.memexec_cache; (* We reset the concurrent value analysis (necessary because sometimes, only the hooks have changed, and this is not captured by the project infrastructure) *) Mt_lib.clear_value_results (); (* We set the parameters for the value analysis *) Globals.set_entry_point (Kernel_function.get_name th.th_fun) false; Eva_results.set_initial_state th.th_init_state; Eva_results.set_main_args th.th_params; Thread.set_current th.th_eva_thread; Analysis.compute (); if Mt_options.ShowTime.get () then Mt_options.feedback ~level:2 "* Value analysis computed for thread %a, %f sec" ThreadState.pretty th (Sys.time () -. time); ;; let analysis before = iter_threads analysis (fun th -> try AccessesByZone.fold (fun z _ () -> if not (Locations.Zone.is_included z before) then raise Exit) th.th_read_written () with Exit -> ThreadState.recompute_because th PotentialSharedVarsChanged ) ;; (** Recompute all the threads that are not [th], and that read a value that has changed between [before] and [now] *) let analysis th before now = let changed_zone b offsm z = (* b is present in [now] but not in [before], or has changed: add the entire base to the changed_zone *) let default () = let zb = Locations.Zone.inject b Int_Intervals.top in Locations.Zone.join z zb in try match Cvalue.Model.find_base b before with | `Top | `Bottom -> assert false | `Value offsm' -> if Cvalue.V_Offsetmap.equal offsm offsm' then z else default () with Not_found -> default () in match now with | Cvalue.Model.Top | Cvalue.Model.Bottom -> assert false | Cvalue.Model.Map now -> (* Over-approximation of the zones changed from [before] to [now] *) let z_changed = Cvalue.Model.fold changed_zone now Locations.Zone.bottom in iter_threads analysis (fun th' -> if not (ThreadState.equal th' th) then try AccessesByZone.fold (fun z accesses () -> if Locations.Zone.intersects z_changed z && (* YYY: recompute also threads that only write the variable?*) (SetStmtIdAccess.exists (fun (op, _, _) -> RW.is_read op) accesses) then begin ThreadState.recompute_because th' SharedVarsValuesChanged; raise Exit (* Speed up things, th' will be recomputed *) end) th'.th_read_written () with Exit -> () ) ;; let analysis = let _imprecise = Mt_options.feedback "***** Computing shared variables"; let (ww_accesses, rw_accesses), _ = Mt_shared_vars.Global.concurrent_accesses_all_threads (threads analysis) in let accesses = ww_accesses @ rw_accesses in Mt_options.debug ~level:2 "Global concurrent var accesses:@.%a" (Mt_shared_vars.Global.pretty_concurrent_accesses ()) accesses; let all_zones = Mt_shared_vars.Global.all_zones_accessed accesses in Mt_options.result ~level:3 "@[<hov 2>Imprecise locations to watch: %a@]" Locations.Zone.pretty all_zones; (* Detect changes *) if not (Locations.Zone.equal all_zones analysis.concurrent_accesses) then ( let before = analysis.concurrent_accesses in Mt_options.feedback ~level:2 "@[<v>Concurrent imprecise accesses have \ changed: before@ @[<hov 2> %a@]@ vs.@ @[<hov 2> %a@]" Locations.Zone.pretty before Locations.Zone.pretty all_zones; let after = Locations.Zone.join before all_zones in analysis.concurrent_accesses <- after; recompute_shared_vars_changed analysis before; ) in (* Precise computation. Very similar to the above code, we just compute, store and print things differently *) let precise = let (ww_accesses, rw_accesses), zmap = Mt_shared_vars.Precise.concurrent_accesses_all_threads (threads analysis) in if Mt_options.DumpSharedVarsValues.get () > 0 then Mt_shared_vars.Precise.display_shared_vars_value zmap; let written = Mt_shared_vars.Precise.enumerate_written_vars_value zmap in let all_accesses = ww_accesses @ rw_accesses in let header fmt = Format.fprintf fmt "Possible read/write data races:" in Mt_options.printf ~level:1 ~header " @[<v 0>%a@]" Mt_mutexes.pretty_with_mutexes rw_accesses; if Mt_options.WriteWriteRaces.get () then begin let header fmt = Format.fprintf fmt "Possible write/write data races:" in Mt_options.printf ~level:1 ~header " @[<v 0>%a@]" Mt_mutexes.pretty_with_mutexes ww_accesses; end; let all_zones = Mt_shared_vars.Precise.all_zones_accessed (ww_accesses @ rw_accesses) in Mt_options.result ~level:2 "@[<hov 2>Shared memory:@ %a@]" Locations.Zone.pretty all_zones; (* Detect changes *) if not (Locations.Zone.equal all_zones analysis.precise_concurrent_accesses) then ( let before = analysis.precise_concurrent_accesses in Mt_options.feedback ~level:2 "@[<v>Concurrent precise var accesses have changed: before@ \ @[<hov 2> %a@]@ \ vs.@ \ @[<hov 2> %a@]@]" Locations.Zone.pretty before Locations.Zone.pretty all_zones; (* let after = Locations.Zone.join before all_zones in *) analysis.precise_concurrent_accesses <- all_zones; (* No need to recompute for the moment, this field is not used by the analysis *) ); all_accesses, written in precise ;; (* Update the th_values_written field of all the threads, using the list of concurrent accesses that is returned by the shared var analysis. This function must be called once the [th_read_written] fields have been updated to ensure correct convergence *) let store_written_value analysis lw = let aux th = let l = List.filter (fun (id, _, _) -> Thread.equal id th.th_eva_thread) lw in let old_written = th.th_values_written in let written = Mt_shared_vars.Precise.join_shared_values l in (* XXX: temporary *) let priority, hint = Widen_type.hints_from_keys Cil.dummyStmt (Widen_type.default ()) in let written = Cvalue.Model.widen ~priority ~hint old_written written in let changed = not (Cvalue.Model.equal written old_written) in if changed then recompute_shared_vars_values_changed analysis th old_written written; if Mt_options.DumpSharedVarsValues.get () > 0 && not (Cvalue.Model.equal Cvalue.Model.empty_map written) then Mt_options.result "@[Write summary for %a%t:@ %a@]" ThreadState.pretty th (fun fmt -> if changed then Format.fprintf fmt " (updated)") Cvalue.Model.pretty written; th.th_values_written <- written in iter_threads analysis aux (* Function that does one pass of value analysis on all the threads that are marked as needed to be recomputed. Returns the values written by each thread recomputed*) let one_iteration analysis = iter_threads analysis (fun th -> if not (SetRecomputeReason.is_empty th.th_to_recompute) then ( if Mt_thread.should_compute_thread th then if Cvalue.Model.is_reachable th.th_init_state then ( Mt_options.feedback "@[<hov 2>*** Computing thread %a,@ iteration %d@ (%a)@]" ThreadState.pretty th analysis.iteration (Pretty_utils.pp_iter ~sep:",@ " SetRecomputeReason.iter RecomputeReason.pretty) th.th_to_recompute; compute_thread analysis th; (* We save all our results *) record_end_of_thread_analysis analysis; Mt_options.feedback "*** Thread %a computed" ThreadState.pretty th; ) else ( Mt_options.feedback "@[<hov 2>*** Thread %a has been@ created but@ \ not started. Skipping.@]" ThreadState.pretty th ) else ( Mt_options.feedback "*** Skipping thread %a as requested" ThreadState.pretty th; ); th.th_to_recompute <- SetRecomputeReason.empty; ) else Mt_options.debug "No need to recompute thread %a" ThreadState.pretty th ); Mt_options.feedback "***** Threads computed for iteration %d." analysis.iteration; (* We update the locked mutexes and started threads information of the cfg. This must obviously be done before shared variables are computed, but it supposes the thread creation structure is completely known. Hence, it is safer to do this at the end of a full iteration, instead of at the end of a thread *) Mt_options.feedback ~level:2 "* Computing live threads and locked mutexes"; iter_threads analysis (Mt_cfg.update_cfg_contexts analysis); Mt_options.feedback ~level:2 "* threads and mutexes computed"; let precise_accesses, written = compute_shared_vars analysis in analysis.concurrent_accesses_by_nodes <- precise_accesses; store_written_value analysis written; let mutexes = Mt_mutexes.mutexes_protecting_zones' precise_accesses in Mt_options.result "@[<v 0>Mutexes for concurrent accesses:@ %a@]" MutexesByZone.pretty mutexes; if Mt_options.CheckProtections.get () then begin let protections = Mt_mutexes.check_protection analysis precise_accesses in Mt_options.result "Detailed shared zones protections@.%a" Mt_mutexes.pretty_protections protections; let ill_protected = Mt_mutexes.ill_protected precise_accesses protections in let need_sync = Mt_mutexes.need_sync ill_protected in if need_sync <> [] then begin let pp fmt (stmt, z) = Format.fprintf fmt "@[%a (for %a)@]" Cil_datatype.Location.pretty (Cil_datatype.Stmt.loc stmt) Locations.Zone.pretty z in Mt_options.result "Statements needing manual synchronisation@.%a" (Pretty_utils.pp_list ~pre:"@[<v>" ~sep:"@ " ~suf:"@]" pp) need_sync end; end; Mt_options.feedback "***** Shared variables computed"; fold_threads analysis false (fun th v -> v || not (SetRecomputeReason.is_empty th.th_to_recompute)) ;; (* Remove "white" nodes in the cfg, ie accesses to variables that are not concurrent at all. Done at the very end of the analysis because - those nodes are needed before to reach the fixpoint - the marking of nodes by colors is not used by the analysis YYY: this can endanger restarting the analysis from the last point (the fixpoint may not be reached immediately, or we might reach a wrong one concerning shared variables). This should probably be done in a copy of the cfgs, but this means rewriting a fair amount of other analysis structures too *) let analysis = let precise_accesses = analysis.concurrent_accesses_by_nodes in let = Mt_shared_vars.Precise.all_zones_accessed precise_accesses in (* Update the informations in the cfgs *) iter_threads analysis (fun th -> Mt_shared_vars.Precise.remove_non_concur_zones_from_cfg shared_vars th.th_cfg ); Mt_shared_vars.Precise.mark_concur_access_in_cfg precise_accesses; if (not (Mt_options.KeepWhiteNodes.get ()) || not (Mt_options.KeepGreenNodes.get ())) && not (Mt_options.FullCfg.get ()) then iter_threads analysis (fun th -> let keep = match Mt_options.KeepWhiteNodes.get (), Mt_options.KeepGreenNodes.get () with | false, false -> Mt_cfg_types.ConcurrentAccess | false, true -> Mt_cfg_types.SharedVarNonConcurrentAccess | true, true -> Mt_cfg_types.NotReallySharedVar | true, false -> Mt_options.warning ~once:true "Incoherent@ combination@ of@ options@ %s@ \ and@ %s.@ Only@ non-shared@ variables@ will@ be@ removed." Mt_options.KeepWhiteNodes.option_name Mt_options.KeepGreenNodes.option_name; Mt_cfg_types.SharedVarNonConcurrentAccess in let cfg = Mt_cfg.remove_superfluous_nodes ~keep th.th_cfg in th.th_cfg <- cfg; ); ;; (* Auxiliary function iterating the analysis until the fixpoint is reached *) let reach_fixpoint analysis = Mt_options.feedback "******* Starting to iterate"; let rec aux i = Mt_options.feedback "***** Iteration %d" i; analysis.iteration <- i; let continue = one_iteration analysis in if continue && i < Mt_options.StopAfter.get () then aux (i+1) else (* Stop iteration *) if continue then Mt_options.feedback "@[<v 0>\ @[<hov 2>******* Analysis stopped after@ \ %d iterations.@ Remaining@ to@ do:@]@ \ %a@]" i (fun fmt () -> iter_threads analysis (fun th -> if not (SetRecomputeReason.is_empty th.th_to_recompute) then Format.fprintf fmt "@[<hov 2>Thread %a:@ %a@]@ " ThreadState.pretty_detailed th (Pretty_utils.pp_iter ~sep:",@ " ~pre:"" ~suf:"" SetRecomputeReason.iter RecomputeReason.pretty) th.th_to_recompute ) ) () else Mt_options.feedback "******* Analysis performed, %d iterations" i in aux 1
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>