cling/lib/MetaProcessor/MetaSema.cpp
2023-12-13 13:29:06 +01:00

498 lines
20 KiB
C++

//------------------------------------------------------------------------------
// CLING - the C++ LLVM-based InterpreterG :)
// author: Vassil Vassilev <vvasilev@cern.ch>
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------
#include "cling/Interpreter/DynamicLibraryManager.h"
#include "cling/Interpreter/Interpreter.h"
#include "cling/Interpreter/Transaction.h"
#include "cling/Interpreter/Value.h"
#include "cling/MetaProcessor/Display.h"
#include "cling/MetaProcessor/MetaProcessor.h"
#include "cling/MetaProcessor/MetaSema.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Sema/Sema.h"
#include "clang/Serialization/ASTReader.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/Support/Casting.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Basic/SourceManager.h"
#include <algorithm>
#include <cctype>
#include <cstdlib>
namespace {
///\brief Make a valid C++ identifier, replacing illegal characters in `S'
/// by '_'. It does not take into account valid Unicode ranges.
///\param[in] S - std::string& that (may) contain chars that cannot be part
/// of an identifier
///
static std::string makeValidCXXIdentifier(const std::string& S) {
std::string ret(S);
if (std::isdigit(ret[0]))
ret.insert(ret.begin(), '_');
std::replace_if(ret.begin(), ret.end(),
[] (char c) { return (c != '_')
&& !std::isdigit(c) && !std::isalpha(c); },
'_');
return ret;
}
}
namespace cling {
MetaSema::MetaSema(Interpreter& interp, MetaProcessor& meta)
: m_Interpreter(interp), m_MetaProcessor(meta), m_IsQuitRequested(false) { }
MetaSema::ActionResult MetaSema::actOnLCommand(llvm::StringRef file,
Transaction** transaction /*= 0*/) {
if (file.empty()) {
m_Interpreter.DumpDynamicLibraryInfo();
return AR_Success;
}
if (actOnUCommand(file) != AR_Success)
return AR_Failure;
// In case of libraries we get .L lib.so, which might automatically pull in
// decls (from header files). Thus we want to take the restore point before
// loading of the file and revert exclusively if needed.
const Transaction* unloadPoint = m_Interpreter.getLastTransaction();
// fprintf(stderr,"DEBUG: Load for %s unloadPoint is %p\n", file.str().c_str(), unloadPoint);
std::string pathname(m_Interpreter.lookupFileOrLibrary(file));
if (pathname.empty())
pathname = file.str();
if (m_Interpreter.loadFile(pathname, /*allowSharedLib=*/true, transaction)
== Interpreter::kSuccess) {
registerUnloadPoint(unloadPoint, pathname);
return AR_Success;
}
return AR_Failure;
}
MetaSema::ActionResult MetaSema::actOnOCommand(int optLevel) {
if (optLevel >= 0 && optLevel < 4) {
m_Interpreter.setDefaultOptLevel(optLevel);
return AR_Success;
}
m_MetaProcessor.getOuts()
<< "Refusing to set invalid cling optimization level " << optLevel << '\n';
return AR_Failure;
}
void MetaSema::actOnOCommand() {
m_MetaProcessor.getOuts() << "Current cling optimization level: "
<< m_Interpreter.getDefaultOptLevel() << '\n';
}
MetaSema::ActionResult MetaSema::actOnTCommand(llvm::StringRef inputFile,
llvm::StringRef outputFile) {
m_Interpreter.GenerateAutoLoadingMap(inputFile, outputFile);
return AR_Success;
}
MetaSema::ActionResult MetaSema::actOnRedirectCommand(llvm::StringRef file,
MetaProcessor::RedirectionScope stream, bool append) {
m_MetaProcessor.setStdStream(file, stream, append);
return AR_Success;
}
void MetaSema::actOnComment(llvm::StringRef comment) const {
// Some of the comments are meaningful for the cling::Interpreter
m_Interpreter.declare(comment.str());
}
MetaSema::ActionResult MetaSema::actOnxCommand(llvm::StringRef file,
llvm::StringRef args,
Value* result) {
assert(!args.empty() && "Arguments must be provided (at least \"()\"");
enum CallResult { CR_NoSuchDecl, CR_Failure, CR_Success };
auto tryCallFunction = [this] (cling::Transaction* T, std::string func,
llvm::StringRef args, Value* ret) {
if (!T->containsNamedDecl(func))
return CR_NoSuchDecl;
std::string S;
llvm::raw_string_ostream OS(S);
OS << func << args << " /* .x tries to invoke function `" << func << "` */";
// Transaction `T' might have a different OptLevel; use that.
struct OptLevelRAII {
OptLevelRAII(Interpreter& I, int L)
: m_Interp(I), m_OptLevel(I.getDefaultOptLevel()) { I.setDefaultOptLevel(L); }
~OptLevelRAII() { m_Interp.setDefaultOptLevel(m_OptLevel); }
Interpreter& m_Interp;
int m_OptLevel;
} RAII(m_Interpreter, T->getCompilationOpts().OptLevel);
return (m_Interpreter.echo(OS.str(), ret) == Interpreter::kSuccess)
? CR_Success : CR_Failure;
};
cling::Transaction* T = nullptr;
if (actOnLCommand(file, &T) != AR_Success || !T)
return AR_Failure;
// First, try function named after `file`; add any alternatives below.
const std::string tryCallThese[] = {
makeValidCXXIdentifier(llvm::sys::path::stem(file).str()),
// FIXME: this provides an entry point that is independent from the macro
// filename (and still works if file is renamed); should we enable this?
//"__main__",
};
bool noAlternativeFound = 0;
for (auto &func : tryCallThese) {
CallResult CR = tryCallFunction(T, func, args, result);
if (CR == CR_Success)
return AR_Success;
noAlternativeFound |= (CR == CR_NoSuchDecl);
}
if (noAlternativeFound) {
static constexpr char msg[] = "Failed to call `%0%1` to execute the macro.\n"
"Add this function or rename the macro. Falling back to `.L`.";
clang::DiagnosticsEngine& Diags = m_Interpreter.getDiagnostics();
unsigned diagID = Diags.getCustomDiagID(clang::DiagnosticsEngine::Level::Warning, msg);
//FIXME: Figure out how to pass in proper source locations, which we can
// use with -verify.
Diags.Report(clang::SourceLocation(), diagID) << tryCallThese[0] << args.str();
return AR_Success; //FIXME: should this be AR_Failure?
}
return AR_Failure;
}
void MetaSema::actOnqCommand() {
m_IsQuitRequested = true;
}
void MetaSema::actOnAtCommand() {
m_MetaProcessor.cancelContinuation();
}
MetaSema::ActionResult MetaSema::actOnUndoCommand(unsigned N/*=1*/) {
m_Interpreter.unload(N);
return AR_Success;
}
MetaSema::ActionResult MetaSema::actOnUCommand(llvm::StringRef file) {
//FIXME: search for the transaction, i.e. verify that it has not already
// been unloaded, e.g. through `.undo X'.
auto interpreterHasTransaction = [] (const Interpreter& Interp,
const Transaction* T) {
for (const Transaction* I = Interp.getFirstTransaction();
I != nullptr; I = I->getNext())
if (I == T)
return true;
return false;
};
clang::FileManager& FM = m_Interpreter.getSema().getSourceManager().getFileManager();
std::string pathname(m_Interpreter.lookupFileOrLibrary(file));
const auto FE = FM.getFile(pathname, /*OpenFile=*/false,
/*CacheFailure=*/false);
if (!FE)
return AR_Failure;
auto TI = m_FEToTransaction.find(*FE);
if (TI == m_FEToTransaction.end())
return AR_Success;
const Transaction* unloadPoint = (*TI).second;
if (interpreterHasTransaction(m_Interpreter, unloadPoint)) {
// Revert all the transactions that came after `unloadPoint'.
while (m_Interpreter.getLastTransaction() != unloadPoint) {
if (const auto ThisFE = m_TransactionToFE[m_Interpreter.getLastTransaction()]) {
auto I = m_FEToTransaction.find(ThisFE);
if (I != m_FEToTransaction.end())
m_FEToTransaction.erase(I);
}
m_Interpreter.unload(/*numberOfTransactions=*/1);
}
DynamicLibraryManager* DLM = m_Interpreter.getDynamicLibraryManager();
if (DLM->isLibraryLoaded(pathname))
DLM->unloadLibrary(pathname);
} else {
m_MetaProcessor.getOuts() << "!!!ERROR: Transaction for file: " << file
<< " has already been unloaded\n";
}
m_FEToTransaction.erase(TI);
return AR_Success;
}
void MetaSema::actOnICommand(llvm::StringRef path) const {
if (path.empty())
m_Interpreter.DumpIncludePath();
else
m_Interpreter.AddIncludePath(path.str());
}
void MetaSema::actOnrawInputCommand(SwitchMode mode/* = kToggle*/) const {
if (mode == kToggle) {
bool flag = !m_Interpreter.isRawInputEnabled();
m_Interpreter.enableRawInput(flag);
m_MetaProcessor.getOuts() << (flag ? "U" :"Not u") << "sing raw input\n";
}
else
m_Interpreter.enableRawInput(mode);
}
void MetaSema::actOndebugCommand(std::optional<int> mode) const {
constexpr clang::codegenoptions::DebugInfoKind DebugInfo[] = {
clang::codegenoptions::NoDebugInfo,
clang::codegenoptions::LocTrackingOnly,
clang::codegenoptions::DebugLineTablesOnly,
clang::codegenoptions::LimitedDebugInfo,
clang::codegenoptions::FullDebugInfo
};
constexpr int N = (int)std::extent<decltype(DebugInfo)>::value;
clang::CodeGenOptions& CGO = m_Interpreter.getCI()->getCodeGenOpts();
if (!mode) {
bool flag = (CGO.getDebugInfo() == clang::codegenoptions::NoDebugInfo);
if (flag)
CGO.setDebugInfo(clang::codegenoptions::LimitedDebugInfo);
else
CGO.setDebugInfo(clang::codegenoptions::NoDebugInfo);
m_MetaProcessor.getOuts() << (flag ? "G" : "Not g") << "enerating debug symbols\n";
} else {
mode = (*mode < 0) ? 0 : ((*mode >= N) ? N - 1 : *mode);
CGO.setDebugInfo(DebugInfo[*mode]);
if (!*mode) {
m_MetaProcessor.getOuts() << "Not generating debug symbols\n";
} else {
m_MetaProcessor.getOuts() << "Generating debug symbols level "
<< *mode << '\n';
}
}
}
void MetaSema::actOnprintDebugCommand(SwitchMode mode/* = kToggle*/) const {
if (mode == kToggle) {
bool flag = !m_Interpreter.isPrintingDebug();
m_Interpreter.enablePrintDebug(flag);
m_MetaProcessor.getOuts() << (flag ? "P" : "Not p") << "rinting Debug\n";
}
else
m_Interpreter.enablePrintDebug(mode);
}
void MetaSema::actOnstoreStateCommand(llvm::StringRef name) const {
m_Interpreter.storeInterpreterState(name.str());
}
void MetaSema::actOncompareStateCommand(llvm::StringRef name) const {
m_Interpreter.compareInterpreterState(name.str());
}
void MetaSema::actOnstatsCommand(llvm::StringRef name,
llvm::StringRef args) const {
m_Interpreter.dump(name, args);
}
void MetaSema::actOndynamicExtensionsCommand(SwitchMode mode/* = kToggle*/) const {
if (mode == kToggle) {
bool flag = !m_Interpreter.isDynamicLookupEnabled();
m_Interpreter.enableDynamicLookup(flag);
m_MetaProcessor.getOuts()
<< (flag ? "U" : "Not u") << "sing dynamic extensions\n";
}
else
m_Interpreter.enableDynamicLookup(mode);
}
void MetaSema::actOnhelpCommand() const {
std::string& metaString = m_Interpreter.getOptions().MetaString;
llvm::raw_ostream& outs = m_MetaProcessor.getOuts();
outs << "\n Cling (C/C++ interpreter) meta commands usage\n"
" All commands must be preceded by a '" << metaString << "', except\n"
" for the evaluation statement { }\n"
" ==============================================================================\n"
" Syntax: " << metaString << "Command [arg0 arg1 ... argN]\n"
"\n"
" " << metaString << "L <filename>\t\t- Load the given file or library\n"
"\n"
" " << metaString << "(x|X) <filename>[(args)]\t- Same as .L and runs a function with"
"\n\t\t\t\t signature: ret_type filename(args)\n"
"\n"
" " << metaString << "> <filename>\t\t- Redirect command to a given file\n"
" '>' or '1>'\t\t- Redirects the stdout stream only\n"
" '2>'\t\t\t- Redirects the stderr stream only\n"
" '&>' (or '2>&1')\t\t- Redirects both stdout and stderr\n"
" '>>'\t\t\t- Appends to the given file\n"
"\n"
" " << metaString << "undo [n]\t\t\t- Unloads the last 'n' inputs lines\n"
"\n"
" " << metaString << "U <filename>\t\t- Unloads the given file\n"
"\n"
" " << metaString << "(I|include) [path]\t\t- Shows all include paths. If a path is given,"
"\n\t\t\t\t adds the path to the include paths.\n"
"\n"
" " << metaString << "O <level>\t\t\t- Sets the optimization level (0-3)"
"\n\t\t\t\t If no level is given, prints the current setting.\n"
"\n"
" " << metaString << "class <name>\t\t- Prints out class <name> in a CINT-like style (one-level).\n"
"\t\t\t\t If no name is given, prints out list of all classes.\n"
"\n"
" " << metaString << "Class <name>\t\t- Prints out class <name> in a CINT-like style (all-levels).\n"
"\t\t\t\t If no name is given, prints out list of all classes.\n"
"\n"
" " << metaString << "namespace\t\t\t- Prints list of all known namespaces\n"
"\n"
" " << metaString << "typedef <name>\t\t- Prints out typedef <name> in a CINT-like style\n"
"\t\t\t\t If no name is given, prints out list of all typedefs.\n"
"\n"
" " << metaString << "files\t\t\t- Prints names of all included (parsed) files\n"
"\n"
" " << metaString << "fileEx\t\t\t- Prints out included (parsed) file statistics\n"
"\t\t\t\t as well as a list of their names\n"
"\n"
" " << metaString << "g <var>\t\t\t- Prints out information about global variable"
"\n\t\t\t\t 'var' - if no name is given, print them all\n"
"\n"
" " << metaString << "@ \t\t\t\t- Cancels and ignores the multiline input\n"
"\n"
" " << metaString << "rawInput [0|1]\t\t- Toggle wrapping and printing the"
"\n\t\t\t\t execution results of the input\n"
"\n"
" " << metaString << "dynamicExtensions [0|1]\t- Toggles the use of the dynamic scopes"
"\n\t\t\t\t and the late binding\n"
"\n"
" " << metaString << "debug <level>\t\t- Generates debug symbols (level is optional, 0 to disable)\n"
"\n"
" " << metaString << "printDebug [0|1]\t\t- Toggles the printing of input's corresponding"
"\n\t\t\t\t state changes\n"
"\n"
" " << metaString << "storeState <filename>\t- Store the interpreter's state to a given file\n"
"\n"
" " << metaString << "compareState <filename>\t- Compare the interpreter's state with the one"
"\n\t\t\t\t saved in a given file\n"
"\n"
" " << metaString << "stats [name]\t\t- Show stats for internal data structures\n"
"\t\t\t\t 'ast' abstract syntax tree stats\n"
"\t\t\t\t 'asttree [filter]' abstract syntax tree layout\n"
"\t\t\t\t 'decl' dump ast declarations\n"
"\t\t\t\t 'undo' show undo stack\n"
"\n"
" " << metaString << "T <filePath> <comment>\t- Generate autoload map\n"
"\n"
" " << metaString << "trace <repr> <id>\t\t- Dump trace of requested respresentation\n"
"\t\t\t\t (see " << metaString << "stats arguments for <repr>)\n"
"\n"
" " << metaString << "help\t\t\t- Shows this information (also " << metaString << "?)\n"
"\n"
" " << metaString << "q\t\t\t\t- Exit the program\n"
"\n";
}
void MetaSema::actOnfileExCommand() const {
const clang::SourceManager& SM = m_Interpreter.getCI()->getSourceManager();
SM.getFileManager().PrintStats();
m_MetaProcessor.getOuts() << "\n***\n\n";
for (clang::SourceManager::fileinfo_iterator I = SM.fileinfo_begin(),
E = SM.fileinfo_end(); I != E; ++I) {
m_MetaProcessor.getOuts() << (*I).first->getName();
m_MetaProcessor.getOuts() << "\n";
}
#if 0
// Only available in clang's trunk:
clang::ASTReader* Reader = m_Interpreter.getCI()->getASTReader();
const clang::serialization::ASTReader& ASTRead
= Reader->getASTReader();
for (clang::serialization::ASTReader::ModuleConstIterator I
= ASTRead.begin(), E = ASTRead.end(); I != E; ++I) {
typedef
std::vector<llvm::PointerIntPair<const clang::FileEntry*, 1, bool> >
InputFiles_t;
const InputFiles_t& InputFiles = (*I)->InputFilesLoaded;
for (InputFiles_t::const_iterator IFI = InputFiles.begin(),
IFE = InputFiles.end(); IFI != IFE; ++IFI) {
m_MetaProcessor.getOuts() << IFI->getPointer()->getName();
m_MetaProcessor.getOuts() << "\n";
}
}
#endif
}
void MetaSema::actOnfilesCommand() const {
m_Interpreter.printIncludedFiles(m_MetaProcessor.getOuts());
}
void MetaSema::actOnClassCommand(llvm::StringRef className, bool verbose) const {
DisplayClass(m_MetaProcessor.getOuts(),
&m_Interpreter, className.str().c_str(), verbose);
}
void MetaSema::actOnNamespaceCommand() const {
DisplayNamespaces(m_MetaProcessor.getOuts(), &m_Interpreter);
}
void MetaSema::actOngCommand(llvm::StringRef varName) const {
if (varName.empty())
DisplayGlobals(m_MetaProcessor.getOuts(), &m_Interpreter);
else
DisplayGlobal(m_MetaProcessor.getOuts(),
&m_Interpreter, varName.str().c_str());
}
void MetaSema::actOnTypedefCommand(llvm::StringRef typedefName) const {
if (typedefName.empty())
DisplayTypedefs(m_MetaProcessor.getOuts(), &m_Interpreter);
else
DisplayTypedef(m_MetaProcessor.getOuts(),
&m_Interpreter, typedefName.str().c_str());
}
MetaSema::ActionResult
MetaSema::actOnShellCommand(llvm::StringRef commandLine,
Value* result) const {
llvm::StringRef trimmed(commandLine.trim(" \t\n\v\f\r"));
if (!trimmed.empty()) {
int exitStatus = std::system(trimmed.str().c_str());
// Build the result
clang::ASTContext& Ctx = m_Interpreter.getCI()->getASTContext();
if (result) {
*result = Value(Ctx.IntTy, m_Interpreter);
result->setLongLong(exitStatus); // FIXME: This should assert.
}
return (exitStatus == 0) ? AR_Success : AR_Failure;
}
if (result)
*result = Value();
return AR_Failure; //FIXME: should this be success or failure?
}
void MetaSema::registerUnloadPoint(const Transaction* unloadPoint,
llvm::StringRef filename) {
std::string pathname(m_Interpreter.lookupFileOrLibrary(filename));
if (pathname.empty())
pathname = filename.str();
clang::FileManager& FM = m_Interpreter.getSema().getSourceManager().getFileManager();
auto FE = FM.getFile(pathname, /*OpenFile=*/false, /*CacheFailure=*/false);
if (!FE)
return;
if (*FE && !m_FEToTransaction[*FE]) {
m_FEToTransaction[*FE] = unloadPoint;
m_TransactionToFE[unloadPoint] = *FE;
}
}
} // end namespace cling