Skip to content

Commit cfa057b

Browse files
committed
This commit implements a CC1 flag -dump-deserialized-declaration-ranges. The flag allows to specify a file path to dump ranges of deserialized declarations in ASTReader. Example usage:
``` clang -Xclang=-dump-deserialized-declaration-ranges=/tmp/decls -c file.cc -o file.o ``` Example output: ``` // /tmp/decls { "required_ranges": [ { "file": "foo.h", "range": [ { "from": { "line": 26, "column": 1 }, "to": { "line": 27, "column": 77 } } ] }, { "file": "bar.h", "range": [ { "from": { "line": 30, "column": 1 }, "to": { "line": 35, "column": 1 } }, { "from": { "line": 92, "column": 1 }, "to": { "line": 95, "column": 1 } } ] } ] } ``` Specifying the flag creates an instance of `DeserializedDeclsLineRangePrinter`, which dumps ranges of deserialized declarations to aid debugging and bug minimization. Required ranges are computed from source ranges of Decls. `TranslationUnitDecl`, `LinkageSpecDecl` and `NamespaceDecl` are ignored for the sake of this PR. Technical details: * `DeserializedDeclsLineRangePrinter` implements `ASTConsumer` and `ASTDeserializationListener`, so that an object of `DeserializedDeclsLineRangePrinter` registers as its own listener. * `ASTDeserializationListener` interface provides the `DeclRead` callback that we use to collect the deserialized Decls. Printing or otherwise processing them as this point is dangerous, since that could trigger additional deserialization and crash compilation. * The collected Decls are processed in `HandleTranslationUnit` method of `ASTConsumer`. This is a safe point, since we know that by this point all the Decls needed by the compiler frontend have been deserialized. * In case our processing causes further deserialization, `DeclRead` from the listener might be called again. However, at that point we don't accept any more Decls for processing.
1 parent 809f857 commit cfa057b

File tree

4 files changed

+310
-5
lines changed

4 files changed

+310
-5
lines changed

clang/include/clang/Driver/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7968,6 +7968,10 @@ def print_dependency_directives_minimized_source : Flag<["-"],
79687968
"print-dependency-directives-minimized-source">,
79697969
HelpText<"Print the output of the dependency directives source minimizer">;
79707970
}
7971+
def dump_deserialized_declaration_ranges : Joined<["-"],
7972+
"dump-deserialized-declaration-ranges=">,
7973+
HelpText<"Dump ranges of deserialized declarations to aid debugging and minimization">,
7974+
MarshallingInfoString<FrontendOpts<"DumpDeserializedDeclarationRangesPath">>;
79717975

79727976
defm emit_llvm_uselists : BoolOption<"", "emit-llvm-uselists",
79737977
CodeGenOpts<"EmitLLVMUseLists">, DefaultFalse,

