Skip to content

Commit 090db2a

Browse files
authored
feat: symbol filtering
1 parent 2e554c8 commit 090db2a

25 files changed

+1282
-26
lines changed

.github/workflows/ci.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,15 +350,25 @@ jobs:
350350
cd ..
351351
cmake -D BOOST_URL_BUILD_TESTS=OFF -D BOOST_URL_BUILD_EXAMPLES=OFF -D CMAKE_EXPORT_COMPILE_COMMANDS=ON -D CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES="$default_includes" -D CMAKE_CXX_COMPILER=${{ steps.setup-cpp.outputs.cxx }} -D CMAKE_C_COMPILER=${{ steps.setup-cpp.outputs.cc }} ..
352352
353-
- name: Generate reference
353+
- name: Generate demos
354354
run: |
355355
echo "verbose: true" > $(pwd)/boost/libs/url/mrdox-single.yml
356356
echo "source-root: ." >> $(pwd)/boost/libs/url/mrdox-single.yml
357357
echo "multipage: false" >> $(pwd)/boost/libs/url/mrdox-single.yml
358+
echo "filters:" >> $(pwd)/boost/libs/url/mrdox-single.yml
359+
echo " symbols:" >> $(pwd)/boost/libs/url/mrdox-single.yml
360+
echo " exclude:" >> $(pwd)/boost/libs/url/mrdox-single.yml
361+
echo " - 'boost::urls::detail'" >> $(pwd)/boost/libs/url/mrdox-single.yml
362+
echo " - 'boost::urls::*::detail'" >> $(pwd)/boost/libs/url/mrdox-single.yml
358363
359364
echo "verbose: true" > $(pwd)/boost/libs/url/mrdox-multi.yml
360365
echo "source-root: ." >> $(pwd)/boost/libs/url/mrdox-multi.yml
361366
echo "multipage: true" >> $(pwd)/boost/libs/url/mrdox-multi.yml
367+
echo "filters:" >> $(pwd)/boost/libs/url/mrdox-multi.yml
368+
echo " symbols:" >> $(pwd)/boost/libs/url/mrdox-multi.yml
369+
echo " exclude:" >> $(pwd)/boost/libs/url/mrdox-multi.yml
370+
echo " - 'boost::urls::detail'" >> $(pwd)/boost/libs/url/mrdox-multi.yml
371+
echo " - 'boost::urls::*::detail'" >> $(pwd)/boost/libs/url/mrdox-multi.yml
362372
363373
set -x
364374
for variant in single multi; do
@@ -368,6 +378,15 @@ jobs:
368378
done
369379
asciidoctor -R "$(pwd)/demos/boost-url/$variant/adoc" -D "$(pwd)/demos/boost-url/$variant/adoc-asciidoc" "$(pwd)/demos/boost-url/$variant/adoc/**/*.adoc"
370380
done
381+
tar -cjf $(pwd)/demos.tar.gz -C $(pwd)/demos --strip-components 1 .
382+
echo "demos_path=$(pwd)/demos.tar.gz" >> $GITHUB_ENV
383+
384+
- name: Upload demo artifacts
385+
uses: actions/upload-artifact@v3
386+
with:
387+
name: demos
388+
path: ${{ env.demos_path }}
389+
retention-days: 1
371390

372391
- name: Update website demos
373392
if: ${{ github.event_name == 'push' && (contains(fromJSON('["master", "develop"]'), github.ref_name) || startsWith(github.ref, 'refs/tags/')) }}

docs/modules/ROOT/pages/config-file.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ input:
1111
include: # <.>
1212
multipage: # <.>
1313
source-root: # <.>
14+
filters: # <.>
1415
----
1516
<.> Optional `concurrency` key
1617
<.> Optional `defines` key
@@ -20,6 +21,7 @@ source-root: # <.>
2021
<.> Optional `include` key
2122
<.> Optional `multipage` key
2223
<.> Optional `source-root` key
24+
<.> Optional `filters` key
2325

