-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathdecision_map.adb
3244 lines (2635 loc) · 117 KB
/
decision_map.adb
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
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
------------------------------------------------------------------------------
-- --
-- GNATcoverage --
-- --
-- Copyright (C) 2008-2024, AdaCore --
-- --
-- GNATcoverage is free software; you can redistribute it and/or modify it --
-- under terms of the GNU General Public License as published by the Free --
-- Software Foundation; either version 3, or (at your option) any later --
-- version. This software is distributed in the hope that it will be useful --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- --
-- TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public --
-- License for more details. You should have received a copy of the GNU --
-- General Public License distributed with this software; see file --
-- COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy --
-- of the license. --
------------------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with GNAT.Regexp; use GNAT.Regexp;
with GNAT.Strings; use GNAT.Strings;
with Interfaces; use Interfaces;
with System.Storage_Elements;
with Binary_Files; use Binary_Files;
with Coverage.Source; use Coverage.Source;
with Coverage.Tags; use Coverage.Tags;
with Coverage_Options; use Coverage_Options;
with Diagnostics; use Diagnostics;
with Elf_Common;
with Elf_Disassemblers; use Elf_Disassemblers;
with Execs_Dbase; use Execs_Dbase;
with Disa_Symbolize;
with Files_Table; use Files_Table;
with Hex_Images; use Hex_Images;
with Highlighting;
with Qemu_Traces;
with Slocs; use Slocs;
with Strings; use Strings;
with Switches; use Switches;
with Traces_Dbase; use Traces_Dbase;
with Traces_Files; use Traces_Files;
with Traces_Names; use Traces_Names;
package body Decision_Map is
-- This unit instantiates containers and we want to avoid too much
-- performance cost when using references to their elements, so suppress
-- tampering checks.
pragma Suppress (Tampering_Check);
use Ada.Containers;
use Coverage;
use all type Unbounded_String;
Decision_Map_Base : Traces_Base;
-- The decision map is a list of code addresses, so we manage it as a
-- trace database.
package Decision_Occurrence_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Decision_Occurrence_Access);
use type Decision_Occurrence_Vectors.Vector;
-- A list of decision occurrences, used for Decision_Occurrence_Maps below,
-- and also to maintain the stack of open decision occurrences while
-- analysing object code.
package Decision_Occurrence_Maps is new Ada.Containers.Ordered_Maps
(Key_Type => SCO_Id,
Element_Type => Decision_Occurrence_Vectors.Vector);
Decision_Occurrence_Map : Decision_Occurrence_Maps.Map;
-- The decision occurrence map lists all object code occurrences of each
-- source decision (identified by its SCO_Id).
function First_CBI_PC (D_Occ : Decision_Occurrence_Access) return Pc_Type;
-- Return the PC of the first conditional branch instruction in D_Occ.
-- Used as unique identifier for occurrences.
type Call_Kind is (Normal, Raise_Exception, Finalizer, Finalization_Exp);
-- Classification of calls within a decision:
-- - normal calls to subprograms
-- - calls that are known to raise an exception
-- - calls to generated block finalizers / cleanup code
-- - calls to finalization expansion symbols - excluding finalizers
--
-- A basic block in object code
package Outcome_Reached_Maps is new Ada.Containers.Ordered_Maps
(Key_Type => Pc_Type,
Element_Type => Boolean);
-- Map of decision occurrence (identified by PC of first conditional
-- branch instruction) to Outcome_Reached status (see below).
type Basic_Block is record
From, To_PC, To : Pc_Type := No_PC;
-- Start and end addresses (note: To is not necessarily a valid PC value
-- but instead the address of the last byte in the last instruction of
-- the BB, whose first byte is at To_PC).
-- Properties of the branch instruction at the end of the basic block:
Branch_Dest, FT_Dest : Dest := (Target => No_PC, Delay_Slot => No_PC);
-- Branch and fallthrough destinations
Branch : Branch_Kind := Br_None;
-- Branch kind
Cond : Boolean := False;
-- True if conditional branch
First_Cond : Boolean := False;
-- True if Cond and this is the first conditional branch in the
-- enclosing decision occurrence.
Call : Call_Kind := Normal;
Called_Sym : String_Access;
-- If Branch = Br_Call, information about the called subprogram
Condition : SCO_Id := No_SCO_Id;
-- If this is a conditional branch testing a condition, identifies it
Branch_SCO : SCO_Id := No_SCO_Id;
Branch_SCO_Tag : SC_Tag := No_SC_Tag;
-- Condition or Statement SCO for To_PC, with corresponding tag, for
-- statistics purposes.
-- If multiple SCOs are associated with this PC:
-- - if one of them is a Condition, it is selected (in which case
-- BB.Branch_SCO = BB.Condition)
-- - else an arbitrary statement SCO is selected.
-- Note that no two condition SCOs may be associated with a given PC.
Outcome_Reached : Outcome_Reached_Maps.Map;
-- Set True for basic blocks that are reached after the outcome of the
-- enclosing decision is determined: subsequent conditional branch
-- instructions in the decision occurrence must be excluded from
-- coverage analysis. Set False for basic blocks that are known to be
-- reachable while the outcome is not determined yet. Note that this
-- is computed per decision occurrence, because if two occurrences
-- of the same decision appear in close succession (e.g. inlined one
-- after the other, or possibly as part of an unrolled loop), the
-- initial (pre-outcome) basic block of the second occurrence might
-- need to be marked as a post-outcome block for the first one.
Jump_Only : Boolean := False;
-- Set True for basic blocks that are a singleton unconditional branch.
-- The compiler creates such basic blocks only to hold source locations:
-- they can be useful for decision mapping heuristics, but can also be
-- skipped to analyze the destination BB if needed (see
-- Label_From_Other).
end record;
No_Basic_Block : constant Basic_Block := (others => <>);
function "<" (L, R : Basic_Block) return Boolean;
-- Order by From
Finalizer_Symbol_Pattern : constant Regexp := Compile
(".*___finalizer\.[0-9]+");
Pre_Finalizer_Symbol_Pattern : constant Regexp := Compile
("system__finalization_primitives__.*");
package Pc_Sets is new Ada.Containers.Ordered_Sets (Pc_Type);
package Basic_Block_Sets is new Ada.Containers.Ordered_Sets (Basic_Block);
function Find_Basic_Block
(Basic_Blocks : Basic_Block_Sets.Set;
PC : Pc_Type) return Basic_Block_Sets.Cursor;
function Find_Basic_Block
(Basic_Blocks : Basic_Block_Sets.Set;
PC : Pc_Type) return Basic_Block;
-- Return the basic block containing PC from the given set, or
-- No_Element / No_Basic_Block if none.
type Branch_Count_Array is
array (Branch_Kind, Any_Statement_Kind, Boolean) of Natural;
-- Branch counts by branch kind and, for branches associated with a
-- statement SCO, statement kind. The third dimension discriminates
-- between conditional and non-conditional branches.
type Cond_Branch_Kind is (None, Statement, Condition, Check, Cleanup);
-- Statistics category for a conditional branch instruction:
-- * no SCO
-- * statement SCO
-- * condition SCO, non-exception
-- * condition SCO, exception
-- * cleanup actions after outcome has been determined
type Cond_Branch_Count_Array is array (Cond_Branch_Kind) of Natural;
type Branch_Statistics is record
Branch_Counts : Branch_Count_Array :=
(others => (others => (others => 0)));
Cond_Branch_Counts : Cond_Branch_Count_Array := (others => 0);
Non_Traceable : Natural := 0;
end record;
type Cond_Branch_Context is limited record
Decision_Stack : Decision_Occurrence_Vectors.Vector;
-- The stack of open decision occurrences
Basic_Blocks : Basic_Block_Sets.Set;
-- All basic blocks in the routine being analyzed
Stats : Branch_Statistics;
-- Statistics on conditional branches in the routine being analyzed
Subprg : Address_Info_Acc;
-- Info of enclosing subprogram
end record;
procedure Analyze_Routine
(Name : String_Access;
Exec : Exe_File_Acc;
Insns : Binary_Content);
-- Build decision map for the given subprogram
procedure Analyze_Conditional_Branch
(Exec : Exe_File_Acc;
Insn : Binary_Content;
Tag : SC_Tag;
C_SCO : SCO_Id;
Branch_Dest : Dest;
FT_Dest : Dest;
Ctx : in out Cond_Branch_Context;
BB : in out Basic_Block);
-- Process one conditional branch instruction for the given condition SCO.
-- Sets BB.Condition to C_SCO, if applicable.
procedure Analyze_Call (Exe : Exe_File_Acc; BB : in out Basic_Block);
-- Set information about the call/ret instruction at the end of BB
procedure Skip_Constant_Conditions
(Cond : in out SCO_Id;
Outcome : out Tristate;
Skipped : access SCO_Sets.Set);
-- Set Cond to the next runtime condition starting at Cond (included) and
-- Outcome to Unknown. If there is no runtime condition before reaching an
-- outcome, set Cond to No_SCO_Id and Outcome to the known outcome. Store
-- the SCO of skipped conditions in Skipped.
function Is_Expected_First_Condition
(Decision : SCO_Id;
Condition : SCO_Id) return Boolean;
-- Return whether Condition can be the first condition to be evaluated at
-- runtime for Decision.
function Is_Last_Runtime_Condition
(D_Occ : Decision_Occurrence_Access) return Boolean;
-- Return whether the last condition seen after analyzing conditional
-- branches is the last one to be evaluated at runtime.
procedure Analyze_Decision_Occurrence
(Exe : Exe_File_Acc;
Ctx : in out Cond_Branch_Context;
D_Occ : Decision_Occurrence_Access);
-- Perform logical structure analysis of the given decision occurrence
procedure Append_Decision_Occurrence (D_Occ : Decision_Occurrence_Access);
-- Record association of D_Occ with its decision
function Image (BB : Basic_Block) return String;
pragma Unreferenced (Image);
-- For debugging purposes
procedure Write_Map (Filename : String);
-- Write the contents of the decision map to the named file
function Check_Possible_Successor
(D_SCO : SCO_Id;
This_Condition : Any_Condition_Index;
Next_Condition : Condition_Index) return Tristate;
-- Determine whether Next_Condition is a valid successor of This_Condition
-- in the given decision, and if so, return the associated origin (i.e.
-- the associated valuation of This_Condition). If not, return Unknown.
-- This_Condition may be No_Condition_Index, in which case we check
-- whether Next_Condition is a valid first condition to be tested
-- (and return Unknown iff it's not).
---------
-- "<" --
---------
function "<" (L, R : Basic_Block) return Boolean is
begin
return L.From < R.From;
end "<";
function "<" (L, R : Cond_Branch_Loc) return Boolean is
use System.Storage_Elements;
begin
return To_Integer (L.Exe.all'Address) < To_Integer (R.Exe.all'Address)
or else (L.Exe = R.Exe and then L.PC < R.PC);
end "<";
-------------
-- Analyze --
-------------
procedure Analyze (Exe_File : Exe_File_Acc) is
Sym_It : Addresses_Iterator;
Sym : Address_Info_Acc;
Sec : Address_Info_Acc;
First_Symbol_Occurrence : Boolean;
Subp_Key : Subprogram_Key;
Subp_Info : Subprogram_Info;
begin
-- Do not map decisions more than once: although it is correct to run
-- this more than once per executable, this is a time consuming
-- operation.
if Exe_File.Has_Decision_Mapped then
return;
end if;
Build_Debug_Lines (Exe_File.all);
-- Add routine names of interest to routines database
Routine_Names_From_Lines (Exe_File, Has_SCO'Access);
-- Analyze control flow graph
Init_Iterator (Exe_File.all, Symbol_Addresses, Sym_It);
loop
Next_Iterator (Sym_It, Sym);
exit when Sym = null;
-- Only process symbols we are interested in
if Is_Routine_Of_Interest (Sym.Symbol_Name.all) then
Sec := Sym.Parent;
Load_Section_Content (Exe_File.all, Sec);
Key_From_Symbol (Exe_File, Sym, Subp_Key);
Add_Code
(Subp_Key,
Exe_File,
Sec.Section_Sec_Idx,
Slice (Sec.Section_Content, Sym.First, Sym.Last),
First_Symbol_Occurrence,
Subp_Info);
-- Process the reference instance of the routine
if First_Symbol_Occurrence then
Tag_Provider.Enter_Routine (Subp_Info);
-- Map routine instructions to SCOs
Analyze_Routine
(Sym.Symbol_Name,
Exe_File,
Slice (Sec.Section_Content, Sym.First, Sym.Last));
end if;
end if;
end loop;
Exe_File.Set_Decision_Mapped;
end Analyze;
---------------------------
-- Subp_Raises_Exception --
---------------------------
function Subp_Raises_Exception (Symbol_Name : String) return Boolean is
begin
return
(Symbol_Name = "__gnat_last_chance_handler"
or else
Symbol_Name = "system__assertions__raise_assert_failure"
or else
Has_Prefix (Symbol_Name, Prefix => "__gnat_rcheck_"));
end Subp_Raises_Exception;
------------------
-- Analyze_Call --
------------------
procedure Analyze_Call (Exe : Exe_File_Acc; BB : in out Basic_Block) is
pragma Assert (BB.Branch = Br_Call);
Sym : constant Address_Info_Acc :=
Get_Symbol (Exe.all, BB.Branch_Dest.Target);
begin
if Sym /= null then
BB.Called_Sym := Sym.Symbol_Name;
end if;
if BB.Called_Sym = null then
return;
end if;
declare
Sym_Name : constant String :=
Platform_Independent_Symbol (BB.Called_Sym.all, Exe.all);
begin
if Sym_Name = "ada__exceptions__triggered_by_abort" then
BB.Call := Finalizer;
elsif Subp_Raises_Exception (Sym_Name) then
BB.Call := Raise_Exception;
elsif Is_Finalizer_Symbol (Sym_Name) then
BB.Call := Finalizer;
elsif Is_Finalization_Expansion_Symbol (Sym_Name) then
BB.Call := Finalization_Exp;
end if;
end;
-- If call is known to never return, clear out falltrhough destination
if BB.Call = Raise_Exception then
BB.FT_Dest := (No_PC, No_PC);
end if;
end Analyze_Call;
--------------------------------
-- Analyze_Conditional_Branch --
--------------------------------
procedure Analyze_Conditional_Branch
(Exec : Exe_File_Acc;
Insn : Binary_Content;
Tag : SC_Tag;
C_SCO : SCO_Id;
Branch_Dest : Dest;
FT_Dest : Dest;
Ctx : in out Cond_Branch_Context;
BB : in out Basic_Block)
is
pragma Assert (Kind (C_SCO) = Condition);
D_SCO : constant SCO_Id := Enclosing_Decision (C_SCO);
function Is_Begin_Handler_Call (D : Dest) return Boolean;
-- True if Dest branches to a basic block ending in a
-- __gnat_begin_handler call.
---------------------------
-- Is_Begin_Handler_Call --
---------------------------
function Is_Begin_Handler_Call (D : Dest) return Boolean is
BB : constant Basic_Block :=
Find_Basic_Block (Ctx.Basic_Blocks, D.Target);
begin
return BB /= No_Basic_Block
and then BB.Branch = Br_Call
and then BB.Called_Sym /= null
and then Platform_Independent_Symbol (BB.Called_Sym.all, Exec.all)
in "__gnat_begin_handler" | "__gnat_begin_handler_v1";
end Is_Begin_Handler_Call;
-- Start of processing for Analyze_Conditional_Branch
begin
-- If one of the edges branches to a __gnat_begin_handler
-- call, then this conditional branch is an exception
-- dispatch test, and does not contribute to any decision.
if Is_Begin_Handler_Call (Branch_Dest)
or else
Is_Begin_Handler_Call (FT_Dest)
then
Report
(Exec, Insn.First, "exception dispatch", Kind => Notice);
return;
end if;
-- Record address in SCO descriptor
Add_Address (C_SCO, Insn.First);
-- Update control flow information
Process_Condition :
declare
Parent_SCO : SCO_Id;
-- Parent SCO of D_SCO, if appropriate
Enclosing_D_SCO : SCO_Id;
-- For a nested decision, the enclosing decision
Enclosing_Inlined_Body : Address_Info_Acc;
-- For a condition occurring in an inlined body, descriptor of that
-- body.
Cond_Index : constant Condition_Index := Index (C_SCO);
-- Index of C_SCO in D_SCO
Starting_Evaluation : constant Boolean :=
Is_Expected_First_Condition
(D_SCO, Condition (D_SCO, Cond_Index));
-- True if this condition can be the first one evaluated in
-- its decision.
DS_Top : Decision_Occurrence_Access;
-- Innermost currently open decision evaluation
function Is_Expected_Condition
(CI : Condition_Index;
Report_If_Unexpected : Boolean := False) return Boolean;
-- Check whether we expect to evaluate CI: either we remain in the
-- current condition (case of a condition that requires multiple
-- branches), or we move to the next one..
---------------------------
-- Is_Expected_Condition --
---------------------------
function Is_Expected_Condition
(CI : Condition_Index;
Report_If_Unexpected : Boolean := False) return Boolean
is
Current_CI : Condition_Index renames DS_Top.Seen_Condition;
begin
if
-- Case of remaining in the current evaluation, or starting a
-- new one if there's none in progress.
CI = Condition_Index'Max (Current_CI, 0)
-- Else the next condition is reachable through the fallthrough
-- edge of the current condition, so it must be a possible
-- successor.
or else
Check_Possible_Successor
(DS_Top.Decision,
This_Condition => Current_CI,
Next_Condition => CI) /= Unknown
then
return True;
end if;
if Report_If_Unexpected then
declare
Msg : Unbounded_String;
begin
Msg :=
+("unexpected condition" & CI'Img
& " in decision " & Image (DS_Top.Decision));
if Tag /= No_SC_Tag then
Append (Msg, ", tag=" & Tag_Provider.Tag_Name (Tag));
end if;
-- This could correspond to some finalization code, that has
-- a debug info code location corresponding to a condition.
-- We will silence it unless explicitely requested with a
-- verbose mode.
Report (Exec, Insn.First, +Msg, Kind => Notice);
end;
end if;
return False;
end Is_Expected_Condition;
-- Start of processing for Process_Condition
begin
-- Determine enclosing SCO
Parent_SCO := Parent (D_SCO);
if Parent_SCO /= No_SCO_Id
and then Kind (Parent_SCO) = Condition
then
Enclosing_D_SCO := Enclosing_Decision (Parent_SCO);
else
Enclosing_D_SCO := No_SCO_Id;
end if;
-- Determine innermost enclosing inlined body
Enclosing_Inlined_Body :=
Get_Address_Info
(Exec.all, Inlined_Subprogram_Addresses, Insn.First);
-- Flush completed decisions from the Decision_Stack
while Ctx.Decision_Stack.Length > 0 loop
DS_Top := Ctx.Decision_Stack.Last_Element;
exit when DS_Top.Decision = D_SCO
and then DS_Top.Inlined_Body = Enclosing_Inlined_Body
and then Is_Expected_Condition (Cond_Index);
if DS_Top.Decision = Enclosing_D_SCO then
-- Here if the parent of our decision is part of a
-- condition in another decision, and DS_Top is that
-- enclosing decision.
if not Is_Expected_Condition (Index (Parent_SCO),
Report_If_Unexpected => True)
then
return;
end if;
DS_Top := null;
exit;
end if;
-- If the condition being tested is the first of its decision,
-- then we may be starting a new decision occurrence: determine
-- whether it is nested in the current one.
if Starting_Evaluation then
if DS_Top.Seen_Condition = DS_Top.Last_Cond_Index
and then DS_Top.Last_Cond_Index > 0
then
-- Previous evaluation is complete: pop it
null;
elsif DS_Top.Inlined_Body /= null
and then Insn.First not in DS_Top.Inlined_Body.First
.. DS_Top.Inlined_Body.Last
then
-- Exited inlined body of previous evaluation: pop it
null;
else
-- Nested decision: remain in current evaluation
exit;
end if;
elsif (DS_Top.Inlined_Body = null
and then Enclosing_Inlined_Body /= null)
or else
(DS_Top.Inlined_Body /= null
and then Enclosing_Inlined_Body /= DS_Top.Inlined_Body
and then Insn.First in DS_Top.Inlined_Body.First
.. DS_Top.Inlined_Body.Last)
then
-- Entering an inlined body: do not presume that the current
-- evaluation is completed.
exit;
-- Check whether call site loc is within current decision???
-- Difficulty: case of nested inlined calls, we need to find
-- the call site for the outermost call in that case???
end if;
-- Otherwise pop completed evaluations from the stack until
-- we find the relevant pending one.
Analyze_Decision_Occurrence (Exec, Ctx, DS_Top);
Ctx.Decision_Stack.Delete_Last;
DS_Top := null;
end loop;
-- Check if we are evaluating part of a parent decision already on
-- the decision stack, and point DS_Top at that decision if so. This
-- is not necessarily the immediately enclosing decision, in
-- particular with complex CFGs resulting from the use of quantified
-- expressions.
--
-- Typically, in the example sketched below:
--
-- < D5 >
-- --------------------------------------------------------
-- R := (for all x ... => P(x)) and then (for all x ... => Q(x))
-- ^^^^
-- D9
-- C10
-- ---------------------- -----------------------
-- C7 (1st cond of D5) C8 (2nd cond of D5)
--
-- We could well see
-- * A first branch for C7, starting D5
-- * A branch for C10, starting D9, then
-- * Another branch for C7, still for D5, implementing
-- part of the first for-all control flow.
--
-- If we overlook ancestors and start a new decision occurrence
-- everytime we encounter a condition not part of the being-analyzed
-- decision, then we will start two occurrences of the decision 5,
-- which is not what we want.
for D_Occ of Ctx.Decision_Stack loop
if D_Occ.Decision = D_SCO then
DS_Top := D_Occ;
end if;
end loop;
-- Push a new occurrence on the evaluation stack, if needed
if
-- No pending evaluation
DS_Top = null
-- Evaluating a new, different decision than an enclosing one
or else DS_Top.Decision /= D_SCO
-- Start of evaluation in a new inlined body: cannot be the same
-- decision occurrence.
or else (Starting_Evaluation
and then DS_Top.Inlined_Body /= Enclosing_Inlined_Body)
then
declare
function Enclosing_Inlined_Body_Image return String;
-- Return information about enclosing inlined body if in one,
-- else null string.
----------------------------------
-- Enclosing_Inlined_Body_Image --
----------------------------------
function Enclosing_Inlined_Body_Image return String is
begin
if Enclosing_Inlined_Body /= null then
return " in inlined call from "
& Image (Enclosing_Inlined_Body.Call_Sloc)
& " ("
& Hex_Image (Enclosing_Inlined_Body.First)
& ".." & Hex_Image (Enclosing_Inlined_Body.Last) & ")";
else
return "";
end if;
end Enclosing_Inlined_Body_Image;
begin
Report (Exec, Insn.First,
"starting occurrence"
& Enclosing_Inlined_Body_Image,
SCO => D_SCO,
Kind => Notice);
end;
DS_Top := new Decision_Occurrence'
(Last_Cond_Index => Last_Cond_Index (D_SCO),
Decision => D_SCO,
Inlined_Body => Enclosing_Inlined_Body,
others => <>);
if not Is_Expected_Condition (Cond_Index,
Report_If_Unexpected => True)
then
return;
end if;
Ctx.Decision_Stack.Append (DS_Top);
end if;
-- Here after pushing context for current decision, if needed
pragma Assert (DS_Top.Decision = D_SCO);
-- Record condition occurrence
Report
(Exec, Insn.First,
"cond branch for " & Image (C_SCO)
& " (" & Img (Integer (Index (C_SCO))) & ")",
Kind => Notice);
pragma Assert (BB.Condition = No_SCO_Id);
BB.Condition := C_SCO;
if Cond_Index > DS_Top.Seen_Condition then
DS_Top.Seen_Condition := Cond_Index;
end if;
DS_Top.Conditional_Branches.Append (Insn.First);
Cond_Branch_Map.Insert
((Exec, Insn.First),
Cond_Branch_Info'
(Last_PC => Insn.Last,
Decision_Occurrence => DS_Top,
Condition => C_SCO,
Edges =>
(Branch =>
(Destination => Branch_Dest,
Dest_Kind =>
(if BB.Branch = Br_Ret then Outcome else Unknown),
others => <>),
Fallthrough =>
(Destination => FT_Dest,
others => <>))));
end Process_Condition;
end Analyze_Conditional_Branch;
------------------------------
-- Skip_Constant_Conditions --
------------------------------
procedure Skip_Constant_Conditions
(Cond : in out SCO_Id;
Outcome : out Tristate;
Skipped : access SCO_Sets.Set)
is
Next_Cond : SCO_Id;
Cond_Value : Tristate;
begin
if Cond = No_SCO_Id then
Outcome := Unknown;
return;
end if;
loop
Cond_Value := SC_Obligations.Value (Cond);
if Cond_Value = Unknown then
-- Condition tested at run time
Outcome := Unknown;
return;
else
-- Condition with compile time known value: skip
if Skipped /= null then
Skipped.Include (Cond);
end if;
Next_Cond := Next_Condition (Cond, To_Boolean (Cond_Value));
if Next_Cond = No_SCO_Id then
-- No successor: outcome reached
Outcome := SC_Obligations.Outcome
(Cond, To_Boolean (Cond_Value));
Cond := No_SCO_Id;
return;
else
-- Continue by jumping to its (only) successor
Cond := Next_Cond;
end if;
end if;
end loop;
end Skip_Constant_Conditions;
---------------------------------
-- Is_Expected_First_Condition --
---------------------------------
function Is_Expected_First_Condition
(Decision : SCO_Id;
Condition : SCO_Id) return Boolean
is
use SCO_Sets;
First_Condition : SCO_Id :=
SC_Obligations.Condition (Decision, 0);
Outcome : Tristate;
Possible_First_Conditions : aliased SCO_Sets.Set;
begin
Skip_Constant_Conditions
(First_Condition, Outcome, Possible_First_Conditions'Access);
return Condition = First_Condition
or else
Possible_First_Conditions.Contains (Condition);
end Is_Expected_First_Condition;
-------------------------------
-- Is_Last_Runtime_Condition --
-------------------------------
function Is_Last_Runtime_Condition
(D_Occ : Decision_Occurrence_Access) return Boolean
is
CI_SCO : SCO_Id;
Outcome : Tristate;
begin
-- If this is the last condition in the whole decision, so it is
-- obviously acceptable.
if D_Occ.Last_Cond_Index = D_Occ.Seen_Condition then
return True;
end if;
-- When the last conditions are constant, the last seen condition can be
-- different that the last decision condition. Return whether there is
-- at least one runtime condition between the last seen condition and
-- skipping constants conditions.
for Value in Boolean'Range loop
CI_SCO := Next_Condition
(Condition (D_Occ.Decision, D_Occ.Seen_Condition),
Value);
Skip_Constant_Conditions (CI_SCO, Outcome, null);
if CI_SCO /= No_SCO_Id then
return False;
end if;
end loop;
-- We reached the outcome by both original's condition outgoing
-- edges without meeting any runtime condition, thus the last seen
-- condition was the last runtime one.
return True;
end Is_Last_Runtime_Condition;
---------------------------------
-- Analyze_Decision_Occurrence --
---------------------------------
procedure Analyze_Decision_Occurrence
(Exe : Exe_File_Acc;
Ctx : in out Cond_Branch_Context;
D_Occ : Decision_Occurrence_Access)
is
First_Seen_Condition_PC : constant Pc_Type :=
D_Occ.Conditional_Branches.First_Element;
Last_Seen_Condition_PC : constant Pc_Type :=
D_Occ.Conditional_Branches.Last_Element;
-- Note: all the analysis is done under control of an initial check that
-- D_Occ.Seen_Condition = D_Occ.Last_Condition_Index
Last_CBI : constant Cond_Branch_Info :=
Cond_Branch_Map.Element ((Exe, Last_Seen_Condition_PC));
-- For destinations for which we have identified origin information,
-- reference to the conditional branch and edge having that destination,
-- which carries the known information. This information can be used to
-- copy edge information for edges that share the same destination.
-- Note that this assumes that control flow changes fully capture the
-- values of short-circuit operators. This still holds in cases where
-- such a value is captured in a temporary variable, because:
-- - for the case of branches corresponding to non-short-circuit value
-- this is trivial (the destination corresponds to the single point
-- in code where the value is assigned True).
-- - if two instructions branch to the same destination past the
-- assignment, then both must bypass it (there can't be one
-- bypassing it and one occurring after the assignment has been
-- actually evaluated).
type Known_Destination is record
Cond_Branch_PC : Pc_Type;
Edge : Edge_Kind;
end record;
package Known_Destination_Maps is new Ada.Containers.Ordered_Maps
(Key_Type => Dest,
Element_Type => Known_Destination);
Known_Destinations : Known_Destination_Maps.Map;
function Get_CBE (KD : Known_Destination) return Cond_Edge_Info;
-- Return the edge information associated to KD
package Dest_Sets is new Ada.Containers.Ordered_Sets (Dest);
Known_Outcome : array (Boolean) of Dest_Sets.Set;
-- When set, each element of this array is a set of edge destinations
-- known to correspond to the respective outcome of the decision.
procedure Trace_Destination
(CBI : Cond_Branch_Info;
Edge : Edge_Kind;
Edge_Info : in out Cond_Edge_Info);
-- Inspect the basic block containing Edge's destination, and if
-- necessary any basic block we unconditionally branch to from there,
-- until we find a conditional branch or a call to an exception-raising
-- routine.
procedure Label_Destination
(Cond_Branch_PC : Pc_Type;
CBI : in out Cond_Branch_Info;
Edge : Edge_Kind);
-- First pass of control flow analysis: test if Edge's destination
-- matches either of Last_CBI's edges' destination, and if so mark it as
-- an outcome destination.
procedure Label_From_Opposite
(Cond_Branch_PC : Pc_Type;
CBI : in out Cond_Branch_Info;
Edge : Edge_Kind);
-- Second pass of control flow analysis: if Edge is not qualified yet,
-- but the opposite destination of CBI is, deduce qualification for Edge
-- from that information.
procedure Label_From_Other
(Cond_Branch_PC : Pc_Type;
CBI : in out Cond_Branch_Info;