Skip to content

Commit c29e1be

Browse files
committed
C++ instrumentation: fix for ranges support
The instrumentation scheme for C++ for ranges resulted in incorrect code when initializer lists were involved, e.g.: ``` for (auto i : {1, 2) { <for_body> } ``` being instrumented as ``` for (auto i : (witness(), {1, 2}) { <for_body> } ``` which is invalid C++ code. To fix this, we now instrument such expressions as followed: ``` witness(); for (auto i : {1, 2}) { <for_body> } ``` We can't execute the loop statement without executing the witness with a goto as it is forbidden to bypass initialization statements in C++. When there is an initialization statement, e.g.: ``` for (int j = 0; auto i : {1, 2}){} ``` we actually introduce an outer scope and instrument as followed: ``` { witness_j(); int j = 0; witness_i(); for (auto i : {1, 2}){} } ```
1 parent 92a6200 commit c29e1be

File tree

7 files changed

+210
-9
lines changed

7 files changed

+210
-9
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <initializer_list>
2+
3+
int
4+
main (void)
5+
{
6+
int sum = 0; // # init
7+
for (auto i : { 1, 2, 3 }) // # for-range
8+
{
9+
sum += i; // # for-body
10+
}
11+
return 0;
12+
}
13+
14+
//# test_for_range.cpp
15+
//
16+
// /init/ l+ ## 0
17+
// /for-range/ l+ ## 0
18+
// /for-body/ l+ ## 0
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
Test that gnatcov correctly instruments a for range with an initializer as the
3+
range expression and an initialization statement, e.g.
4+
```
5+
for (int i = 0; auto j : {1, 2}){}
6+
```
7+
"""
8+
9+
from SCOV.tc import TestCase
10+
from SUITE.context import thistest
11+
12+
TestCase().run()
13+
thistest.result()

tools/gnatcov/clang-extensions.adb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,29 @@ package body Clang.Extensions is
132132
CX_Rewriter_Insert_Text_Before_Token_C (Rew, Loc, Insert & ASCII.NUL);
133133
end CX_Rewriter_Insert_Text_Before_Token;
134134

135+
------------------------------------
136+
-- CX_Rewriter_Get_Rewritten_Text --
137+
------------------------------------
138+
139+
function CX_Rewriter_Get_Rewritten_Text
140+
(Rew : Rewriter_T;
141+
R : Source_Range_T) return String
142+
is
143+
function CX_Rewriter_Get_Rewritten_Text
144+
(Rew : Rewriter_T;
145+
R : Source_Range_T) return String_T
146+
with
147+
Import, Convention => C,
148+
External_Name => "clang_CXRewriter_getRewrittenText";
149+
150+
Rewritten_Text_C : constant String_T :=
151+
CX_Rewriter_Get_Rewritten_Text (Rew, R);
152+
Rewritten_Text : constant String := Get_C_String (Rewritten_Text_C);
153+
begin
154+
Dispose_String (Rewritten_Text_C);
155+
return Rewritten_Text;
156+
end CX_Rewriter_Get_Rewritten_Text;
157+
135158
-----------------------
136159
-- Spelling_Location --
137160
-----------------------

tools/gnatcov/clang-extensions.ads

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ package Clang.Extensions is
124124
-- Insert the text Insert before the token at the given location, and after
125125
-- any previously inserted string (at the same location).
126126

127+
function CX_Rewriter_Get_Rewritten_Text
128+
(Rew : Rewriter_T;
129+
R : Source_Range_T) return String
130+
with Inline;
131+
-- Return the rewritten text for the given source range.
132+
127133
function Get_Cursor_TU (C : Cursor_T) return Translation_Unit_T
128134
with
129135
Import, Convention => C,

tools/gnatcov/clang-wrapper.cc

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,10 +608,19 @@ clang_CXRewriter_insertTextBeforeToken (CXRewriter Rew, CXSourceLocation Loc,
608608
R.InsertTextAfter (Prv, Insert);
609609
}
610610

611-
/* Wrappers around source location analysis functions. */
611+
extern "C" CXString
612+
clang_CXRewriter_getRewrittenText (CXRewriter Rew, CXSourceRange Rng)
613+
{
614+
assert (Rew);
615+
Rewriter &R = *reinterpret_cast<Rewriter *> (Rew);
616+
SourceRange SR = translateCXSourceRange (Rng);
617+
return createDup (R.getRewrittenText (SR).c_str ());
618+
}
612619

613-
extern "C" unsigned
614-
clang_isMacroLocation (CXSourceLocation Loc)
620+
/* Wrappers around source location analysis functions. */
621+
622+
extern "C" unsigned
623+
clang_isMacroLocation (CXSourceLocation Loc)
615624
{
616625
const SourceLocation SLoc = translateSourceLocation (Loc);
617626
return SLoc.isMacroID () ? 1 : 0;

tools/gnatcov/instrument-c.adb

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ with Coverage; use Coverage;
3737
with Coverage_Options;
3838
with Hex_Images; use Hex_Images;
3939
with Inputs; use Inputs;
40-
with Instrument.C_Utils; use Instrument.C_Utils;
4140
with Outputs; use Outputs;
4241
with Paths; use Paths;
4342
with SCOs;
@@ -213,6 +212,11 @@ package body Instrument.C is
213212
Loc : Source_Location_T;
214213
Text : String);
215214

215+
overriding procedure Register_CXX_For_Range
216+
(Pass : Instrument_Pass_Kind;
217+
UIC : in out C_Unit_Inst_Context'Class;
218+
N : Cursor_T);
219+
216220
Record_PP_Info_Pass : aliased Record_PP_Info_Pass_Kind;
217221
Instrument_Pass : aliased Instrument_Pass_Kind;
218222

@@ -273,6 +277,54 @@ package body Instrument.C is
273277
function Insert_MCDC_State
274278
(UIC : in out C_Unit_Inst_Context'Class; Name : String) return String;
275279

280+
procedure Fix_CXX_For_Ranges (UIC : in out C_Unit_Inst_Context);
281+
-- This procedure fixes C++ for ranges that were purposefully instrumented
282+
-- wrongly. To instrument a C++ for range, we actually need to turn
283+
--
284+
-- for (int i = 0; auto j : {1, 2}) {}
285+
--
286+
-- into
287+
--
288+
-- {
289+
-- witness_i(); int i = 0;
290+
-- witness_j();
291+
-- for (auto j : {1, 2}) {}
292+
-- }
293+
--
294+
-- We introduce an outer scope to leave the initializer declaration
295+
-- lifetime unchanged, and to be able to easily instrument both the
296+
-- initializer declaration and the range expression.
297+
--
298+
-- Note that this is valid because you can't goto inside the loop
299+
-- (and thus skip the execution of witness_j) as it is
300+
-- forbidden to bypass declarations with initialization in C++,
301+
-- which "auto j : {1, 2}" is.
302+
--
303+
-- Though we can't do that all at once, as this changes the shape as the
304+
-- AST. As we rely on the AST node location to instrument statements and
305+
-- decisions, we would be instrumenting the decision at the wrong place,
306+
-- as we would instrument the initialization statement by moving it.
307+
--
308+
-- Thus, we do the normal instrumentation process, which will yield an
309+
-- unvalid C++ sequence, that we can easily fix in this procedure, by
310+
-- moving around the rewritten code.
311+
--
312+
-- The for ranges at the entry of this procedure will have been
313+
-- instrumented as followed (the added code is identified by <>):
314+
--
315+
-- <witness_j();> for (<witness_i();> int i = 0; auto j : {1, 2}) {}<}>
316+
--
317+
-- Two things to note here: the witness_j is executed before "int i = 0;"
318+
-- (which is wrong), and there is an unmatched closing brace.
319+
--
320+
-- To fix this, we actually need to move both the added code, _and_ the
321+
-- initializer statement before the <witness_j();>, and insert an opening
322+
-- brace before:
323+
--
324+
-- <{><witness_i();> int i = 0; <witness_j();> for (auto j : {1, 2}) {}<}>
325+
--
326+
-- and now we have valid, and correctly instrumented code.
327+
276328
----------------------------
277329
-- Source level rewriting --
278330
----------------------------
@@ -977,6 +1029,18 @@ package body Instrument.C is
9771029
CX_Rewriter_Insert_Text_After (Rew, Loc, Text);
9781030
end Insert_Text_After;
9791031

1032+
----------------------------
1033+
-- Register_CXX_For_Range --
1034+
----------------------------
1035+
1036+
overriding procedure Register_CXX_For_Range
1037+
(Pass : Instrument_Pass_Kind;
1038+
UIC : in out C_Unit_Inst_Context'Class;
1039+
N : Cursor_T) is
1040+
begin
1041+
UIC.Instrumented_CXX_For_Ranges.Append (N);
1042+
end Register_CXX_For_Range;
1043+
9801044
--------------------------
9811045
-- Instrument_Statement --
9821046
--------------------------
@@ -1222,6 +1286,43 @@ package body Instrument.C is
12221286
return Name;
12231287
end Insert_MCDC_State;
12241288

1289+
------------------------
1290+
-- Fix_CXX_For_Ranges --
1291+
------------------------
1292+
1293+
procedure Fix_CXX_For_Ranges (UIC : in out C_Unit_Inst_Context) is
1294+
begin
1295+
for N of UIC.Instrumented_CXX_For_Ranges loop
1296+
declare
1297+
Loc : constant Source_Location_T := Start_Sloc (N);
1298+
For_Init_Stmt : constant Cursor_T := Get_For_Init (N);
1299+
For_Init_Rng : constant Source_Range_T :=
1300+
Get_Cursor_Extent (For_Init_Stmt);
1301+
begin
1302+
-- Get the rewritten text for the initializing statement and
1303+
-- move it before any rewritten text before the for statement.
1304+
1305+
CX_Rewriter_Insert_Text_Before
1306+
(UIC.Rewriter,
1307+
Loc,
1308+
CX_Rewriter_Get_Rewritten_Text (UIC.Rewriter, For_Init_Rng));
1309+
1310+
-- Open the outer scope. It was closed during the instrumentation
1311+
-- process, so we do not need to take care of that.
1312+
1313+
CX_Rewriter_Insert_Text_Before (UIC.Rewriter, Loc, "{");
1314+
CX_Rewriter_Remove_Text (UIC.Rewriter, For_Init_Rng);
1315+
1316+
-- for (; auto i : {1, 2}) is invalid C++ code so insert a dummy
1317+
-- initializing expression to avoid the null statement here, as
1318+
-- it is easier than trying to move around the comma.
1319+
1320+
CX_Rewriter_Insert_Text_Before
1321+
(UIC.Rewriter, Start_Sloc (For_Init_Stmt), "true");
1322+
end;
1323+
end loop;
1324+
end Fix_CXX_For_Ranges;
1325+
12251326
type SC_Entry is record
12261327
N : Cursor_T;
12271328
-- Original statement node, used to compute macro expansion information
@@ -2033,14 +2134,28 @@ package body Instrument.C is
20332134
-- the range declaration initialization expression. Like all
20342135
-- statements, they can contain nested decisions.
20352136

2036-
Extend_Statement_Sequence
2037-
(For_Init_Stmt, ' ', Insertion_N => N);
2038-
Process_Decisions_Defer (For_Init_Stmt, 'X');
2137+
if not Is_Null (For_Init_Stmt) then
2138+
2139+
-- See Fix_CXX_For_Ranges for an explanation of the
2140+
-- below code.
2141+
2142+
Extend_Statement_Sequence
2143+
(For_Init_Stmt, ' ', Insertion_N => For_Init_Stmt);
2144+
Process_Decisions_Defer (For_Init_Stmt, 'X');
2145+
2146+
-- Preemptively end the introduced outer scope as it is
2147+
-- easier done when traversing the AST.
2148+
2149+
Append (Trailing_Braces, '}');
2150+
UIC.Pass.Register_CXX_For_Range (UIC, N);
2151+
end if;
2152+
2153+
-- Instrument the range as mentioned above
20392154

20402155
Extend_Statement_Sequence
20412156
(For_Range_Decl, ' ',
2042-
Insertion_N => For_Range_Decl,
2043-
Instr_Scheme => Instr_Expr);
2157+
Insertion_N => N,
2158+
Instr_Scheme => Instr_Stmt);
20442159
Process_Decisions_Defer (For_Range_Decl, 'X');
20452160

20462161
Set_Statement_Entry;
@@ -3179,6 +3294,11 @@ package body Instrument.C is
31793294
end loop;
31803295
end;
31813296

3297+
-- Once everything has been instrumented, fixup the for ranges. See the
3298+
-- documentation of Fix_CXX_For_Ranges.
3299+
3300+
Fix_CXX_For_Ranges (UIC);
3301+
31823302
-- We import the extern declaration of symbols instead of including the
31833303
-- header where they are defined.
31843304
--

tools/gnatcov/instrument-c.ads

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ with Clang.Index; use Clang.Index;
3131
with Clang.Rewrite; use Clang.Rewrite;
3232

3333
with Files_Table; use Files_Table;
34+
with Instrument.C_Utils; use Instrument.C_Utils;
3435
with Instrument.Common; use Instrument.Common;
3536
with Slocs; use Slocs;
3637

@@ -343,6 +344,11 @@ package Instrument.C is
343344

344345
Current_File_Scope : Source_File_Index;
345346
-- Source file in wich the last scope encountered was opened.
347+
348+
Instrumented_CXX_For_Ranges : Cursor_Vectors.Vector;
349+
-- List of instrumented for ranges. For an explanation of why we need
350+
-- to store these, see the documentation of the Fix_CXX_For_Ranges
351+
-- subprogram.
346352
end record;
347353

348354
type C_Source_Rewriter is tagged limited private;
@@ -428,6 +434,12 @@ private
428434
Loc : Source_Location_T;
429435
Text : String) is null;
430436

437+
procedure Register_CXX_For_Range
438+
(Pass : Pass_Kind;
439+
UIC : in out C_Unit_Inst_Context'Class;
440+
N : Cursor_T) is null;
441+
-- See the documentation of Fix_CXX_For_Ranges
442+
431443
type C_Source_Rewriter is limited new Ada.Finalization.Limited_Controlled
432444
with record
433445
CIdx : Index_T;

0 commit comments

Comments
 (0)