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

Commit 252c6f6

Browse files
gamesh411gamesh411
authored andcommitted
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 e2504ca commit 252c6f6

18 files changed

+414
-50
lines changed

include/clang/CrossTU/CrossTranslationUnit.h

Lines changed: 18 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+
llvm::Optional<StringRef> OnDemandParsingDatabase);
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+
llvm::Optional<StringRef> OnDemandParsingDatabase);
129136

130137
/// This function loads a function definition from an external AST
131138
/// file.
@@ -141,11 +148,11 @@ 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,
155+
llvm::Optional<StringRef> OnDemandParsingDatabase);
149156

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

174181
private:
182+
std::unique_ptr<ASTUnit> loadASTOnDemand(StringRef ASTFileName) const;
175183
void lazyInitImporterSharedSt(TranslationUnitDecl *ToTU);
176184
ASTImporter &getOrCreateASTImporter(ASTUnit *Unit);
177185
const FunctionDecl *findFunctionInDeclContext(const DeclContext *DC,
@@ -190,6 +198,9 @@ class CrossTranslationUnitContext {
190198
ASTContext &Context;
191199
std::shared_ptr<ASTImporterSharedState> ImporterSharedSt;
192200
unsigned NumASTLoaded{0u};
201+
/// In case of on-demand parsing, the compilation database is parsed and
202+
/// stored.
203+
std::unique_ptr<tooling::JSONCompilationDatabase> CompileCommands;
193204
};
194205

195206
} // namespace cross_tu