clang/include/clang/Frontend/FrontendOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ class FrontendOptions {
530530
/// Output Path for module output file.
531531
std::string ModuleOutputPath;
532532

533+
/// Output path to dump ranges of deserialized declarations.
534+
std::string DumpDeserializedDeclarationRangesPath;
535+
533536
public:
534537
FrontendOptions()
535538
: DisableFree(false), RelocatablePCH(false), ShowHelp(false),

clang/lib/Frontend/FrontendAction.cpp

Lines changed: 185 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "clang/Basic/FileEntry.h"
1616
#include "clang/Basic/LangStandard.h"
1717
#include "clang/Basic/Sarif.h"
18+
#include "clang/Basic/SourceLocation.h"
19+
#include "clang/Basic/SourceManager.h"
1820
#include "clang/Basic/Stack.h"
1921
#include "clang/Frontend/ASTUnit.h"
2022
#include "clang/Frontend/CompilerInstance.h"
@@ -35,6 +37,7 @@
3537
#include "clang/Serialization/ASTReader.h"
3638
#include "clang/Serialization/GlobalModuleIndex.h"
3739
#include "llvm/ADT/ScopeExit.h"
40+
#include "llvm/ADT/StringRef.h"
3841
#include "llvm/Support/BuryPointer.h"
3942
#include "llvm/Support/ErrorHandling.h"
4043
#include "llvm/Support/FileSystem.h"
@@ -49,6 +52,166 @@ LLVM_INSTANTIATE_REGISTRY(FrontendPluginRegistry)
4952

5053
namespace {
5154

55+
/// DeserializedDeclsLineRangePrinter dumps ranges of deserialized declarations
56+
/// to aid debugging and bug minimization. It implements ASTConsumer and
57+
/// ASTDeserializationListener, so that an object of
58+
/// DeserializedDeclsLineRangePrinter registers as its own listener. The
59+
/// ASTDeserializationListener interface provides the DeclRead callback that we
60+
/// use to collect the deserialized Decls. Note that printing or otherwise
61+
/// processing them as this point is dangerous, since that could trigger
62+
/// additional deserialization and crash compilation. Therefore, we process the
63+
/// collected Decls in HandleTranslationUnit method of ASTConsumer. This is a
64+
/// safe point, since we know that by this point all the Decls needed by the
65+
/// compiler frontend have been deserialized. In case our processing causes
66+
/// further deserialization, DeclRead from the listener might be called again.
67+
/// However, at that point we don't accept any more Decls for processing.
68+
class DeserializedDeclsLineRangePrinter : public ASTConsumer,
69+
ASTDeserializationListener {
70+
public:
71+
explicit DeserializedDeclsLineRangePrinter(
72+
SourceManager &SM, std::unique_ptr<llvm::raw_fd_ostream> OS)
73+
: ASTDeserializationListener(), SM(SM), OS(std::move(OS)) {}
74+
75+
ASTDeserializationListener *GetASTDeserializationListener() override {
76+
return this;
77+
}
78+
79+
void DeclRead(GlobalDeclID ID, const Decl *D) override {
80+
if (!IsCollectingDecls)
81+
return;
82+
if (!D || isa<TranslationUnitDecl>(D) || isa<LinkageSpecDecl>(D) ||
83+
isa<NamespaceDecl>(D)) {
84+
// These decls cover a lot of nested declarations that might not be used,
85+
// reducing the granularity and making the output less useful.
86+
return;
87+
}
88+
if (auto *DC = D->getDeclContext(); !DC || !DC->isFileContext()) {
89+
// We choose to work at namespace level to reduce complexity and the
90+
// number of cases we care about.
91+
return;
92+
}
93+
PendingDecls.push_back(D);
94+
}
95+
96+
struct Position {
97+
unsigned Line;
98+
unsigned Column;
99+
100+
bool operator<(const Position &other) const {
101+
if (Line < other.Line)
102+
return true;
103+
if (Line > other.Line)
104+
return false;
105+
return Column < other.Column;
106+
}
107+
108+
static Position GetSpelling(const SourceManager &SM,
109+
const SourceLocation &SL) {
110+
return {SM.getSpellingLineNumber(SL), SM.getSpellingColumnNumber(SL)};
111+
}
112+
};
113+
114+
struct RequiredRanges {
115+
StringRef Filename;
116+
std::vector<std::pair<Position, Position>> FromTo;
117+
};
118+
void HandleTranslationUnit(ASTContext &Context) override {
119+
assert(IsCollectingDecls && "HandleTranslationUnit called twice?");
120+
IsCollectingDecls = false;
121+
122+
// Merge ranges in each of the files.
123+
struct FileData {
124+
std::vector<std::pair<Position, Position>> FromTo;
125+
OptionalFileEntryRef Ref;
126+
};
127+
llvm::DenseMap<const FileEntry *, FileData> FileToLines;
128+
for (const Decl *D : PendingDecls) {
129+
CharSourceRange R = SM.getExpansionRange(D->getSourceRange());
130+
if (!R.isValid())
131+
continue;
132+
133+
auto *F = SM.getFileEntryForID(SM.getFileID(R.getBegin()));
134+
if (F != SM.getFileEntryForID(SM.getFileID(R.getEnd()))) {
135+
// Such cases are rare and difficult to handle.
136+
continue;
137+
}
138+
139+
auto &Data = FileToLines[F];
140+
if (!Data.Ref)
141+
Data.Ref = SM.getFileEntryRefForID(SM.getFileID(R.getBegin()));
142+
Data.FromTo.push_back({Position::GetSpelling(SM, R.getBegin()),
143+
Position::GetSpelling(SM, R.getEnd())});
144+
}
145+
146+
// To simplify output, merge consecutive and intersecting ranges.
147+
std::vector<RequiredRanges> Result;
148+
for (auto &[F, Data] : FileToLines) {
149+
auto &FromTo = Data.FromTo;
150+
assert(!FromTo.empty());
151+
152+
if (!Data.Ref)
153+
continue;
154+
155+
llvm::sort(FromTo);
156+
157+
std::vector<std::pair<Position, Position>> MergedRanges;
158+
MergedRanges.push_back(FromTo.front());
159+
for (auto It = FromTo.begin() + 1; It < FromTo.end(); ++It) {
160+
if (MergedRanges.back().second < It->first) {
161+
MergedRanges.push_back(*It);
162+
continue;
163+
}
164+
if (MergedRanges.back().second < It->second)
165+
MergedRanges.back().second = It->second;
166+
}
167+
Result.push_back({Data.Ref->getName(), MergedRanges});
168+
}
169+
printJson(Result);
170+
}
171+
172+
private:
173+
std::vector<const Decl *> PendingDecls;
174+
bool IsCollectingDecls = true;
175+
const SourceManager &SM;
176+
std::unique_ptr<llvm::raw_ostream> OS;
177+
178+
void printJson(llvm::ArrayRef<RequiredRanges> Result) {
179+
*OS << "{\n";
180+
*OS << R"( "required_ranges": [)" << "\n";
181+
for (size_t I = 0; I < Result.size(); ++I) {
182+
auto &F = Result[I].Filename;
183+
auto &MergedRanges = Result[I].FromTo;
184+
*OS << R"( {)" << "\n";
185+
*OS << R"( "file": ")" << F << "\"," << "\n";
186+
*OS << R"( "range": [)" << "\n";
187+
for (size_t J = 0; J < MergedRanges.size(); ++J) {
188+
auto &From = MergedRanges[J].first;
189+
auto &To = MergedRanges[J].second;
190+
*OS << R"( {)" << "\n";
191+
*OS << R"( "from": {)" << "\n";
192+
*OS << R"( "line": )" << From.Line << ",\n";
193+
*OS << R"( "column": )" << From.Column << "\n"
194+
<< R"( },)" << "\n";
195+
*OS << R"( "to": {)" << "\n";
196+
*OS << R"( "line": )" << To.Line << ",\n";
197+
*OS << R"( "column": )" << To.Column << "\n"
198+
<< R"( })" << "\n";
199+
*OS << R"( })";
200+
if (J < MergedRanges.size() - 1) {
201+
*OS << ",";
202+
}
203+
*OS << "\n";
204+
}
205+
*OS << " ]" << "\n" << " }";
206+
if (I < Result.size() - 1)
207+
*OS << ",";
208+
*OS << "\n";
209+
}
210+
*OS << " ]\n";
211+
*OS << "}\n";
212+
}
213+
};
214+
52215
/// Dumps deserialized declarations.
53216
class DeserializedDeclsDumper : public DelegatingDeserializationListener {
54217
public:
@@ -121,6 +284,25 @@ FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
121284
if (!Consumer)
122285
return nullptr;
123286

287+
std::vector<std::unique_ptr<ASTConsumer>> Consumers;
288+
llvm::StringRef DumpDeserializedDeclarationRangesPath =
289+
CI.getFrontendOpts().DumpDeserializedDeclarationRangesPath;
290+
if (!DumpDeserializedDeclarationRangesPath.empty()) {
291+
std::error_code ErrorCode;
292+
auto FileStream = std::make_unique<llvm::raw_fd_ostream>(
293+
DumpDeserializedDeclarationRangesPath, ErrorCode,
294+
llvm::sys::fs::OF_None);
295+
if (!ErrorCode) {
296+
Consumers.push_back(std::make_unique<DeserializedDeclsLineRangePrinter>(
297+
CI.getSourceManager(), std::move(FileStream)));
298+
} else {
299+
llvm::errs() << "Failed to create output file for "
300+
"-dump-deserialized-declaration-ranges flag, file path: "
301+
<< DumpDeserializedDeclarationRangesPath
302+
<< ", error: " << ErrorCode.message() << "\n";
303+
}
304+
}
305+
124306
// Validate -add-plugin args.
125307
bool FoundAllPlugins = true;
126308
for (const std::string &Arg : CI.getFrontendOpts().AddPluginActions) {
@@ -138,17 +320,12 @@ FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
138320
if (!FoundAllPlugins)
139321
return nullptr;
140322

141-
// If there are no registered plugins we don't need to wrap the consumer
142-
if (FrontendPluginRegistry::begin() == FrontendPluginRegistry::end())
143-
return Consumer;
144-
145323
// If this is a code completion run, avoid invoking the plugin consumers
146324
if (CI.hasCodeCompletionConsumer())
147325
return Consumer;
148326

149327
// Collect the list of plugins that go before the main action (in Consumers)
150328
// or after it (in AfterConsumers)
151-
std::vector<std::unique_ptr<ASTConsumer>> Consumers;
152329
std::vector<std::unique_ptr<ASTConsumer>> AfterConsumers;
153330
for (const FrontendPluginRegistry::entry &Plugin :
154331
FrontendPluginRegistry::entries()) {
@@ -191,6 +368,9 @@ FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
191368
Consumers.push_back(std::move(C));
192369
}
193370

371+
assert(Consumers.size() >= 1 && "should have added the main consumer");
372+
if (Consumers.size() == 1)
373+
return std::move(Consumers.front());
194374
return std::make_unique<MultiplexConsumer>(std::move(Consumers));
195375
}
196376

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: split-file %s %t
4+
// RUN: %clang_cc1 -xc++ -fmodules -fmodule-name=foo -fmodule-map-file=%t/foo.cppmap -emit-module %t/foo.cppmap -o %t/foo.pcm
5+
// RUN: %clang_cc1 -xc++ -fmodules -dump-deserialized-declaration-ranges=%t/decls -fmodule-file=%t/foo.pcm %t/foo.cpp -o %t/foo.o
6+
// RUN: cat %t/decls | FileCheck -check-prefix=RANGE %s
7+
// RANGE:{
8+
// RANGE-NEXT: "required_ranges": [
9+
// RANGE-NEXT: {
10+
// RANGE-NEXT: "file": "{{.+}}/foo.h",
11+
// RANGE-NEXT: "range": [
12+
// RANGE-NEXT: {
13+
// RANGE-NEXT: "from": {
14+
// RANGE-NEXT: "line": 1,
15+
// RANGE-NEXT: "column": 1
16+
// RANGE-NEXT: },
17+
// RANGE-NEXT: "to": {
18+
// RANGE-NEXT: "line": 9,
19+
// RANGE-NEXT: "column": 1
20+
// RANGE-NEXT: }
21+
// RANGE-NEXT: },
22+
// RANGE-NEXT: {
23+
// RANGE-NEXT: "from": {
24+
// RANGE-NEXT: "line": 11,
25+
// RANGE-NEXT: "column": 1
26+
// RANGE-NEXT: },
27+
// RANGE-NEXT: "to": {
28+
// RANGE-NEXT: "line": 11,
29+
// RANGE-NEXT: "column": 12
30+
// RANGE-NEXT: }
31+
// RANGE-NEXT: },
32+
// RANGE-NEXT: {
33+
// RANGE-NEXT: "from": {
34+
// RANGE-NEXT: "line": 13,
35+
// RANGE-NEXT: "column": 1
36+
// RANGE-NEXT: },
37+
// RANGE-NEXT: "to": {
38+
// RANGE-NEXT: "line": 15,
39+
// RANGE-NEXT: "column": 1
40+
// RANGE-NEXT: }
41+
// RANGE-NEXT: }
42+
// RANGE-NEXT: ]
43+
// RANGE-NEXT: }
44+
// RANGE-NEXT: ]
45+
// RANGE-NEXT:}
46+
// RUN: echo -e '{\n\
47+
// RUN: "required_ranges": [\n\
48+
// RUN: {\n\
49+
// RUN: "file": "%t/foo.h",\n\
50+
// RUN: "range": [\n\
51+
// RUN: {\n\
52+
// RUN: "from": {\n\
53+
// RUN: "line": 1,\n\
54+
// RUN: "column": 1\n\
55+
// RUN: },\n\
56+
// RUN: "to": {\n\
57+
// RUN: "line": 9,\n\
58+
// RUN: "column": 1\n\
59+
// RUN: }\n\
60+
// RUN: },\n\
61+
// RUN: {\n\
62+
// RUN: "from": {\n\
63+
// RUN: "line": 11,\n\
64+
// RUN: "column": 1\n\
65+
// RUN: },\n\
66+
// RUN: "to": {\n\
67+
// RUN: "line": 11,\n\
68+
// RUN: "column": 12\n\
69+
// RUN: }\n\
70+
// RUN: },\n\
71+
// RUN: {\n\
72+
// RUN: "from": {\n\
73+
// RUN: "line": 13,\n\
74+
// RUN: "column": 1\n\
75+
// RUN: },\n\
76+
// RUN: "to": {\n\
77+
// RUN: "line": 15,\n\
78+
// RUN: "column": 1\n\
79+
// RUN: }\n\
80+
// RUN: }\n\
81+
// RUN: ]\n\
82+
// RUN: }\n\
83+
// RUN: ]\n\
84+
// RUN:}' > %t/expected_decls
85+
// RUN: diff %t/decls %t/expected_decls
86+
87+
//--- foo.cppmap
88+
module foo {
89+
header "foo.h"
90+
export *
91+
}
92+
93+
//--- foo.h
94+
class MyData {
95+
public:
96+
MyData(int val): value_(val) {}
97+
int getValue() const {
98+
return 5;
99+
}
100+
private:
101+
int value_;
102+
};
103+
104+
extern int global_value;
105+
106+
int multiply(int a, int b) {
107+
return a * b;
108+
}
109+
110+
//--- foo.cpp
111+
#include "foo.h"
112+
int global_value = 5;
113+
int main() {
114+
MyData data(5);
115+
int current_value = data.getValue();
116+
int doubled_value = multiply(current_value, 2);
117+
int final_result = doubled_value + global_value;
118+
}

0 commit comments

Comments
 (0)