Skip to content

Commit 0d8b3ae

Browse files
committed
instr-c: Introduce a generic witness to instrument C++ call chains
1 parent 44576fb commit 0d8b3ae

File tree

6 files changed

+235
-51
lines changed

6 files changed

+235
-51
lines changed

tools/gnatcov/clang-extensions.adb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ package body Clang.Extensions is
8585
return Is_Instrumentable_Call_Expr_C (C) /= 0;
8686
end Is_Instrumentable_Call_Expr;
8787

88+
-------------------------------
89+
-- Is_CXX_Member_Call_Expr --
90+
-------------------------------
91+
92+
function Is_Prefixed_CXX_Member_Call_Expr (C : Cursor_T) return Boolean is
93+
function Is_CXX_Member_Call_Expr_C (C : Cursor_T) return unsigned
94+
with
95+
Import, Convention => C,
96+
External_Name => "clang_isPrefixedCXXMemberCallExpr";
97+
begin
98+
return Is_CXX_Member_Call_Expr_C (C) /= 0;
99+
end Is_Prefixed_CXX_Member_Call_Expr;
100+
88101
--------------------
89102
-- Get_Opcode_Str --
90103
--------------------

tools/gnatcov/clang-extensions.ads

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,33 @@ package Clang.Extensions is
130130
-- TODO??? Actually decide what to do for the rest, so Ctor/Dtor call
131131
-- coverage makes sense.
132132

133+
function Is_Prefixed_CXX_Member_Call_Expr (C : Cursor_T) return Boolean
134+
with Inline;
135+
-- Return True if the given cursor is a statement with type
136+
-- Stmt::CXXMemberCallExprClass, and if it is a prefixed method call
137+
-- (meaning not a method that is called from the body of another method
138+
-- in which it is possible to simply omit `this->`).
139+
140+
function Get_CXX_Member_Call_Expr_SCO_Sloc_Range
141+
(C : Cursor_T) return Source_Range_T
142+
with
143+
Import,
144+
Convention => C,
145+
External_Name => "clang_getCXXMemberCallExprSCOSlocRange";
146+
-- Assume the given cursor is a Stmt::CXXMemberCallExprClass.
147+
-- Given the expression is `Foo.Bar(Baz)`, it will return a source range
148+
-- containing `.Bar`.
149+
150+
function Get_CXX_Member_Call_Expr_Base_Sloc_Range
151+
(C : Cursor_T) return Source_Range_T
152+
with
153+
Import,
154+
Convention => C,
155+
External_Name => "clang_getCXXMemberCallExprBaseSlocRange";
156+
-- Assume the given cursor is a Stmt::CXXMemberCallExprClass.
157+
-- Given the expression is `Foo.Bar(Baz)`, it will return the source
158+
-- range of `Foo`.
159+
133160
function Is_Constexpr (C : Cursor_T) return Boolean with Inline;
134161

135162
function Unwrap (C : Cursor_T) return Cursor_T

tools/gnatcov/clang-wrapper.cc

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
/* Make sure we refer to the static version of symbols on Windows, not to DLL
2121
importers. */
2222

23+
#include "clang-c/CXSourceLocation.h"
24+
#include "clang/AST/Expr.h"
2325
#define CINDEX_NO_EXPORTS
2426

2527
#include "libclang/CXCursor.h"
@@ -33,6 +35,7 @@
3335
#include "clang/AST/ExprCXX.h"
3436
#include "clang/AST/ParentMapContext.h"
3537
#include "clang/AST/StmtCXX.h"
38+
#include "clang/Basic/CharInfo.h"
3639
#include "clang/Basic/SourceLocation.h"
3740
#include "clang/Basic/SourceManager.h"
3841
#include "clang/Frontend/ASTUnit.h"
@@ -351,7 +354,7 @@ clang_getLBracLocPlusOne (CXCursor C)
351354
return translateSLoc (TU, S->getLBracLoc ().getLocWithOffset (1));
352355
}
353356

354-
extern "C" bool
357+
extern "C" unsigned
355358
clang_isInstrumentableCallExpr (CXCursor C)
356359
{
357360
if (!clang_isExpression (C.kind))
@@ -382,6 +385,76 @@ clang_isInstrumentableCallExpr (CXCursor C)
382385
}
383386
}
384387

