|
|
|
/*
|
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <functional>
|
|
|
|
#include <memory>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string>
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
#include <clang/AST/ASTConsumer.h>
|
|
|
|
#include <clang/Frontend/CompilerInstance.h>
|
|
|
|
#include <clang/Frontend/FrontendAction.h>
|
|
|
|
#include <clang/Frontend/FrontendActions.h>
|
|
|
|
#include <clang/Lex/Preprocessor.h>
|
|
|
|
#include <clang/Tooling/Tooling.h>
|
|
|
|
|
|
|
|
#include "FileUtils.h"
|
|
|
|
|
|
|
|
namespace ASTPluginLib {
|
|
|
|
|
|
|
|
struct PluginASTOptionsBase {
|
|
|
|
// source file being parsed
|
|
|
|
clang::FrontendInputFile inputFile;
|
|
|
|
// output file for the plugin
|
|
|
|
std::string outputFile;
|
|
|
|
// object file produced by the usual frontend (possibly empty)
|
|
|
|
std::string objectFile;
|
|
|
|
|
|
|
|
/* Will contain the current directory if PREPEND_CURRENT_DIR was specified.
|
|
|
|
* The intention is to make file paths in the AST absolute if needed.
|
|
|
|
*/
|
|
|
|
std::string basePath;
|
|
|
|
|
|
|
|
/* Configure a second pass on file paths to make them relative to the repo
|
|
|
|
* root. */
|
|
|
|
std::string repoRoot;
|
|
|
|
/* Configure a third pass on (absolute) file paths to blank the system root:
|
|
|
|
* /path/to/sysroot/usr/lib/foo.h --> /usr/lib/foo.h
|
|
|
|
*/
|
|
|
|
std::string iSysRoot;
|
|
|
|
/* Configure a fourth pass on (absolute) file paths to detect siblings to
|
|
|
|
* the repo root. If the repo root is /some/path, /some/other_path will be
|
|
|
|
* rewritten ../other_path
|
|
|
|
*/
|
|
|
|
bool allowSiblingsToRepoRoot = false;
|
|
|
|
/* Whether file paths that could not be normalized by any of the rules above
|
|
|
|
* should be kept or blanked.
|
|
|
|
*/
|
|
|
|
bool keepExternalPaths = false;
|
|
|
|
/* Resolve symlinks to their real path. */
|
|
|
|
bool resolveSymlinks = false;
|
|
|
|
/* do not emit string literals larger than this size */
|
|
|
|
unsigned long maxStringSize = 65535;
|
|
|
|
|
|
|
|
typedef std::unordered_map<std::string, std::string> argmap_t;
|
|
|
|
|
|
|
|
static argmap_t makeMap(const std::vector<std::string> &args);
|
|
|
|
|
|
|
|
private:
|
|
|
|
/* cache for normalizeSourcePath */
|
|
|
|
std::unique_ptr<std::unordered_map<const char *, std::string>>
|
|
|
|
normalizationCache;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
static const std::string envPrefix;
|
|
|
|
|
|
|
|
static bool loadString(const argmap_t &map,
|
|
|
|
const char *key,
|
|
|
|
std::string &val);
|
|
|
|
|
|
|
|
static bool loadBool(const argmap_t &map, const char *key, bool &val);
|
|
|
|
|
|
|
|
static bool loadInt(const argmap_t &map, const char *key, long &val);
|
|
|
|
|
|
|
|
static bool loadUnsignedInt(const argmap_t &map,
|
|
|
|
const char *key,
|
|
|
|
unsigned long &val);
|
|
|
|
|
|
|
|
public:
|
|
|
|
PluginASTOptionsBase() {
|
|
|
|
normalizationCache.reset(
|
|
|
|
new std::unordered_map<const char *, std::string>());
|
|
|
|
};
|
|
|
|
|
|
|
|
void loadValuesFromEnvAndMap(const argmap_t map);
|
|
|
|
|
|
|
|
// This should be called after outputFile has been set, so as to finalize
|
|
|
|
// the output file in case a pattern "%.bla" was given.
|
|
|
|
void setObjectFile(const std::string &path);
|
|
|
|
|
|
|
|
const std::string &normalizeSourcePath(const char *path) const;
|
|
|
|
const std::string &normalizeSourcePath(llvm::StringRef path) const;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct EmptyPreprocessorHandlerData {};
|
|
|
|
|
|
|
|
struct EmptyPreprocessorHandler : public clang::PPCallbacks {
|
|
|
|
EmptyPreprocessorHandler(
|
|
|
|
clang::SourceManager &SM,
|
|
|
|
std::shared_ptr<PluginASTOptionsBase> options,
|
|
|
|
std::shared_ptr<EmptyPreprocessorHandlerData> sharedData) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class PluginASTOptions = PluginASTOptionsBase,
|
|
|
|
class PreprocessorHandler = EmptyPreprocessorHandler,
|
|
|
|
class PreprocessorHandlerData = EmptyPreprocessorHandlerData>
|
|
|
|
class SimplePluginASTActionBase : public clang::PluginASTAction {
|
|
|
|
protected:
|
|
|
|
std::shared_ptr<PluginASTOptions> options;
|
|
|
|
std::shared_ptr<PreprocessorHandlerData> sharedData;
|
|
|
|
|
|
|
|
void ExecuteAction() override {
|
|
|
|
auto &preprocessor = getCompilerInstance().getPreprocessor();
|
|
|
|
preprocessor.addPPCallbacks(llvm::make_unique<PreprocessorHandler>(
|
|
|
|
preprocessor.getSourceManager(), options, sharedData));
|
|
|
|
clang::PluginASTAction::ExecuteAction();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when FrontendPluginRegistry is used.
|
|
|
|
bool ParseArgs(const clang::CompilerInstance &CI,
|
|
|
|
const std::vector<std::string> &args_) override {
|
|
|
|
std::vector<std::string> args = args_;
|
|
|
|
if (args.size() > 0) {
|
|
|
|
options->outputFile = args[0];
|
|
|
|
args.erase(args.begin());
|
|
|
|
}
|
|
|
|
options->loadValuesFromEnvAndMap(PluginASTOptions::makeMap(args));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
SimplePluginASTActionBase() {
|
|
|
|
// These data structures will be shared between PreprocessorHandler
|
|
|
|
// and ASTConsumer (the relative lifetimes of which are unknown).
|
|
|
|
// During the AST traversal, it is expected that `options` is only read
|
|
|
|
// and `sharedData` is only written.
|
|
|
|
options = std::make_shared<PluginASTOptions>();
|
|
|
|
sharedData = std::make_shared<PreprocessorHandlerData>();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alternate constructor to pass an optional sequence "KEY=VALUE,.."
|
|
|
|
// expected to be use with SimpleFrontendActionFactory below.
|
|
|
|
explicit SimplePluginASTActionBase(const std::vector<std::string> &args)
|
|
|
|
: SimplePluginASTActionBase() {
|
|
|
|
options->loadValuesFromEnvAndMap(PluginASTOptions::makeMap(args));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SetFileOptions(clang::CompilerInstance &CI,
|
|
|
|
llvm::StringRef inputFilename) {
|
|
|
|
// When running clang tool on more than one source file, CreateASTConsumer
|
|
|
|
// will be ran for each of them separately. Hence, Inputs.size() = 1.
|
|
|
|
clang::FrontendInputFile inputFile = CI.getFrontendOpts().Inputs[0];
|
|
|
|
|
|
|
|
switch (inputFile.getKind().getLanguage()) {
|
|
|
|
case clang::InputKind::Unknown:
|
|
|
|
case clang::InputKind::Asm:
|
|
|
|
case clang::InputKind::LLVM_IR:
|
|
|
|
// We can't do anything with these - they may trigger errors when
|
|
|
|
// running clang frontend
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
// run the consumer for IK_AST and all others
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
options->inputFile = inputFile;
|
|
|
|
options->setObjectFile(CI.getFrontendOpts().OutputFile);
|
|
|
|
// success
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class SimpleASTAction>
|
|
|
|
class SimpleFrontendActionFactory
|
|
|
|
: public clang::tooling::FrontendActionFactory {
|
|
|
|
std::vector<std::string> args_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit SimpleFrontendActionFactory(std::vector<std::string> args)
|
|
|
|
: args_(args) {}
|
|
|
|
|
|
|
|
clang::FrontendAction *create() override {
|
|
|
|
return new SimpleASTAction(args_);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class ASTConsumer,
|
|
|
|
bool Binary = 0,
|
|
|
|
bool RemoveFileOnSignal = 1,
|
|
|
|
bool UseTemporary = 1,
|
|
|
|
bool CreateMissingDirectories = 0>
|
|
|
|
class SimplePluginASTAction
|
|
|
|
: public SimplePluginASTActionBase<
|
|
|
|
typename ASTConsumer::ASTConsumerOptions,
|
|
|
|
typename ASTConsumer::PreprocessorHandler,
|
|
|
|
typename ASTConsumer::PreprocessorHandlerData> {
|
|
|
|
using Parent =
|
|
|
|
SimplePluginASTActionBase<typename ASTConsumer::ASTConsumerOptions,
|
|
|
|
typename ASTConsumer::PreprocessorHandler,
|
|
|
|
typename ASTConsumer::PreprocessorHandlerData>;
|
|
|
|
|
|
|
|
public:
|
|
|
|
SimplePluginASTAction() {}
|
|
|
|
|
|
|
|
explicit SimplePluginASTAction(const std::vector<std::string> &args)
|
|
|
|
: Parent(args) {}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
|
|
|
|
clang::CompilerInstance &CI, llvm::StringRef inputFilename) {
|
|
|
|
if (!Parent::SetFileOptions(CI, inputFilename)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
std::unique_ptr<llvm::raw_ostream> OS =
|
|
|
|
CI.createOutputFile(Parent::options->outputFile,
|
|
|
|
Binary,
|
|
|
|
RemoveFileOnSignal,
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
UseTemporary,
|
|
|
|
CreateMissingDirectories);
|
|
|
|
if (!OS) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::unique_ptr<clang::ASTConsumer>(new ASTConsumer(
|
|
|
|
CI, Parent::options, Parent::sharedData, std::move(OS)));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class ASTConsumer>
|
|
|
|
class NoOpenSimplePluginASTAction
|
|
|
|
: public SimplePluginASTActionBase<
|
|
|
|
typename ASTConsumer::ASTConsumerOptions,
|
|
|
|
typename ASTConsumer::PreprocessorHandler,
|
|
|
|
typename ASTConsumer::PreprocessorHandlerData> {
|
|
|
|
using Parent =
|
|
|
|
SimplePluginASTActionBase<typename ASTConsumer::ASTConsumerOptions,
|
|
|
|
typename ASTConsumer::PreprocessorHandler,
|
|
|
|
typename ASTConsumer::PreprocessorHandlerData>;
|
|
|
|
|
|
|
|
public:
|
|
|
|
NoOpenSimplePluginASTAction() {}
|
|
|
|
|
|
|
|
explicit NoOpenSimplePluginASTAction(const std::vector<std::string> &args)
|
|
|
|
: Parent(args) {}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
|
|
|
|
clang::CompilerInstance &CI, llvm::StringRef inputFilename) {
|
|
|
|
if (!Parent::SetFileOptions(CI, inputFilename)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
std::unique_ptr<std::string> outputFile = std::unique_ptr<std::string>(
|
|
|
|
new std::string(Parent::options->outputFile));
|
|
|
|
return std::unique_ptr<clang::ASTConsumer>(new ASTConsumer(
|
|
|
|
CI, Parent::options, Parent::sharedData, std::move(outputFile)));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace ASTPluginLib
|