Skip to content

Instantly share code, notes, and snippets.

@vitonzhangtt
Last active June 30, 2022 08:02
Show Gist options
  • Save vitonzhangtt/4d90a4775f7bcead8d6d063b5b08aa0c to your computer and use it in GitHub Desktop.
Save vitonzhangtt/4d90a4775f7bcead8d6d063b5b08aa0c to your computer and use it in GitHub Desktop.
Driver.cpp in oclint-22.02
/*-
* This code is derived from
* http://llvm.org/svn/llvm-project/cfe/trunk/lib/Tooling/Tooling.cpp
* with the following license:
*
* University of Illinois/NCSA
* Open Source License
*
* Copyright (c) 2003-2013 University of Illinois at Urbana-Champaign.
* All rights reserved.
*
* Developed by:
*
* LLVM Team
*
* University of Illinois at Urbana-Champaign
*
* http://llvm.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the names of the LLVM Team, University of Illinois at
* Urbana-Champaign, nor the names of its contributors may be used to
* endorse or promote products derived from this Software without specific
* prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
#include "oclint/Driver.h"
#include <unistd.h>
#include <sstream>
#include <llvm/ADT/IntrusiveRefCntPtr.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Option/ArgList.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/Host.h>
#include <llvm/Support/raw_ostream.h>
#include <clang/Basic/Diagnostic.h>
#include <clang/Driver/Compilation.h>
#include <clang/Driver/Driver.h>
#include <clang/Driver/Job.h>
#include <clang/Driver/Tool.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendDiagnostic.h>
#include <clang/Tooling/ArgumentsAdjusters.h>
#include <clang/Tooling/CompilationDatabase.h>
#include <clang/Tooling/Tooling.h>
#include "oclint/CompilerInstance.h"
#include "oclint/DiagnosticDispatcher.h"
#include "oclint/GenericException.h"
#include "oclint/Logger.h"
#include "oclint/Options.h"
#include "oclint/ViolationSet.h"
using namespace oclint;
typedef std::vector<std::pair<std::string, clang::tooling::CompileCommand>> CompileCommandPairs;
/**
https://clang.llvm.org/doxygen/classclang_1_1driver_1_1Driver.html
Constructor
Driver::Driver(StringRef ClangExecutable,
StringRef TargetTriple,
DiagnosticsEngine & Diags,
std::string Title = "clang LLVM compiler",
IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS = nullptr
)
*/
static clang::driver::Driver *newDriver(clang::DiagnosticsEngine *diagnostics,
const char *binaryName)
{
clang::driver::Driver *driver =
new clang::driver::Driver(binaryName, llvm::sys::getDefaultTargetTriple(), *diagnostics);
driver->setTitle("OCLint");
return driver;
}
static clang::CompilerInvocation *newInvocation(clang::DiagnosticsEngine *diagnostics,
const llvm::opt::ArgStringList &argStringList)
{
assert(!argStringList.empty() && "Must at least contain the program name!");
auto invocation = new clang::CompilerInvocation;
clang::CompilerInvocation::CreateFromArgs(*invocation, argStringList, *diagnostics);
invocation->getFrontendOpts().DisableFree = false;
return invocation;
}
/**
Construct the compile commands from compilation database and paths of source code.
Each compile command will be put into the parameter `compileCommands`.
1. clang::tooling::CompileCommand
Specifies the working directory and command of a compilation.
https://clang.llvm.org/doxygen/structclang_1_1tooling_1_1CompileCommand.html
*/
static void constructCompileCommands(
CompileCommandPairs &compileCommands,
const clang::tooling::CompilationDatabase &compilationDatabase,
llvm::ArrayRef<std::string> sourcePaths)
{
for (const auto &sourcePath : sourcePaths)
{
std::string filePath(clang::tooling::getAbsolutePath(sourcePath));
std::vector<clang::tooling::CompileCommand> compileCmdsForFile =
compilationDatabase.getCompileCommands(filePath);
if (compileCmdsForFile.empty())
{
llvm::errs() << "Skipping " << filePath << ". Compile command not found.\n";
continue;
}
for (auto &compileCommand : compileCmdsForFile)
{
compileCommands.push_back(std::make_pair(filePath, compileCommand));
}
}
}
static std::string compilationJobsToString(const clang::driver::JobList &jobs)
{
clang::SmallString<256> errorMsg;
llvm::raw_svector_ostream errorStream(errorMsg);
jobs.Print(errorStream, "; ", true);
return errorStream.str().str();
}
static const llvm::opt::ArgStringList *getCC1Arguments(clang::driver::Compilation *compilation)
{
/*
JobList - A sequence of jobs to perform.
https://clang.llvm.org/doxygen/Job_8h_source.html
JobList contains a list of Command instances.
*/
// Compilation - A set of tasks to perform for a single driver invocation.
const clang::driver::JobList &jobList = compilation->getJobs();
auto jobSize = jobList.size();
if (jobSize == 0)
{
throw oclint::GenericException("compilation contains no job:\n" +
compilationJobsToString(jobList) + "\n");
}
bool offloadCompilation = false;
if (jobSize > 1)
{
/* clang::driver::Action
Represent an abstract compilation step to perform.
An action represents an edge in the compilation graph;
typically it is a job to transform an input using some tool.
The current driver is hard wired to expect actions which
produce a single primary output, at least in terms of
controlling the compilation.
Actions can produce auxiliary files, but can only produce
a single output to feed into subsequent actions.
*/
auto actions = compilation->getActions();
for (auto action : actions)
{
// An offload action combines host or/and device actions according
// to the programming model implementation needs and
// propagates the offloading kind to its dependences.
if (llvm::isa<clang::driver::OffloadAction>(action))
{
assert(actions.size() > 1);
offloadCompilation = true;
break;
}
}
}
if (jobSize > 1 && !offloadCompilation)
{
throw oclint::GenericException("compilation contains multiple jobs:\n" +
compilationJobsToString(jobList) + "\n");
}
if (!clang::isa<clang::driver::Command>(*jobList.begin()))
{
throw oclint::GenericException("compilation job does not contain correct command:\n" +
compilationJobsToString(jobList) + "\n");
}
/*
clang::driver::Command - An executable path/name and argument vector to execute.
*/
const clang::driver::Command &cmd = clang::cast<clang::driver::Command>(*jobList.begin());
// getCreator - Return the Tool which caused the creation of this job.
// clang::driver::Tool - Information on a specific compilation tool.
if (llvm::StringRef(cmd.getCreator().getName()) != "clang")
{
throw oclint::GenericException("expected a command for clang compiler");
}
// The list of program arguments (not including the implicit first
// argument, which will be the executable).
return &cmd.getArguments();
}
/**
1. clang::DiagnosticOptions
Options for controlling the compiler diagnostics engine.
https://clang.llvm.org/doxygen/classclang_1_1DiagnosticOptions.html
2. clang::DiagnosticsEngine
Concrete class used by the front-end to report problems and issues.
This massages the diagnostics (e.g. handling things like "report warnings
as errors") and passes them off to the DiagnosticConsumer for reporting to
the user.
DiagnosticsEngine is tied to one translation unit and one SourceManager.
3. clang::DiagnosticIDs
Used for handling and querying diagnostic IDs.
Can be used and shared by multiple Diagnostics for multiple translation units.
DiagnosticIDs.h
https://clang.llvm.org/doxygen/DiagnosticIDs_8h_source.html
```cpp
class DiagnosticIDs : public RefCountedBase<DiagnosticIDs> {
// ... ...
private:
/// Information for uniquing and looking up custom diags.
std::unique_ptr<diag::CustomDiagInfo> CustomDiagInfo;
// ... ...
}
```
Each DiagnosticIDs has a `CustomDiagInfo` instance which is the custom diagnostic.
The builtin diagnostic is defined as `StaticDiagInfoRec` type in DiagnosticIDs.cpp.
DiagnosticIDs.cpp
https://clang.llvm.org/doxygen/DiagnosticIDs_8cpp_source.html
4. clang::DiagnosticConsumer
Abstract interface, implemented by clients of the front-end, which formats and prints fully processed diagnostics.
Diagnostic.h
https://clang.llvm.org/doxygen/Basic_2Diagnostic_8h_source.html
IgnoringDiagConsumer:
A diagnostic client that ignores all diagnostics.
ForwardingDiagnosticConsumer:
Diagnostic consumer that forwards diagnostics along to an
existing, already-initialized diagnostic consumer.
5. clang::CompilerInvocation
Helper class for holding the data necessary to invoke the compiler.
This class is designed to represent an abstract "invocation" of the compiler,
including data such as the include paths, the code generation options,
the warning flags, and so on.
CompilerInvocation.h
https://clang.llvm.org/doxygen/CompilerInvocation_8h_source.html#l00193
6. clang::driver::Driver
Encapsulate logic for constructing compilation processes from
a set of gcc-driver-like command line arguments.
7. clang::driver::Compilation
A set of tasks to perform for a single driver invocation.
https://clang.llvm.org/doxygen/classclang_1_1driver_1_1Compilation.html
*/
static clang::CompilerInvocation *newCompilerInvocation(
std::string &mainExecutable,
std::vector<std::string> &commandLine,
bool runClangChecker = false)
{
assert(!commandLine.empty() && "Command line must not be empty!");
commandLine[0] = mainExecutable;
std::vector<const char*> argv;
int start = 0, end = commandLine.size();
if (runClangChecker)
{
argv.push_back(commandLine[0].c_str());
argv.push_back("--analyze");
start = 1;
end -= 1;
}
for (int cmdIndex = start; cmdIndex != end; cmdIndex++)
{
if (commandLine[cmdIndex] != "-gmodules")
{
argv.push_back(commandLine[cmdIndex].c_str());
}
}
argv.push_back("-D__OCLINT__");
// create diagnostic engine
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> diagOpts =
new clang::DiagnosticOptions();
clang::DiagnosticsEngine diagnosticsEngine(
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()),
&*diagOpts,
new clang::DiagnosticConsumer());
// create driver
const char *const mainBinaryPath = argv[0];
//
const std::unique_ptr<clang::driver::Driver> driver(
newDriver(&diagnosticsEngine, mainBinaryPath));
driver->setCheckInputsExist(false);
// create compilation invocation
const std::unique_ptr<clang::driver::Compilation> compilation(
// Driver::BuildCompilation - Construct a compilation object for a command line argument vector.
driver->BuildCompilation(llvm::makeArrayRef(argv)));
auto cc1Args = getCC1Arguments(compilation.get());
return newInvocation(&diagnosticsEngine, *cc1Args);
}
/**
1. clang::CompilerInstance
Helper class for managing a single instance of the Clang compiler.
*/
static oclint::CompilerInstance *newCompilerInstance(clang::CompilerInvocation *compilerInvocation,
bool runClangChecker = false)
{
auto compilerInstance = new oclint::CompilerInstance();
auto invocation = std::make_shared<clang::CompilerInvocation>(*compilerInvocation);
// setInvocation() - Replace the current invocation
compilerInstance->setInvocation(std::move(invocation));
/**
void CompilerInstance::createDiagnostics(DiagnosticConsumer *Client = nullptr,
bool ShouldOwnClient = true)
Create the diagnostics engine using the invocation's diagnostic options
and replace any existing one with it.
Note that this routine also replaces the diagnostic client, allocating
one if one is not provided.
*/
compilerInstance->createDiagnostics(new DiagnosticDispatcher(runClangChecker));
if (!compilerInstance->hasDiagnostics())
{
throw oclint::GenericException("cannot create compiler diagnostics");
}
return compilerInstance;
}
#ifndef NDEBUG
static void printCompileCommandDebugInfo(
std::pair<std::string, clang::tooling::CompileCommand> &compileCommand,
std::vector<std::string> &commandLine)
{
LOG_DEBUG_LINE("-----------------------");
LOG_DEBUG("File: ");
LOG_DEBUG_LINE(compileCommand.first.c_str());
LOG_DEBUG("Directory: ");
LOG_DEBUG_LINE(compileCommand.second.Directory.c_str());
LOG_DEBUG("Command: ");
for (auto& flag : compileCommand.second.CommandLine)
{
LOG_DEBUG(flag.c_str());
LOG_DEBUG(" ");
}
LOG_DEBUG_LINE("");
LOG_DEBUG("Adjusted Command: ");
for (auto& cmd : commandLine)
{
LOG_DEBUG(cmd.c_str());
LOG_DEBUG(" ");
}
LOG_DEBUG_LINE("");
}
#endif
static std::vector<std::string> adjustArguments(std::vector<std::string> &unadjustedCmdLine,
const std::string& filename)
{
clang::tooling::ArgumentsAdjuster argAdjuster =
clang::tooling::combineAdjusters(
clang::tooling::getClangStripOutputAdjuster(),
clang::tooling::getClangSyntaxOnlyAdjuster());
return argAdjuster(unadjustedCmdLine, filename);
}
std::string stringReplace(std::string orig, std::string oldStr, std::string newStr)
{
std::string::size_type pos(orig.find(oldStr));
while (pos != std::string::npos)
{
orig.replace(pos, oldStr.length(), newStr);
pos = orig.find(oldStr, pos + newStr.length());
}
return orig;
}
static void constructCompilers(std::vector<oclint::CompilerInstance *> &compilers,
CompileCommandPairs &compileCommands,
std::string &mainExecutable)
{
for (auto &compileCommand : compileCommands)
{
// Adjust the arguments.
std::vector<std::string> adjustedCmdLine =
adjustArguments(compileCommand.second.CommandLine, compileCommand.first);
#ifndef NDEBUG
printCompileCommandDebugInfo(compileCommand, adjustedCmdLine);
#endif
LOG_VERBOSE("Compiling ");
LOG_VERBOSE(compileCommand.first.c_str());
LOG_VERBOSE_LINE(" ...");
std::string targetDir = stringReplace(compileCommand.second.Directory, "\\ ", " ");
if(chdir(targetDir.c_str()))
{
throw oclint::GenericException("Cannot change dictionary into \"" +
targetDir + "\", "
"please make sure the directory exists and you have permission to access!");
}
// CompilerInvocation represent an abstract "invocation" of the compiler
clang::CompilerInvocation *compilerInvocation =
newCompilerInvocation(mainExecutable, adjustedCmdLine);
oclint::CompilerInstance *compiler = newCompilerInstance(compilerInvocation);
/*
oclint::CompilerInstance::start()
*/
compiler->start();
if (!compiler->getDiagnostics().hasErrorOccurred() && compiler->hasASTContext())
{
LOG_VERBOSE(" - Success");
compilers.push_back(compiler);
}
else
{
LOG_VERBOSE(" - Failed");
}
LOG_VERBOSE_LINE("");
}
}
static void invokeClangStaticAnalyzer(
CompileCommandPairs &compileCommands,
std::string &mainExecutable)
{
for (auto &compileCommand : compileCommands)
{
LOG_VERBOSE("Clang Static Analyzer ");
LOG_VERBOSE(compileCommand.first.c_str());
std::string targetDir = stringReplace(compileCommand.second.Directory, "\\ ", " ");
if (chdir(targetDir.c_str()))
{
throw oclint::GenericException("Cannot change dictionary into \"" +
targetDir + "\", "
"please make sure the directory exists and you have permission to access!");
}
// Adjust the arguments.
std::vector<std::string> adjustedArguments =
adjustArguments(compileCommand.second.CommandLine, compileCommand.first);
//
clang::CompilerInvocation *compilerInvocation =
newCompilerInvocation(mainExecutable, adjustedArguments, true);
oclint::CompilerInstance *compiler = newCompilerInstance(compilerInvocation, true);
compiler->start();
if (!compiler->getDiagnostics().hasErrorOccurred() && compiler->hasASTContext())
{
LOG_VERBOSE(" - Done");
}
else
{
LOG_VERBOSE(" - Finished with Failure");
}
compiler->end();
LOG_VERBOSE_LINE("");
}
}
static void invoke(CompileCommandPairs &compileCommands,
std::string &mainExecutable, oclint::Analyzer &analyzer)
{
std::vector<oclint::CompilerInstance *> compilers;
constructCompilers(compilers, compileCommands, mainExecutable);
// collect a collection of AST contexts
std::vector<clang::ASTContext *> localContexts;
for (auto compiler : compilers)
{
localContexts.push_back(&compiler->getASTContext());
}
// use the analyzer to do the actual analysis
analyzer.preprocess(localContexts);
analyzer.analyze(localContexts);
analyzer.postprocess(localContexts);
// send out the signals to release or simply leak resources
for (size_t compilerIndex = 0; compilerIndex != compilers.size(); ++compilerIndex)
{
compilers.at(compilerIndex)->end();
delete compilers.at(compilerIndex);
}
}
void Driver::run(const clang::tooling::CompilationDatabase &compilationDatabase,
llvm::ArrayRef<std::string> sourcePaths, oclint::Analyzer &analyzer)
{
CompileCommandPairs compileCommands;
constructCompileCommands(compileCommands, compilationDatabase, sourcePaths);
static int staticSymbol;
std::string mainExecutable = llvm::sys::fs::getMainExecutable("oclint", &staticSymbol);
if (option::enableGlobalAnalysis())
{
invoke(compileCommands, mainExecutable, analyzer);
}
else
{
for (auto &compileCommand : compileCommands)
{
CompileCommandPairs oneCompileCommand { compileCommand };
invoke(oneCompileCommand, mainExecutable, analyzer);
}
}
if (option::enableClangChecker())
{
invokeClangStaticAnalyzer(compileCommands, mainExecutable);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment