Skip to content

Commit 6d61256

Browse files
committed
[WIP] Implement -dump-deserialized-declaration-ranges flag to dump source ranges of deserialized decls.
1 parent 809f857 commit 6d61256

File tree

4 files changed

+243
-5
lines changed

4 files changed

+243
-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: 156 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "clang/Basic/FileEntry.h"
1616
#include "clang/Basic/LangStandard.h"
1717
#include "clang/Basic/Sarif.h"
18+
#include "clang/Basic/SourceManager.h"
1819
#include "clang/Basic/Stack.h"
1920
#include "clang/Frontend/ASTUnit.h"
2021
#include "clang/Frontend/CompilerInstance.h"
@@ -35,6 +36,7 @@
3536
#include "clang/Serialization/ASTReader.h"
3637
#include "clang/Serialization/GlobalModuleIndex.h"
3738
#include "llvm/ADT/ScopeExit.h"
39+
#include "llvm/ADT/StringRef.h"
3840
#include "llvm/Support/BuryPointer.h"
3941
#include "llvm/Support/ErrorHandling.h"
4042
#include "llvm/Support/FileSystem.h"
@@ -49,6 +51,144 @@ LLVM_INSTANTIATE_REGISTRY(FrontendPluginRegistry)
4951

5052
namespace {
5153

54+
/// DeserializedDeclsLineRangePrinter dumps ranges of deserialized declarations to aid debugging and bug minimization.
55+
/// It implements ASTConsumer and ASTDeserializationListener, so that an object of DeserializedDeclsLineRangePrinter registers
56+
/// as its own listener.
57+
/// The ASTDeserializationListener interface provides the DeclRead callback that we use to collect the deserialized Decls.
58+
/// Note that printing or otherwise processing them as this point is dangerous, since that could trigger additional
59+
/// deserialization and crash compilation.
60+
/// Therefore, we process the collected Decls in HandleTranslationUnit method of ASTConsumer.
61+
/// This is a safe point, since we know that by this point all the Decls needed by the compiler frontend have been
62+
/// deserialized. In case our processing causes further deserialization, DeclRead from the listener might be called again.
63+
/// However, at that point we don't accept any more Decls for processing.
64+
class DeserializedDeclsLineRangePrinter : public ASTDeserializationListener, public ASTConsumer {
65+
public:
66+
explicit DeserializedDeclsLineRangePrinter(SourceManager &SM, std::unique_ptr<llvm::raw_fd_ostream> OS)
67+
: ASTDeserializationListener(), SM(SM), OS(std::move(OS)) {}
68+
69+
void DeclRead(GlobalDeclID ID, const Decl *D) override {
70+
if (!IsCollectingDecls) {
71+
return;
72+
}
73+
if (!D || isa<TranslationUnitDecl>(D) || isa<LinkageSpecDecl>(D) ||
74+
isa<NamespaceDecl>(D))
75+
return;
76+
if (auto *DC = D->getDeclContext(); !DC || !DC->isFileContext())
77+
return;
78+
PendingDecls.push_back(D);
79+
ASTDeserializationListener::DeclRead(ID, D);
80+
}
81+
82+
using Position = std::pair<unsigned, unsigned>;
83+
struct RequiredRanges {
84+
StringRef Filename;
85+
std::vector<std::pair<Position, Position>> FromTo;
86+
};
87+
void HandleTranslationUnit(ASTContext &Context) override {
88+
IsCollectingDecls = false;
89+
std::vector<const Decl *> Decls = std::move(PendingDecls);
90+
if (!PendingDecls.empty()) {
91+
llvm::errs() << "Deserialized more decls while printing, total of "
92+
<< PendingDecls.size() << "\n";
93+
PendingDecls.clear();
94+
}
95+
96+
// Merge ranges in each of the files. For simplicity, track lines and hope
97+
// they do not break things.
98+
struct FileData {
99+
std::vector<std::pair<Position, Position>> FromTo;
100+
std::vector<std::pair<unsigned, unsigned>> Columns;
101+
OptionalFileEntryRef Ref;
102+
};
103+
llvm::DenseMap<const FileEntry *, FileData> FileToLines;
104+
for (const Decl *D : Decls) {
105+
CharSourceRange R = SM.getExpansionRange(D->getSourceRange());
106+
if (!R.isValid())
107+
continue;
108+
109+
auto *F = SM.getFileEntryForID(SM.getFileID(R.getBegin()));
110+
if (F != SM.getFileEntryForID(SM.getFileID(R.getEnd())))
111+
continue;
112+
113+
auto &Data = FileToLines[F];
114+
if (!Data.Ref)
115+
Data.Ref =
116+
SM.getFileEntryRefForID(SM.getFileID(R.getBegin()));
117+
Data.FromTo.push_back({{SM.getSpellingLineNumber(R.getBegin()), SM.getSpellingColumnNumber(R.getBegin())},
118+
{SM.getSpellingLineNumber(R.getEnd()), SM.getSpellingColumnNumber(R.getEnd())}});
119+
}
120+
121+
std::vector<RequiredRanges> Result;
122+
for (auto &[F, Data] : FileToLines) {
123+
auto& FromTo = Data.FromTo;
124+
assert(!FromTo.empty());
125+
126+
if (!Data.Ref) continue;
127+
128+
llvm::sort(FromTo);
129+
130+
std::vector<std::pair<Position, Position>> MergedLines;
131+
MergedLines.push_back(FromTo.front());
132+
for (auto It = FromTo.begin() + 1; It < FromTo.end(); ++It) {
133+
if (MergedLines.back().second < It->first) {
134+
MergedLines.push_back(*It);
135+
continue;
136+
}
137+
if (MergedLines.back().second < It->second)
138+
MergedLines.back().second = It->second;
139+
}
140+
Result.push_back({Data.Ref->getName(), MergedLines});
141+
}
142+
printJson(Result);
143+
}
144+
145+
void printJson(const std::vector<RequiredRanges>& Result) {
146+
*OS << "{\n";
147+
*OS << " \"required_ranges\": [\n";
148+
for (size_t i = 0; i < Result.size(); ++i) {
149+
auto &F = Result[i].Filename;
150+
auto &MergedLines = Result[i].FromTo;
151+
*OS << " {\n";
152+
*OS << " \"file\": \"" << F << "\",\n";
153+
*OS << " \"range\": [\n";
154+
for (size_t j = 0; j < MergedLines.size(); ++j) {
155+
auto &From = MergedLines[j].first;
156+
auto &To = MergedLines[j].second;
157+
*OS << " {\n";
158+
*OS << " \"from\": {\n";
159+
*OS << " \"line\": " << From.first << ",\n";
160+
*OS << " \"column\": " << From.second << "\n },\n";
161+
*OS << " \"to\": {\n";
162+
*OS << " \"line\": " << To.first << ",\n";
163+
*OS << " \"column\": " << To.second << "\n }\n";
164+
*OS << " }";
165+
if (j < MergedLines.size() - 1) {
166+
*OS << ",";
167+
}
168+
*OS << "\n";
169+
}
170+
*OS << " ]\n }";
171+
if (i < Result.size() - 1) {
172+
*OS << ",";
173+
}
174+
*OS << "\n";
175+
}
176+
*OS << " ]\n";
177+
*OS << "}\n";
178+
}
179+
180+
ASTDeserializationListener *GetASTDeserializationListener() override {
181+
return this;
182+
}
183+
184+
private:
185+
std::vector<const Decl *> PendingDecls;
186+
bool IsCollectingDecls = true;
187+
const SourceManager &SM;
188+
std::unique_ptr<llvm::raw_ostream> OS;
189+
};
190+
191+
52192
/// Dumps deserialized declarations.
53193
class DeserializedDeclsDumper : public DelegatingDeserializationListener {
54194
public:
@@ -121,6 +261,19 @@ FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
121261
if (!Consumer)
122262
return nullptr;
123263

264+
std::vector<std::unique_ptr<ASTConsumer>> Consumers;
265+
llvm::StringRef DumpDeserializedDeclarationRangesPath = CI.getFrontendOpts().DumpDeserializedDeclarationRangesPath;
266+
if (!DumpDeserializedDeclarationRangesPath.empty()) {
267+
std::error_code ErrorCode;
268+
auto FileStream = std::make_unique<llvm::raw_fd_ostream>(DumpDeserializedDeclarationRangesPath, ErrorCode, llvm::sys::fs::OF_None);
269+
if (!ErrorCode) {
270+
auto Printer = std::make_unique<DeserializedDeclsLineRangePrinter>(CI.getSourceManager(), std::move(FileStream));
271+
Consumers.push_back(std::move(Printer));
272+
} else {
273+
llvm::errs() << "Failed to create output file for -dump-deserialized-declaration-ranges flag, file path: " << DumpDeserializedDeclarationRangesPath << ", error: " << ErrorCode.message() << "\n";
274+
}
275+
}
276+
124277
// Validate -add-plugin args.
125278
bool FoundAllPlugins = true;
126279
for (const std::string &Arg : CI.getFrontendOpts().AddPluginActions) {
@@ -138,17 +291,12 @@ FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
138291
if (!FoundAllPlugins)
139292
return nullptr;
140293

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-
145294
// If this is a code completion run, avoid invoking the plugin consumers
146295
if (CI.hasCodeCompletionConsumer())
147296
return Consumer;
148297

149298
// Collect the list of plugins that go before the main action (in Consumers)
150299
// or after it (in AfterConsumers)
151-
std::vector<std::unique_ptr<ASTConsumer>> Consumers;
152300
std::vector<std::unique_ptr<ASTConsumer>> AfterConsumers;
153301
for (const FrontendPluginRegistry::entry &Plugin :
154302
FrontendPluginRegistry::entries()) {
@@ -191,6 +339,9 @@ FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
191339
Consumers.push_back(std::move(C));
192340
}
193341

342+
assert(Consumers.size() >= 1 && "should have added the main consumer");
343+
if (Consumers.size() == 1)
344+
return std::move(Consumers.front());
194345
return std::make_unique<MultiplexConsumer>(std::move(Consumers));
195346
}
196347

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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
7+
// RUN: echo '{ \
8+
// RUN: "required_ranges": [\
9+
// RUN: {\
10+
// RUN: "file": "/usr/local/google/home/bakalova/llvm-project/build/tools/clang/test/Frontend/Output/dump-deserialized-declaration-ranges.cpp.tmp/foo.h",\
11+
// RUN: "range": [\
12+
// RUN: {\
13+
// RUN: "from": {\
14+
// RUN: "line": 1,\
15+
// RUN: "column": 1\
16+
// RUN: },\
17+
// RUN: "to": {\
18+
// RUN: "line": 9,\
19+
// RUN: "column": 1\
20+
// RUN: }\
21+
// RUN: },\
22+
// RUN: {\
23+
// RUN: "from": {\
24+
// RUN: "line": 11,\
25+
// RUN: "column": 1\
26+
// RUN: },\
27+
// RUN: "to": {\
28+
// RUN: "line": 11,\
29+
// RUN: "column": 12\
30+
// RUN: }\
31+
// RUN: },\
32+
// RUN: {\
33+
// RUN: "from": {\
34+
// RUN: "line": 13,\
35+
// RUN: "column": 1\
36+
// RUN: },\
37+
// RUN: "to": {\
38+
// RUN: "line": 15,\
39+
// RUN: "column": 1\
40+
// RUN: }\
41+
// RUN: }\
42+
// RUN: ]\
43+
// RUN: }\
44+
// RUN: ]\
45+
// RUN:}' > %t/expected_decls
46+
// RUN: jq '.' %t/expected_decls > %t/expected_decls_formatted
47+
// RUN: diff %t/decls %t/expected_decls_formatted
48+
49+
//--- foo.cppmap
50+
module foo {
51+
header "foo.h"
52+
export *
53+
}
54+
55+
//--- foo.h
56+
class MyData {
57+
public:
58+
MyData(int val): value_(val) {}
59+
int getValue() const {
60+
return 5;
61+
}
62+
private:
63+
int value_;
64+
};
65+
66+
extern int global_value;
67+
68+
int multiply(int a, int b) {
69+
return a * b;
70+
}
71+
72+
//--- foo.cpp
73+
#include "foo.h"
74+
int global_value = 5;
75+
int main() {
76+
MyData data(5);
77+
int current_value = data.getValue();
78+
int doubled_value = multiply(current_value, 2);
79+
int final_result = doubled_value + global_value;
80+
}

0 commit comments

Comments
 (0)