2426
== Available configuration keys
2527

@@ -61,4 +63,9 @@ the hardware-suggested concurrency.
6163
|The absolute or relative path to the directory containing the
6264
input file hierarchy.
6365
|No
66+
67+
|filters
68+
|Patterns specifying symbols which should be included/excluded.
69+
See xref:filters[specifying filters] for details.
70+
|No
6471
|===

docs/modules/ROOT/pages/filters.adoc

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
= Specifying Filters
2+
3+
[,yaml]
4+
----
5+
filters:
6+
symbols: # <.>
7+
exclude: # <.>
8+
include: # <.>
9+
----
10+
<.> Optional `symbols` key
11+
<.> Optional `exclude` key
12+
<.> Optional `include` key
13+
14+
== Filtering Symbols
15+
16+
Symbol filter patterns specified using (optionally) qualified names, and may contain wildcards:
17+
18+
[,yaml]
19+
----
20+
filters:
21+
symbols:
22+
exclude:
23+
- 'A::B'
24+
- 'A::f*'
25+
----
26+
27+
If a symbol matches a pattern in the exclude list, that symbol and its members will not be extracted:
28+
29+
[,yaml]
30+
----
31+
filters:
32+
symbols:
33+
exclude:
34+
- 'A'
35+
----
36+
37+
[,cpp]
38+
----
39+
void f0(); // ok, does not match any excluded pattern
40+
41+
namespace A // matches the pattern 'A', will not be extracted
42+
{
43+
void f1(); // enclosing namespace matches an excluded pattern, will not be extracted
44+
}
45+
----
46+
47+
In all contexts, a symbol which matches an included pattern and an excluded pattern will be extracted.
48+
NOTE: Included patterns which do not match a subset of an excluded pattern
49+
50+
NOTE: Specifying include patterns is only useful when the pattern would match a subset of symbols matched by an exclude pattern. An include pattern without a subsuming exclude pattern is meaningless and will be ignored.
51+
52+
This permits fine-grained control of extraction for individual members of a class or namespace:
53+
54+
[,yaml]
55+
----
56+
filters:
57+
symbols:
58+
exclude:
59+
- 'A'
60+
include:
61+
- 'A::g*'
62+
----
63+
64+
[,cpp]
65+
----
66+
namespace A
67+
{
68+
void f0(); // enclosing namespace matches an excluded pattern, will not be extracted
69+
void g0(); // ok, matches the pattern 'A::g*' which overrides the exclude list
70+
}
71+
----
72+
73+
In order for a filter pattern to match a symbol, it must consist of simple identifiers
74+
which matches the name as written in its declaration; namespace aliases, typedef-names,
75+
and `decltype` specifiers naming the symbol cannot be used.

include/mrdox/mrdox.natvis

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,35 @@
140140
<DisplayString>duk_value@{idx_}</DisplayString>
141141
</Type>
142142