include/clang/StaticAnalyzer/Core/AnalyzerOptions.def

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,21 @@ 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(bool, CTUOnDemandParsing, "ctu-on-demand-parsing",
359+
"Whether to parse function definitions from external TUs in "
360+
"an on-demand manner during analysis. When using on-demand "
361+
"parsing there is no need for pre-dumping ASTs. External "
362+
"definition mapping is still needed, and a valid compilation "
363+
"database with compile commands for the external TUs is also "
364+
"necessary. Disabled by default.",
365+
false)
366+
367+
ANALYZER_OPTION(StringRef, CTUOnDemandParsingDatabase,
368+
"ctu-on-demand-parsing-database",
369+
"The path to the compilation database used for on-demand "
370+
"parsing of ASTs during CTU analysis.",
371+
"compile_commands.json")
372+
358373
ANALYZER_OPTION(
359374
StringRef, ModelPath, "model-path",
360375
"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: 155 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+
llvm::Optional<StringRef> OnDemandParsingDatabase) {
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 (!OnDemandParsingDatabase) {
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+
llvm::Optional<StringRef> OnDemandParsingDatabase) {
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, OnDemandParsingDatabase);
221231
if (!ASTUnitOrError)
222232
return ASTUnitOrError.takeError();
223233
ASTUnit *Unit = *ASTUnitOrError;
@@ -302,9 +312,93 @@ 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 OnDemandParsingCommands. The compilation database
317+
/// can contain the path of the file under the key "file" as an absolute path,
318+
/// or as 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+
///
329+
/// We must have absolute paths inside the plist, because otherwise we would
330+
/// not be able to parse the bug, because we could not find the files with
331+
/// relative paths. The directory of one entry in the compilation db may be
332+
/// different from the directory where the plist is interpreted.
333+
///
334+
/// Note that as the ClangTool is instantiated with a lookup-vector, which
335+
/// contains a single entry; the supposedly absolute path of the source file.
336+
/// So, the ArgumentAdjuster will only be used on the single corresponding
337+
/// invocation. This garantees that even if two files match in name, but
338+
/// differ in location, only the correct one's invocation will be handled. This
339+
/// is due to the fact that the lookup is done correctly inside the
340+
/// OnDemandParsingDatabase, so it works for already absolute paths given under
341+
/// the "file" entry of the compilation database, but also if a relative path is
342+
/// given. In such a case, the lookup uses the "directory" entry as well to
343+
/// identify the correct file.
344+
std::unique_ptr<ASTUnit>
345+
CrossTranslationUnitContext::loadASTOnDemand(StringRef ASTSourcePath) const {
346+
347+
using namespace tooling;
348+
349+
SmallVector<std::string, 1> Files;
350+
Files.push_back(ASTSourcePath);
351+
ClangTool Tool(*CompileCommands, Files, CI.getPCHContainerOperations());
352+
353+
/// Lambda filter designed to find the source file argument inside an
354+
/// invocation used to build the ASTs, and replace it with its absolute path
355+
/// equivalent.
356+
auto SourcePathNormalizer = [ASTSourcePath](const CommandLineArguments &Args,
357+
StringRef FileName) {
358+
/// Match the argument to the absolute path by checking whether it is a
359+
/// postfix.
360+
auto IsPostfixOfLookup = [ASTSourcePath](const std::string &Arg) {
361+
return ASTSourcePath.rfind(Arg) != llvm::StringRef::npos;
362+
};
363+
364+
/// Commandline arguments are modified, and the API dictates the return a
365+
/// new instance, so copy the original.
366+
CommandLineArguments Result{Args};
367+
368+
/// Search for the source file argument. Start from the end as a heuristic,
369+
/// as most invocations tend to contain the source file argument in their
370+
/// the latter half. Only the first match is replaced.
371+
auto SourceFilePath =
372+
std::find_if(Result.rbegin(), Result.rend(), IsPostfixOfLookup);
373+
374+
/// If source file argument could not been found, return the original
375+
/// CommandlineArgumentsInstance.
376+
if (SourceFilePath == Result.rend())
377+
return Result;
378+
379+
/// Overwrite the argument with the \p ASTSourcePath, as it is assumed to be
380+
/// the absolute path of the file.
381+
*SourceFilePath = ASTSourcePath.str();
382+
383+
return Result;
384+
};
385+
386+
Tool.appendArgumentsAdjuster(std::move(SourcePathNormalizer));
387+
388+
std::vector<std::unique_ptr<ASTUnit>> ASTs;
389+
Tool.buildASTs(ASTs);
390+
391+
if (ASTs.size() > 0) {
392+
ASTs[0]->enableSourceFileDiagnostics();
393+
return std::move(ASTs[0]);
394+
} else
395+
return nullptr;
396+
}
397+
305398
llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
306399
StringRef LookupName, StringRef CrossTUDir, StringRef IndexName,
307-
bool DisplayCTUProgress, unsigned CTULoadThreshold) {
400+
bool DisplayCTUProgress, unsigned CTULoadThreshold,
401+
llvm::Optional<StringRef> OnDemandParsingDatabase) {
308402
// FIXME: The current implementation only supports loading functions with
309403
// a lookup name from a single translation unit. If multiple
310404
// translation units contains functions with the same lookup name an
@@ -326,7 +420,7 @@ llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
326420
else
327421
llvm::sys::path::append(IndexFile, IndexName);
328422
llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
329-
parseCrossTUIndex(IndexFile, CrossTUDir);
423+
parseCrossTUIndex(IndexFile, CrossTUDir, OnDemandParsingDatabase);
330424
if (IndexOrErr)
331425
FunctionFileMap = *IndexOrErr;
332426
else
@@ -338,33 +432,63 @@ llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
338432
++NumNotInOtherTU;
339433
return llvm::make_error<IndexError>(index_error_code::missing_definition);
340434
}
341-
StringRef ASTFileName = It->second;
342-
auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName);
435+
StringRef ASTSource = It->second;
436+
auto ASTCacheEntry = FileASTUnitMap.find(ASTSource);
343437
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);
438+
if (!OnDemandParsingDatabase) {
439+
// If no \p OnDemandParsingDatabase is given, try to load from AST dump
440+
// file, as on-demand parsing is disabled.
441+
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts =
442+
new DiagnosticOptions();
443+
TextDiagnosticPrinter *DiagClient =
444+
new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
445+
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
446+
IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
447+
new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
448+
449+
std::unique_ptr<ASTUnit> LoadedUnit(ASTUnit::LoadFromASTFile(
450+
ASTSource, CI.getPCHContainerOperations()->getRawReader(),
451+
ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts()));
452+
Unit = LoadedUnit.get();
453+
454+
// Cache the resulting ASTUnit.
455+
if (Unit)
456+
FileASTUnitMap[ASTSource] = std::move(LoadedUnit);
457+
} else {
458+
459+
// Lazily initialize the compilation database.
460+
if (!CompileCommands) {
461+
std::string LoadError;
462+
CompileCommands = tooling::JSONCompilationDatabase::loadFromFile(
463+
OnDemandParsingDatabase.getValue(), LoadError,
464+
tooling::JSONCommandLineSyntax::AutoDetect);
465+
if (!CompileCommands)
466+
return llvm::make_error<IndexError>(
467+
index_error_code::failed_to_get_external_ast);
468+
}
469+
470+
// Try loading on-demand.
471+
std::unique_ptr<ASTUnit> LoadedUnit = loadASTOnDemand(ASTSource);
472+
Unit = LoadedUnit.get();
473+
474+
// Cache the sulting ASTUnit.
475+
if (Unit)
476+
FileASTUnitMap[ASTSource] = std::move(LoadedUnit);
477+
}
356478
++NumASTLoaded;
357479
if (DisplayCTUProgress) {
358-
llvm::errs() << "CTU loaded AST file: "
359-
<< ASTFileName << "\n";
480+
llvm::errs() << "CTU loaded AST file: " << ASTSource << "\n";
360481
}
361482
} else {
362483
Unit = ASTCacheEntry->second.get();
363484
}
364-
FunctionASTUnitMap[LookupName] = Unit;
485+
// Fill the cache for the lookup name as well.
486+
if (Unit)
487+
FunctionASTUnitMap[LookupName] = Unit;
365488
} else {
366489
Unit = FnUnitCacheEntry->second;
367490
}
491+
368492
if (!Unit)
369493
return llvm::make_error<IndexError>(
370494
index_error_code::failed_to_get_external_ast);
@@ -397,7 +521,7 @@ CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD,
397521
});
398522
return llvm::make_error<IndexError>(index_error_code::failed_import);
399523
}
400-
auto *ToDecl = cast<FunctionDecl>(*ToDeclOrError);
524+
auto *ToDecl = cast<FunctionDecl>(*ToDeclOrError);
401525
assert(ToDecl->hasBody() && "Imported function should have body.");
402526
++NumGetCTUSuccess;
403527

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,12 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts,
460460
Diags->Report(diag::err_analyzer_config_invalid_input) << "ctu-dir"
461461
<< "a filename";
462462

463+
if (AnOpts.CTUOnDemandParsing &&
464+
!llvm::sys::fs::exists(AnOpts.CTUOnDemandParsingDatabase))
465+
Diags->Report(diag::err_analyzer_config_invalid_input)
466+
<< "ctu-on-demand-parsing-database"
467+
<< "a filename";
468+
463469
if (!AnOpts.ModelPath.empty() &&
464470
!llvm::sys::fs::is_directory(AnOpts.ModelPath))
465471
Diags->Report(diag::err_analyzer_config_invalid_input) << "model-path"

0 commit comments

Comments
 (0)