388+
// Return true if the cursor is C++ Method Call with an explicit base.
389+
extern "C" unsigned
390+
clang_isPrefixedCXXMemberCallExpr (CXCursor C)
391+
{
392+
if (!clang_isExpression (C.kind))
393+
return false;
394+
395+
const Expr *E = getCursorExpr (C);
396+
if (E->getStmtClass () != Stmt::CXXMemberCallExprClass)
397+
return false;
398+
399+
const CXXMemberCallExpr *MCE = cast<CXXMemberCallExpr> (E);
400+
const MemberExpr *ME = cast<MemberExpr> (MCE->getCallee ());
401+
402+
return !ME->isImplicitAccess ();
403+
}
404+
405+
extern "C" CXSourceRange
406+
clang_getCXXMemberCallExprSCOSlocRange (CXCursor C)
407+
{
408+
if (!clang_isExpression (C.kind))
409+
return clang_getNullRange ();
410+
411+
const Expr *E = getCursorExpr (C);
412+
if (E->getStmtClass () != Stmt::CXXMemberCallExprClass)
413+
return clang_getNullRange ();
414+
415+
const CXXMemberCallExpr *MCE = cast<CXXMemberCallExpr> (E);
416+
const MemberExpr *ME = cast<MemberExpr> (MCE->getCallee ());
417+
418+
ASTContext &ctx = getContext (C);
419+
420+
const SourceLocation start_loc = ME->getOperatorLoc ();
421+
const SourceLocation end_loc = ME->getMemberLoc ().getLocWithOffset (
422+
((long) ME->getMemberDecl ()->getName ().size ()) - 1);
423+
424+
// Check for validity on both sides.
425+
// Specifically, start loc can fail if there is no operator, if the method
426+
// call is made from another method and thus not prefixed by `object.` or
427+
// `object->`
428+
if (start_loc.isInvalid () || end_loc.isInvalid ())
429+
return clang_getNullRange ();
430+
431+
// Do not use the translateSourceRange wrapper because the token
432+
// delimitation is not right for us.
433+
return translateSourceRange (
434+
ctx.getSourceManager (), ctx.getLangOpts (),
435+
CharSourceRange::getCharRange (SourceRange (start_loc, end_loc)));
436+
}
437+
438+
extern "C" CXSourceRange
439+
clang_getCXXMemberCallExprBaseSlocRange (CXCursor C)
440+
{
441+
if (!clang_isExpression (C.kind))
442+
return clang_getNullRange ();
443+
444+
const Expr *E = getCursorExpr (C);
445+
if (E->getStmtClass () != Stmt::CXXMemberCallExprClass)
446+
return clang_getNullRange ();
447+
448+
const CXXMemberCallExpr *MCE = cast<CXXMemberCallExpr> (E);
449+
const MemberExpr *ME = cast<MemberExpr> (MCE->getCallee ());
450+
451+
const SourceLocation start_loc = ME->getBase ()->getBeginLoc ();
452+
const SourceLocation end_loc = ME->getBase ()->getEndLoc ();
453+
454+
return translateSourceRange (getContext (C),
455+
SourceRange (start_loc, end_loc));
456+
}
457+
385458
extern "C" CXSourceRange
386459
clang_getFunctionSignatureSloc (CXCursor C)
387460
{

tools/gnatcov/instrument-c.adb

Lines changed: 97 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,35 @@ package body Instrument.C is
13671367
(N => SS.Statement,
13681368
Text => ")",
13691369
Rew => UIC.Rewriter);
1370+
1371+
when Instr_Prefixed_CXXMemberCallExpr =>
1372+
1373+
-- In case we are instrumenting a C++ method call `foo.bar()`
1374+
-- we wrap the base in a generic witness which is an identity
1375+
-- function and call the method on the witness like so :
1376+
--
1377+
-- `witness_generic(buf, id, foo).bar()`
1378+
1379+
declare
1380+
Base_Sloc_Range : constant Source_Range_T :=
1381+
Get_CXX_Member_Call_Expr_Base_Sloc_Range (SS.Statement);
1382+
Witness_Params : constant String :=
1383+
Statement_Buffer_Symbol (UIC.Instrumented_Unit)
1384+
& Buffers_Subscript (Buffers_Index) & ", " & Img (Bit);
1385+
begin
1386+
CX_Rewriter_Insert_Text_After
1387+
(Rew => UIC.Rewriter,
1388+
Loc => Get_Range_Start (Base_Sloc_Range),
1389+
Insert => "gnatcov_rts_witness_generic ("
1390+
& Witness_Params & ", ");
1391+
1392+
CX_Rewriter_Insert_Text_Before
1393+
(Rew => UIC.Rewriter,
1394+
Loc => Get_Range_End (Base_Sloc_Range),
1395+
Insert => ")");
1396+
end;
1397+
1398+
null;
13701399
end case;
13711400
end Insert_Statement_Witness;
13721401

@@ -1583,54 +1612,6 @@ package body Instrument.C is
15831612
end if;
15841613
end Is_Complex_Decision;
15851614

1586-
-----------------------
1587-
-- Process_Call_Expr --
1588-
-----------------------
1589-
1590-
procedure Process_Call_Expr
1591-
(UIC : in out C_Unit_Inst_Context; Cursor : Cursor_T);
1592-
1593-
procedure Process_Call_Expr
1594-
(UIC : in out C_Unit_Inst_Context; Cursor : Cursor_T)
1595-
is
1596-
function Visit_Call_Expr (C : Cursor_T) return Child_Visit_Result_T;
1597-
1598-
function Visit_Call_Expr (C : Cursor_T) return Child_Visit_Result_T is
1599-
begin
1600-
if Kind (C) = Cursor_Lambda_Expr then
1601-
return Child_Visit_Continue;
1602-
end if;
1603-
1604-
if Is_Instrumentable_Call_Expr (C) then
1605-
Sources_Trace.Trace ("Instrument Call at "
1606-
& Image (Start_Sloc (C)));
1607-
1608-
UIC.Pass.Append_SCO
1609-
(UIC => UIC,
1610-
N => C,
1611-
C1 => 'c',
1612-
C2 => 'S',
1613-
From => Start_Sloc (C),
1614-
To => End_Sloc (C),
1615-
Last => True);
1616-
1617-
UIC.Pass.Instrument_Statement
1618-
(UIC => UIC,
1619-
LL_SCO => SCOs.SCO_Table.Last,
1620-
Insertion_N => C,
1621-
Instr_Scheme => Instr_Expr,
1622-
Kind => Call);
1623-
end if;
1624-
1625-
return Child_Visit_Recurse;
1626-
end Visit_Call_Expr;
1627-
1628-
begin
1629-
if Enabled (Coverage_Options.Fun_Call) then
1630-
Visit (Cursor, Visit_Call_Expr'Access);
1631-
end if;
1632-
end Process_Call_Expr;
1633-
16341615
------------------------
16351616
-- Process_Expression --
16361617
------------------------
@@ -1650,6 +1631,8 @@ package body Instrument.C is
16501631
N : Cursor_T;
16511632
T : Character);
16521633

1634+
function Process_Call_Expr (C : Cursor_T) return Child_Visit_Result_T;
1635+
16531636
-------------------------
16541637
-- Process_Lambda_Expr --
16551638
-------------------------
@@ -2030,14 +2013,79 @@ package body Instrument.C is
20302013
Hash_Entries.Free;
20312014
end Process_Decisions;
20322015

2016+
-------------------------
2017+
-- Process_Call_Expr --
2018+
-------------------------
2019+
2020+
function Process_Call_Expr (C : Cursor_T) return Child_Visit_Result_T is
2021+
begin
2022+
2023+
-- Lambda functions are handled in Process_Lambda_Expr
2024+
2025+
if Kind (C) = Cursor_Lambda_Expr then
2026+
return Child_Visit_Continue;
2027+
end if;
2028+
2029+
if Is_Instrumentable_Call_Expr (C) then
2030+
Sources_Trace.Trace ("Instrument Call at "
2031+
& Image (Start_Sloc (C)));
2032+
2033+
declare
2034+
From : Source_Location := Start_Sloc (C);
2035+
To : Source_Location := End_Sloc (C);
2036+
Instr_Scheme : Instr_Scheme_Type := Instr_Expr;
2037+
begin
2038+
if Is_Prefixed_CXX_Member_Call_Expr (C) then
2039+
2040+
-- If we are instrumenting a method call like `foo.bar()`,
2041+
-- adjust the SCO to `.bar`, and set a specific
2042+
-- instrumentation scheme.
2043+
--
2044+
-- This does not apply to un-prefixed method calls (in other
2045+
-- method calls), which are instrumented as regular
2046+
-- functions.
2047+
2048+
declare
2049+
CX_Source_Range : constant Source_Range_T :=
2050+
Get_CXX_Member_Call_Expr_SCO_Sloc_Range (C);
2051+
begin
2052+
From := Sloc (Get_Range_Start (CX_Source_Range));
2053+
To := Sloc (Get_Range_End (CX_Source_Range));
2054+
Instr_Scheme := Instr_Prefixed_CXXMemberCallExpr;
2055+
end;
2056+
end if;
2057+
2058+
UIC.Pass.Append_SCO
2059+
(UIC => UIC,
2060+
N => C,
2061+
C1 => 'c',
2062+
C2 => 'E',
2063+
From => From,
2064+
To => To,
2065+
Last => True);
2066+
2067+
UIC.Pass.Instrument_Statement
2068+
(UIC => UIC,
2069+
LL_SCO => SCOs.SCO_Table.Last,
2070+
Insertion_N => C,
2071+
Instr_Scheme => Instr_Scheme,
2072+
Kind => Call);
2073+
end;
2074+
end if;
2075+
2076+
return Child_Visit_Recurse;
2077+
end Process_Call_Expr;
2078+
20332079
begin
20342080
if UIC.Disable_Coverage then
20352081
return;
20362082
end if;
20372083

20382084
Process_Decisions (UIC, N, T);
20392085

2040-
Process_Call_Expr (UIC, N);
2086+
if Enabled (Coverage_Options.Fun_Call) then
2087+
Visit (N, Process_Call_Expr'Access);
2088+
end if;
20412089

20422090
Visit (N, Process_Lambda_Expr'Access);
20432091
end Process_Expression;

tools/gnatcov/instrument-c.ads

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,11 @@ package Instrument.C is
160160
-- Create a C++ instrumenter. See the definition of the
161161
-- Language_Instrumenter type for the arguments semantic.
162162

163-
type Instr_Scheme_Type is (Instr_Stmt, Instr_Expr, Instr_In_Compound);
163+
type Instr_Scheme_Type is
164+
(Instr_Stmt,
165+
Instr_Expr,
166+
Instr_In_Compound,
167+
Instr_Prefixed_CXXMemberCallExpr);
164168
-- Depending on the statement construct, we can instrument it either with
165169
-- another statement right before (Instr_Stmt), which is the case for most
166170
-- statements:
@@ -193,6 +197,12 @@ package Instrument.C is
193197
-- Function coverage, and particularly useful for functions with empty
194198
-- bodies in which it is not possible to refer to the body's first
195199
-- statement to insert a witness statement before it.
200+
--
201+
-- The variant Instr_Prefixed_CXXMemberCallExpr is expected to be used on
202+
-- C++ prefixed method calls like `foo.bar()` or `foo->bar()`.
203+
-- It will be instrumented using a generic templated witness to conserve
204+
-- the execution order of witnesses in method call chains.
205+
-- Note that NON-prefixed method calls are handled like simple functions.
196206

197207
type C_Source_Statement is record
198208
LL_SCO : Nat;

tools/gnatcov/rts/gnatcov_rts_c-witness.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ gnatcov_rts_witness (unsigned char *buffer, unsigned bit_id)
3535
return 1;
3636
}
3737

38+
#ifdef __cplusplus
39+
40+
template <class T>
41+
T
42+
gnatcov_rts_witness_generic (unsigned char *buffer, unsigned int bit_id,
43+
T value)
44+
{
45+
gnatcov_rts_witness (buffer, bit_id);
46+
return value;
47+
}
48+
49+
#endif /* __cplusplus */
50+
3851
/* Decisions */
3952

4053
/* If VALUE is false, set the boolean corresponding to FALSE_BIT to true in

0 commit comments

Comments
 (0)