143+
<!-- ASTVisitor -->
144+
145+
<Type Name="clang::mrdox::FilterPattern">
146+
<DisplayString>{pattern_}</DisplayString>
147+
</Type>
148+
<Type Name="clang::mrdox::FilterNode">
149+
<Intrinsic Name="n_children" Expression="(size_t)(Children._Mypair._Myval2._Mylast - Children._Mypair._Myval2._Myfirst)" />
150+
<!--
151+
<DisplayString Condition="n_children() == 1">Excluded = {int(Excluded)}, {n_children()} child, Pattern = {Pattern}</DisplayString>
152+
<DisplayString Condition="n_children() != 0">Excluded = {int(Excluded)}, {n_children()} children, Pattern = {Pattern}</DisplayString>
153+
<DisplayString>Excluded = {int(Excluded)}, Pattern = {Pattern}</DisplayString>
154+
-->
155+
<DisplayString Condition="n_children() == 0">Excluded = {int(Excluded)}, Pattern = {Pattern}</DisplayString>
156+
<DisplayString Condition="n_children() == 1">Excluded = {int(Excluded)}, Pattern = {Pattern}, {n_children()} child</DisplayString>
157+
<DisplayString Condition="n_children() != 0">Excluded = {int(Excluded)}, Pattern = {Pattern}, {n_children()} children</DisplayString>
158+
<Expand>
159+
<Item Name="Pattern">Pattern</Item>
160+
<Item Name="Excluded">Excluded</Item>
161+
<Item Name="Explicit">Explicit</Item>
162+
<Synthetic Name="Children" Condition="n_children() != 0">
163+
<DisplayString>{Children._Mypair._Myval2._Myfirst,[n_children()]na}</DisplayString>
164+
<Expand>
165+
<ArrayItems>
166+
<Size>n_children()</Size>
167+
<ValuePointer>Children._Mypair._Myval2._Myfirst</ValuePointer>
168+
</ArrayItems>
169+
</Expand>
170+
</Synthetic>
171+
</Expand>
172+
</Type>
173+
143174
</AutoVisualizer>

src/lib/AST/ASTVisitor.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "lib/Support/Path.hpp"
1818
#include "lib/Support/Debug.hpp"
1919
#include "lib/Lib/Diagnostics.hpp"
20+
#include "lib/Lib/Filters.hpp"
2021
#include <mrdox/Metadata.hpp>
2122
#include <clang/AST/AST.h>
2223
#include <clang/AST/Attr.h>
@@ -94,6 +95,59 @@ struct InfoPtrEqual
9495
}
9596
};
9697

98+
//------------------------------------------------
99+
100+
struct SymbolFilter
101+
{
102+
const FilterNode& root;
103+
const FilterNode* current = nullptr;
104+
const FilterNode* last_explicit = nullptr;
105+
bool detached = false;
106+
107+
SymbolFilter(const FilterNode& root_node)
108+
: root(root_node)
109+
{
110+
setCurrent(&root, false);
111+
}
112+
113+
SymbolFilter(const SymbolFilter&) = delete;
114+
SymbolFilter(SymbolFilter&&) = delete;
115+
116+
void
117+
setCurrent(
118+
const FilterNode* node,
119+
bool node_detached)
120+
{
121+
current = node;
122+
detached = node_detached;
123+
if(node && node->Explicit)
124+
last_explicit = node;
125+
}
126+
127+
class FilterScope
128+
{
129+
SymbolFilter& filter_;
130+
const FilterNode* current_prev_;
131+
const FilterNode* last_explicit_prev_;
132+
bool detached_prev_;
133+
134+
public:
135+
FilterScope(SymbolFilter& filter)
136+
: filter_(filter)
137+
, current_prev_(filter.current)
138+
, last_explicit_prev_(filter.last_explicit)
139+
, detached_prev_(filter.detached)
140+
{
141+
}
142+
143+
~FilterScope()
144+
{
145+
filter_.current = current_prev_;
146+
filter_.last_explicit = last_explicit_prev_;
147+
filter_.detached = detached_prev_;
148+
}
149+
};
150+
};
97151

98152
//------------------------------------------------
99153
//
@@ -147,6 +201,8 @@ class ASTVisitor
147201
// KRYSTIAN FIXME: this is terrible
148202
bool forceExtract_ = false;
149203

