Skip to content
This repository was archived by the owner on Mar 30, 2021. It is now read-only.

Commit 9b4cb57

Browse files
author
gamesh411
committed
Add On-the-fly analysis support
Add an option to enable on-the-fly parsing of needed ASTs during CTU analysis. The option CTUCompilationDatabase should be a path to a compilation database, which has all the necessary information to generate the ASTs. In case an empty string is given, on-the-fly parsing is disabled.
1 parent 643c9c2 commit 9b4cb57

17 files changed

+388
-50
lines changed

include/clang/CrossTU/CrossTranslationUnit.h

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class FunctionDecl;
3232
class NamedDecl;
3333
class TranslationUnitDecl;
3434

35+
namespace tooling {
36+
class JSONCompilationDatabase;
37+
}
38+
3539
namespace cross_tu {
3640

3741
enum class index_error_code {
@@ -41,6 +45,7 @@ enum class index_error_code {
4145
multiple_definitions,
4246
missing_definition,
4347
failed_import,
48+
failed_to_load_compilation_database,
4449
failed_to_get_external_ast,
4550
failed_to_generate_usr,
4651
triple_mismatch,
@@ -85,7 +90,8 @@ class IndexError : public llvm::ErrorInfo<IndexError> {
8590
/// \return Returns a map where the USR is the key and the filepath is the value
8691
/// or an error.
8792
llvm::Expected<llvm::StringMap<std::string>>
88-
parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir);
93+
parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir,
94+
StringRef CompilationDatabase);
8995

9096
std::string createCrossTUIndexString(const llvm::StringMap<std::string> &Index);
9197

@@ -125,7 +131,8 @@ class CrossTranslationUnitContext {
125131
llvm::Expected<const FunctionDecl *>
126132
getCrossTUDefinition(const FunctionDecl *FD, StringRef CrossTUDir,
127133
StringRef IndexName, bool DisplayCTUProgress,
128-
unsigned CTULoadThreshold);
134+
unsigned CTULoadThreshold,
135+
StringRef CompilationDatabase);
129136

130137
/// This function loads a function definition from an external AST
131138
/// file.
@@ -141,11 +148,10 @@ class CrossTranslationUnitContext {
141148
/// The returned pointer is never a nullptr.
142149
///
143150
/// Note that the AST files should also be in the \p CrossTUDir.
144-
llvm::Expected<ASTUnit *> loadExternalAST(StringRef LookupName,
145-
StringRef CrossTUDir,
146-
StringRef IndexName,
147-
bool DisplayCTUProgress,
148-
unsigned CTULoadThreshold);
151+
llvm::Expected<ASTUnit *>
152+
loadExternalAST(StringRef LookupName, StringRef CrossTUDir,
153+
StringRef IndexName, bool DisplayCTUProgress,
154+
unsigned CTULoadThreshold, StringRef CompilationDatabase);
149155

150156
/// This function merges a definition from a separate AST Unit into
151157
/// the current one which was created by the compiler instance that
@@ -172,6 +178,7 @@ class CrossTranslationUnitContext {
172178
GetImportedFromSourceLocation(const clang::SourceLocation &ToLoc) const;
173179

174180
private:
181+
std::unique_ptr<ASTUnit> loadASTOnDemand(StringRef ASTFileName) const;
175182
void lazyInitImporterSharedSt(TranslationUnitDecl *ToTU);
176183
ASTImporter &getOrCreateASTImporter(ASTUnit *Unit);
177184
const FunctionDecl *findFunctionInDeclContext(const DeclContext *DC,
@@ -190,6 +197,9 @@ class CrossTranslationUnitContext {
190197
ASTContext &Context;
191198
std::shared_ptr<ASTImporterSharedState> ImporterSharedSt;
192199
unsigned NumASTLoaded{0u};
200+
/// In case of on-demand parsing, the compilation database is parsed and
201+
/// stored.
202+
std::unique_ptr<tooling::JSONCompilationDatabase> CompileCommands;
193203
};
194204

195205
} // namespace cross_tu

include/clang/StaticAnalyzer/Core/AnalyzerOptions.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,12 @@ ANALYZER_OPTION(StringRef, CTUIndexName, "ctu-index-name",
355355
"the name of the file containing the CTU index of definitions.",
356356
"externalDefMap.txt")
357357

358+
ANALYZER_OPTION(StringRef, CTUCompilationDatabase, "ctu-compilation-database",
359+
"The path to the compilation database used for on-demand "
360+
"parsing of ASTs during CTU analysis. An empty valued disables "
361+
"on-demand parsing. Disabled by default.",
362+
"")
363+
358364
ANALYZER_OPTION(
359365
StringRef, ModelPath, "model-path",
360366
"The analyzer can inline an alternative implementation written in C at the "

lib/CrossTU/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ add_clang_library(clangCrossTU
1010
clangBasic
1111
clangFrontend
1212
clangIndex
13+
clangTooling
1314
)

lib/CrossTU/CrossTranslationUnit.cpp

Lines changed: 153 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
#include "clang/Frontend/CompilerInstance.h"
2020
#include "clang/Frontend/TextDiagnosticPrinter.h"
2121
#include "clang/Index/USRGeneration.h"
22-
#include "llvm/ADT/Triple.h"
22+
#include "clang/Tooling/JSONCompilationDatabase.h"
23+
#include "clang/Tooling/Tooling.h"
2324
#include "llvm/ADT/Statistic.h"
25+
#include "llvm/ADT/Triple.h"
2426
#include "llvm/Support/ErrorHandling.h"
2527
#include "llvm/Support/ManagedStatic.h"
2628
#include "llvm/Support/Path.h"
2729
#include "llvm/Support/raw_ostream.h"
30+
#include <algorithm>
2831
#include <fstream>
2932
#include <sstream>
3033

@@ -101,6 +104,8 @@ class IndexErrorCategory : public std::error_category {
101104
return "Failed to import the definition.";
102105
case index_error_code::failed_to_get_external_ast:
103106
return "Failed to load external AST source.";
107+
case index_error_code::failed_to_load_compilation_database:
108+
return "Failed to load compilation database.";
104109
case index_error_code::failed_to_generate_usr:
105110
return "Failed to generate USR.";
106111
case index_error_code::triple_mismatch:
@@ -130,7 +135,8 @@ std::error_code IndexError::convertToErrorCode() const {
130135
}
131136

132137
llvm::Expected<llvm::StringMap<std::string>>
133-
parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir) {
138+
parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir,
139+
StringRef CompilationDatabase) {
134140
std::ifstream ExternalMapFile(IndexPath);
135141
if (!ExternalMapFile)
136142
return llvm::make_error<IndexError>(index_error_code::missing_index_file,
@@ -148,9 +154,14 @@ parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir) {
148154
return llvm::make_error<IndexError>(
149155
index_error_code::multiple_definitions, IndexPath.str(), LineNo);
150156
StringRef FileName = LineRef.substr(Pos + 1);
151-
SmallString<256> FilePath = CrossTUDir;
152-
llvm::sys::path::append(FilePath, FileName);
153-
Result[LookupName] = FilePath.str().str();
157+
// AST-dump based analysis requires a prefixed path.
158+
if (CompilationDatabase.empty()) {
159+
SmallString<256> FilePath = CrossTUDir;
160+
llvm::sys::path::append(FilePath, FileName);
161+
Result[LookupName] = FilePath.str().str();
162+
} else {
163+
Result[LookupName] = FileName.str();
164+
}
154165
} else
155166
return llvm::make_error<IndexError>(
156167
index_error_code::invalid_index_format, IndexPath.str(), LineNo);
@@ -203,11 +214,10 @@ CrossTranslationUnitContext::findFunctionInDeclContext(const DeclContext *DC,
203214
}
204215

205216
llvm::Expected<const FunctionDecl *>
206-
CrossTranslationUnitContext::getCrossTUDefinition(const FunctionDecl *FD,
207-
StringRef CrossTUDir,
208-
StringRef IndexName,
209-
bool DisplayCTUProgress,
210-
unsigned CTULoadThreshold) {
217+
CrossTranslationUnitContext::getCrossTUDefinition(
218+
const FunctionDecl *FD, StringRef CrossTUDir, StringRef IndexName,
219+
bool DisplayCTUProgress, unsigned CTULoadThreshold,
220+
StringRef CompilationDatabase) {
211221
assert(FD && "FD is missing, bad call to this function!");
212222
assert(!FD->hasBody() && "FD has a definition in current translation unit!");
213223
++NumGetCTUCalled;
@@ -217,7 +227,7 @@ CrossTranslationUnitContext::getCrossTUDefinition(const FunctionDecl *FD,
217227
index_error_code::failed_to_generate_usr);
218228
llvm::Expected<ASTUnit *> ASTUnitOrError =
219229
loadExternalAST(LookupFnName, CrossTUDir, IndexName, DisplayCTUProgress,
220-
CTULoadThreshold);
230+
CTULoadThreshold, CompilationDatabase);
221231
if (!ASTUnitOrError)
222232
return ASTUnitOrError.takeError();
223233
ASTUnit *Unit = *ASTUnitOrError;
@@ -302,9 +312,91 @@ void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE) {
302312
}
303313
}
304314

315+
/// Load the AST from a source-file, which is supposed to be located inside the
316+
/// compilation database \p CompileCommands. The compilation database can can
317+
/// contain the path of the file under the key "file" as an absolute path, or as
318+
/// a relative path. When emitting diagnostics, plist files may contain
319+
/// references to a location in a TU, that is different from the main TU. In
320+
/// such cases, the file path emitted by the DiagnosticEngine is based on how
321+
/// the exact invocation is assembled inside the ClangTool, which performs the
322+
/// building of the ASTs. In order ensure absolute paths inside the diagnostics,
323+
/// we use the ArgumentsAdjuster API of ClangTool to make sure that the
324+
/// invocation inside ClangTool is always made with an absolute path. \p
325+
/// ASTSourcePath is assumed to be the lookup-name of the file, which comes from
326+
/// the Index. The Index is built by the \p clang-extdef-mapping tool, which is
327+
/// supposed to generate absolute paths.
328+
/// Note that as the ClangTool is instantiated with a lookup-vector, which
329+
/// contains a single entry; the the supposedly absolute path of the source
330+
/// file. So the ArgumentAdjuster will only be used on the single corresponding
331+
/// invocation. This garantees that even if two files match in name, but
332+
/// differ in location, only the correct one's invocation will be handled. This
333+
/// is due to the fact that the lookup is done correctly inside the
334+
/// CompilationDatabase, so it works for already absolute paths given under the
335+
/// "file" entry of the compilation database, but also if a relative path is
336+
/// given. In such a case, the lookup uses the "directory" entry as well to
337+
/// identify the correct file.
338+
std::unique_ptr<ASTUnit>
339+
CrossTranslationUnitContext::loadASTOnDemand(StringRef ASTSourcePath) const {
340+
341+
using namespace tooling;
342+
343+
SmallVector<std::string, 1> Files;
344+
Files.push_back(ASTSourcePath);
345+
ClangTool Tool(*CompileCommands, Files, CI.getPCHContainerOperations());
346+
347+
/// Lambda filter designed to find the source file argument inside an
348+
/// invocation used to build the ASTs, and replace it with its absolute path
349+
/// equivalent.
350+
auto SourcePathNormalizer = [ASTSourcePath](const CommandLineArguments &Args,
351+
StringRef FileName) {
352+
/// Match the argument to the absolute path by checking whether it is a
353+
/// postfix.
354+
auto IsPostfixOfLookup = [ASTSourcePath](const std::string &Arg) {
355+
return ASTSourcePath.rfind(Arg) != llvm::StringRef::npos;
356+
};
357+
358+
/// Commandline arguments are modified, and the API dictates the return a
359+
/// new instance, so copy the original.
360+
CommandLineArguments Result{Args};
361+
362+
/// Search for the source file argument. Start from the end as a heuristic,
363+
/// as most invocations tend to contain the source file argument in their
364+
/// the latter half. Only the first match is replaced.
365+
auto SourceFilePath =
366+
std::find_if(Result.rbegin(), Result.rend(), IsPostfixOfLookup);
367+
368+
/// If source file argument could not been found, return the original
369+
/// CommandlineArgumentsInstance.
370+
if (SourceFilePath == Result.rend())
371+
return Result;
372+
373+
llvm::errs() << "Matching argument: '" << *SourceFilePath
374+
<< "', which matches original filename '" << ASTSourcePath
375+
<< "', overwriting...\n";
376+
377+
/// Overwrite the argument with the \p ASTSourcePath, as it is assumed to be
378+
/// the absolute path of the file.
379+
*SourceFilePath = ASTSourcePath.str();
380+
381+
return Result;
382+
};
383+
384+
Tool.appendArgumentsAdjuster(std::move(SourcePathNormalizer));
385+
386+
std::vector<std::unique_ptr<ASTUnit>> ASTs;
387+
Tool.buildASTs(ASTs);
388+
389+
if (ASTs.size() > 0) {
390+
ASTs[0]->enableSourceFileDiagnostics();
391+
return std::move(ASTs[0]);
392+
} else
393+
return nullptr;
394+
}
395+
305396
llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
306397
StringRef LookupName, StringRef CrossTUDir, StringRef IndexName,
307-
bool DisplayCTUProgress, unsigned CTULoadThreshold) {
398+
bool DisplayCTUProgress, unsigned CTULoadThreshold,
399+
StringRef CompilationDatabase) {
308400
// FIXME: The current implementation only supports loading functions with
309401
// a lookup name from a single translation unit. If multiple
310402
// translation units contains functions with the same lookup name an
@@ -326,7 +418,7 @@ llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
326418
else
327419
llvm::sys::path::append(IndexFile, IndexName);
328420
llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
329-
parseCrossTUIndex(IndexFile, CrossTUDir);
421+
parseCrossTUIndex(IndexFile, CrossTUDir, CompilationDatabase);
330422
if (IndexOrErr)
331423
FunctionFileMap = *IndexOrErr;
332424
else
@@ -338,33 +430,63 @@ llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
338430
++NumNotInOtherTU;
339431
return llvm::make_error<IndexError>(index_error_code::missing_definition);
340432
}
341-
StringRef ASTFileName = It->second;
342-
auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName);
433+
StringRef ASTSource = It->second;
434+
auto ASTCacheEntry = FileASTUnitMap.find(ASTSource);
343435
if (ASTCacheEntry == FileASTUnitMap.end()) {
344-
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
345-
TextDiagnosticPrinter *DiagClient =
346-
new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
347-
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
348-
IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
349-
new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
350-
351-
std::unique_ptr<ASTUnit> LoadedUnit(ASTUnit::LoadFromASTFile(
352-
ASTFileName, CI.getPCHContainerOperations()->getRawReader(),
353-
ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts()));
354-
Unit = LoadedUnit.get();
355-
FileASTUnitMap[ASTFileName] = std::move(LoadedUnit);
436+
if (CompilationDatabase.empty()) {
437+
// If no \p CompilationDatabase is given, try to load from AST dump
438+
// file, as on-demand parsing is disabled.
439+
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts =
440+
new DiagnosticOptions();
441+
TextDiagnosticPrinter *DiagClient =
442+
new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
443+
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
444+
IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
445+
new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
446+
447+
std::unique_ptr<ASTUnit> LoadedUnit(ASTUnit::LoadFromASTFile(
448+
ASTSource, CI.getPCHContainerOperations()->getRawReader(),
449+
ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts()));
450+
Unit = LoadedUnit.get();
451+
452+
// Cache the resulting ASTUnit.
453+
if (Unit)
454+
FileASTUnitMap[ASTSource] = std::move(LoadedUnit);
455+
} else {
456+
457+
// Lazily initialize the compilation database.
458+
if (!CompileCommands) {
459+
std::string LoadError;
460+
CompileCommands = tooling::JSONCompilationDatabase::loadFromFile(
461+
CompilationDatabase, LoadError,
462+
tooling::JSONCommandLineSyntax::AutoDetect);
463+
if (!CompileCommands)
464+
return llvm::make_error<IndexError>(
465+
index_error_code::failed_to_get_external_ast);
466+
}
467+
468+
// Try loading on-demand.
469+
std::unique_ptr<ASTUnit> LoadedUnit = loadASTOnDemand(ASTSource);
470+
Unit = LoadedUnit.get();
471+
472+
// Cache the sulting ASTUnit.
473+
if (Unit)
474+
FileASTUnitMap[ASTSource] = std::move(LoadedUnit);
475+
}
356476
++NumASTLoaded;
357477
if (DisplayCTUProgress) {
358-
llvm::errs() << "CTU loaded AST file: "
359-
<< ASTFileName << "\n";
478+
llvm::errs() << "CTU loaded AST file: " << ASTSource << "\n";
360479
}
361480
} else {
362481
Unit = ASTCacheEntry->second.get();
363482
}
364-
FunctionASTUnitMap[LookupName] = Unit;
483+
// Fill the cache for the lookup name as well.
484+
if (Unit)
485+
FunctionASTUnitMap[LookupName] = Unit;
365486
} else {
366487
Unit = FnUnitCacheEntry->second;
367488
}
489+
368490
if (!Unit)
369491
return llvm::make_error<IndexError>(
370492
index_error_code::failed_to_get_external_ast);
@@ -397,7 +519,7 @@ CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD,
397519
});
398520
return llvm::make_error<IndexError>(index_error_code::failed_import);
399521
}
400-
auto *ToDecl = cast<FunctionDecl>(*ToDeclOrError);
522+
auto *ToDecl = cast<FunctionDecl>(*ToDeclOrError);
401523
assert(ToDecl->hasBody() && "Imported function should have body.");
402524
++NumGetCTUSuccess;
403525

lib/StaticAnalyzer/Core/CallEvent.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -560,9 +560,9 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const {
560560
cross_tu::CrossTranslationUnitContext &CTUCtx =
561561
*Engine.getCrossTranslationUnitContext();
562562
llvm::Expected<const FunctionDecl *> CTUDeclOrError =
563-
CTUCtx.getCrossTUDefinition(FD, Opts.CTUDir, Opts.CTUIndexName,
564-
Opts.DisplayCTUProgress,
565-
Opts.CTUImportThreshold);
563+
CTUCtx.getCrossTUDefinition(
564+
FD, Opts.CTUDir, Opts.CTUIndexName, Opts.DisplayCTUProgress,
565+
Opts.CTUImportThreshold, Opts.CTUCompilationDatabase);
566566

567567
if (!CTUDeclOrError) {
568568
handleAllErrors(CTUDeclOrError.takeError(),
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
c:@F@inlineAsm ctu-other.c
2+
c:@F@g ctu-other.c
3+
c:@F@f ctu-other.c
4+
c:@F@enumCheck ctu-other.c
5+
c:@F@identImplicit ctu-other.c
6+
c:@F@structInProto ctu-other.c

0 commit comments

Comments
 (0)