204+
SymbolFilter symbolFilter_;
205+
150206
ASTVisitor(
151207
const ConfigImpl& config,
152208
Diagnostics& diags,
@@ -159,6 +215,7 @@ class ASTVisitor
159215
, context_(context)
160216
, source_(context.getSourceManager())
161217
, sema_(sema)
218+
, symbolFilter_(config->symbolFilter)
162219
{
163220
// install handlers for our custom commands
164221
initCustomCommentCommands(context_);
@@ -1360,13 +1417,98 @@ class ASTVisitor
13601417

13611418
//------------------------------------------------
13621419

1420+
bool
1421+
checkSymbolFilter(const NamedDecl* ND)
1422+
{
1423+
if(forceExtract_ || symbolFilter_.detached)
1424+
return true;
1425+
1426+
std::string name = extractName(ND);
1427+
const FilterNode* parent = symbolFilter_.current;
1428+
if(const FilterNode* child = parent->findChild(name))
1429+
{
1430+
// if there is a matching node, skip extraction if it's
1431+
// explicitly excluded AND has no children. the presence
1432+
// of child nodes indicates that some child exists that
1433+
// is explicitly whitelisted
1434+
if(child->Explicit && child->Excluded &&
1435+
child->isTerminal())
1436+
return false;
1437+
symbolFilter_.setCurrent(child, false);
1438+
}
1439+
else
1440+
{
1441+
// if there was no matching node, check the most
1442+
// recently entered explicitly specified parent node.
1443+
// if it's blacklisted, then the "filtering default"
1444+
// is to exclude symbols unless a child is explicitly
1445+
// whitelisted
1446+
if(symbolFilter_.last_explicit &&
1447+
symbolFilter_.last_explicit->Excluded)
1448+
return false;
1449+
1450+
if(const auto* DC = dyn_cast<DeclContext>(ND);
1451+
! DC || ! DC->isInlineNamespace())
1452+
{
1453+
// if this namespace does not match a child
1454+
// of the current filter node, set the detached flag
1455+
// so we don't update the namespace filter state
1456+
// while traversing the children of this namespace
1457+
symbolFilter_.detached = true;
1458+
}
1459+
}
1460+
return true;
1461+
}
1462+
13631463
// This also sets IsFileInRootDir
13641464
bool
13651465
shouldExtract(
13661466
const Decl* D)
13671467
{
13681468
namespace path = llvm::sys::path;
13691469

1470+
if(const auto* ND = dyn_cast<NamedDecl>(D))
1471+
{
1472+
// out-of-line declarations require us to rebuild
1473+
// the symbol filtering state
1474+
if(ND->isOutOfLine())
1475+
{
1476+
symbolFilter_.setCurrent(
1477+
&symbolFilter_.root, false);
1478+
1479+
// collect all parent classes/enums/namespaces
1480+
llvm::SmallVector<const NamedDecl*, 8> parents;
1481+
const DeclContext* parent = ND->getDeclContext();
1482+
do
1483+
{
1484+
switch(parent->getDeclKind())
1485+
{
1486+
case Decl::Namespace:
1487+
case Decl::Enum:
1488+
case Decl::CXXRecord:
1489+
case Decl::ClassTemplateSpecialization:
1490+
case Decl::ClassTemplatePartialSpecialization:
1491+
parents.push_back(cast<NamedDecl>(parent));
1492+
break;
1493+
default:
1494+
break;
1495+
}
1496+
}
1497+
while((parent = parent->getParent()));
1498+
1499+
// check whether each parent passes the symbol filters
1500+
// as-if the declaration was inline
1501+
for(const auto* PND : std::views::reverse(parents))
1502+
{
1503+
if(! checkSymbolFilter(PND))
1504+
return false;
1505+
}
1506+
}
1507+
1508+
if(! checkSymbolFilter(ND))
1509+
return false;
1510+
}
1511+
13701512
// skip system header
13711513
if(! forceExtract_ && source_.isInSystemHeader(D->getLocation()))
13721514
return false;
@@ -2498,6 +2640,8 @@ traverseDecl(
24982640
if(D->isInvalidDecl() || D->isImplicit())
24992641
return true;
25002642

2643+
SymbolFilter::FilterScope scope(symbolFilter_);
2644+
25012645
AccessSpecifier access =
25022646
D->getAccessUnsafe();
25032647

0 commit comments

Comments
 (0)