cling/lib/MetaProcessor/MetaProcessor.cpp
Javier Lopez-Gomez 2e500a3fb8 Add Interpreter::m_InputFlags
This member provides additional information about the context in which parsing
occurs.
In particular, the `kInputFromFile` and `kIFFLineByLine` flags allow to tell
whether a sequence of calls to `Interpreter::process()` is issued from
`MetaProcessor::readInputFromFile()` for the contents of an external file.
2022-02-11 01:59:05 +01:00

539 lines
18 KiB
C++

//------------------------------------------------------------------------------
// CLING - the C++ LLVM-based InterpreterG :)
// author: Axel Naumann <axel@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/MetaProcessor/MetaProcessor.h"
#include "cling/MetaProcessor/InputValidator.h"
#include "cling/MetaProcessor/MetaParser.h"
#include "cling/MetaProcessor/MetaSema.h"
#include "cling/MetaProcessor/Display.h"
#include "cling/Interpreter/Interpreter.h"
#include "cling/Interpreter/Value.h"
#include "cling/Utils/Output.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Support/Path.h"
#include <fcntl.h>
#include <fstream>
#include <cstdlib>
#include <cctype>
#include <sstream>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#else
#include <io.h>
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#endif
using namespace clang;
namespace cling {
class MetaProcessor::RedirectOutput {
static int dupOnce(int Fd, int& Bak) {
// Flush now or can drop the buffer when dup2 is called with Fd later.
// This seems only neccessary when piping stdout or stderr, but do it
// for ttys to avoid over complicated code for minimal benefit.
::fflush(Fd==STDOUT_FILENO ? stdout : stderr);
if (Bak == kInvalidFD)
Bak = ::dup(Fd);
return Bak;
}
struct Redirect {
int FD;
MetaProcessor::RedirectionScope Scope;
bool Close;
Redirect(std::string file, bool append, RedirectionScope S, int* Baks) :
FD(-1), Scope(S), Close(false) {
if (S & kSTDSTRM) {
// Remove the flag from Scope, we don't need it anymore
Scope = RedirectionScope(Scope & ~kSTDSTRM);
if (file == "&1")
FD = dupOnce(STDOUT_FILENO, Baks[0]);
else if (file == "&2")
FD = dupOnce(STDERR_FILENO, Baks[1]);
// Close = false; Parent manages lifetime
if (FD != -1)
return;
llvm_unreachable("kSTDSTRM passed for unknown stream");
}
const int Perm = 0644;
#ifdef _WIN32
const int Mode = _O_CREAT | _O_WRONLY | (append ? _O_APPEND : _O_TRUNC);
FD = ::_open(file.c_str(), Mode, Perm);
#else
const int Mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC);
FD = ::open(file.c_str(), Mode, Perm);
#endif
if (FD == -1) {
::perror("Redirect::open");
return;
}
Close = true;
if (append)
::lseek(FD, 0, SEEK_END);
}
~Redirect() {
if (Close)
::close(FD);
}
};
typedef std::vector<std::unique_ptr<Redirect>> RedirectStack;
enum { kNumRedirects = 2, kInvalidFD = -1 };
RedirectStack m_Stack;
int m_Bak[kNumRedirects];
int m_CurStdOut;
#ifdef _WIN32
// After a redirection from stdout into stderr then undirecting stdout, the
// console will loose line-buffering. To get arround this we test if stdout
// is a tty during construction, and if so mark the case when stdout has
// returned from a redirection into stderr, then handle it ~RedirectOutput.
// We need two bits for 3 possible states.
unsigned m_TTY : 2;
#else
const bool m_TTY;
#endif
// Exception safe push routine
int push(Redirect* R) {
std::unique_ptr<Redirect> Re(R);
const int FD = R->FD;
m_Stack.emplace_back(Re.get());
Re.release();
return FD;
}
// Call ::dup2 and report errMsg on failure
bool dup2(int oldfd, int newfd, const char* errMsg) {
if (::dup2(oldfd, newfd) == kInvalidFD) {
::perror(errMsg);
return false;
}
return true;
}
// Restore stdstream from backup and close the backup
void close(int &oldfd, int newfd) {
assert((newfd == STDOUT_FILENO || newfd == STDERR_FILENO) && "Not std FD");
assert(oldfd == m_Bak[newfd == STDERR_FILENO] && "Not backup FD");
if (oldfd != kInvalidFD) {
dup2(oldfd, newfd, "RedirectOutput::close");
::close(oldfd);
oldfd = kInvalidFD;
}
}
int restore(int FD, FILE *F, MetaProcessor::RedirectionScope Flag,
int &bakFD) {
// If no backup, we have never redirected the file, so nothing to restore
if (bakFD != kInvalidFD) {
// Find the last redirect for the scope, and restore redirection to it
for (RedirectStack::const_reverse_iterator it = m_Stack.rbegin(),
e = m_Stack.rend();
it != e; ++it) {
const Redirect *R = (*it).get();
if (R->Scope & Flag) {
dup2(R->FD, FD, "RedirectOutput::restore");
return R->FD;
}
}
// No redirection for this scope, restore to backup
fflush(F);
close(bakFD, FD);
}
return kInvalidFD;
}
public:
RedirectOutput() : m_CurStdOut(kInvalidFD),
m_TTY(::isatty(STDOUT_FILENO) ? 1 : 0) {
for (unsigned i = 0; i < kNumRedirects; ++i)
m_Bak[i] = kInvalidFD;
}
~RedirectOutput() {
close(m_Bak[0], STDOUT_FILENO);
close(m_Bak[1], STDERR_FILENO);
while (!m_Stack.empty())
m_Stack.pop_back();
#ifdef _WIN32
// State 2, was tty to begin with, then redirected to stderr and back.
if (m_TTY == 2)
::freopen("CON", "w", stdout);
#else
// If redirection took place without writing anything to the terminal
// beforehand (--nologo) then the dup2 relinking stdout will have caused
// it to be re-opened without line buffering.
if (m_TTY)
::setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
#endif
}
void redirect(llvm::StringRef file, bool apnd,
MetaProcessor::RedirectionScope scope) {
if (file.empty()) {
// Unredirection, remove last redirection state(s) for given scope(s)
if (m_Stack.empty()) {
cling::errs() << "No redirections left to remove\n";
return;
}
MetaProcessor::RedirectionScope lScope = scope;
SmallVector<RedirectStack::iterator, 2> Remove;
for (auto it = m_Stack.rbegin(), e = m_Stack.rend(); it != e; ++it) {
Redirect *R = (*it).get();
const unsigned Match = R->Scope & lScope;
if (Match) {
#ifdef _WIN32
// stdout back from stderr, fix up our console output on destruction
if (m_TTY && R->FD == m_Bak[1] && scope & kSTDOUT)
m_TTY = 2;
#endif
// Clear the flag so restore below will ignore R for scope
R->Scope = MetaProcessor::RedirectionScope(R->Scope & ~Match);
// If no scope left, then R should be removed
if (!R->Scope) {
// standard [24.4.1/1] says &*(reverse_iterator(i)) == &*(i - 1)
Remove.push_back(std::next(it).base());
}
// Clear match to reduce lScope (kSTDBOTH -> kSTDOUT or kSTDERR)
lScope = MetaProcessor::RedirectionScope(lScope & ~Match);
// If nothing to match anymore, then we're done
if (!lScope)
break;
}
}
// std::vector::erase invalidates iterators at or after the point of
// the erase, so if we reverse iterate on Remove everything is fine
for (auto it = Remove.rbegin(), e = Remove.rend(); it != e; ++it)
m_Stack.erase(*it);
} else {
// Add new redirection state
if (push(new Redirect(file.str(), apnd, scope, m_Bak)) != kInvalidFD) {
// Save a backup for the scope(s), if not already done
if (scope & MetaProcessor::kSTDOUT)
dupOnce(STDOUT_FILENO, m_Bak[0]);
if (scope & MetaProcessor::kSTDERR)
dupOnce(STDERR_FILENO, m_Bak[1]);
} else
return; // Failure
}
if (scope & MetaProcessor::kSTDOUT)
m_CurStdOut =
restore(STDOUT_FILENO, stdout, MetaProcessor::kSTDOUT, m_Bak[0]);
if (scope & MetaProcessor::kSTDERR)
restore(STDERR_FILENO, stderr, MetaProcessor::kSTDERR, m_Bak[1]);
}
void resetStdOut(bool toBackup = false) {
// When not outputing to a TTY there is no need to unredirect as
// TerminalDisplay handles writing to the console FD already.
if (!m_TTY)
return;
if (toBackup) {
if (m_Bak[0] != kInvalidFD) {
fflush(stdout);
dup2(m_Bak[0], STDOUT_FILENO, "RedirectOutput::resetStdOut");
}
} else if (m_CurStdOut != kInvalidFD)
dup2(m_CurStdOut, STDOUT_FILENO, "RedirectOutput::resetStdOut");
}
bool empty() const {
return m_Stack.empty();
}
};
MetaProcessor::MaybeRedirectOutputRAII::MaybeRedirectOutputRAII(
MetaProcessor &P) :
m_MetaProcessor(P) {
if (m_MetaProcessor.m_RedirectOutput)
m_MetaProcessor.m_RedirectOutput->resetStdOut(true);
}
MetaProcessor::MaybeRedirectOutputRAII::~MaybeRedirectOutputRAII() {
if (m_MetaProcessor.m_RedirectOutput)
m_MetaProcessor.m_RedirectOutput->resetStdOut();
}
MetaProcessor::MetaProcessor(Interpreter& interp, raw_ostream& outs)
: m_Interp(interp), m_Outs(&outs) {
m_InputValidator.reset(new InputValidator());
m_MetaSema.reset(new MetaSema(interp, *this));
}
MetaProcessor::~MetaProcessor() {
}
int MetaProcessor::process(llvm::StringRef input_line,
Interpreter::CompilationResult& compRes,
Value* result,
bool disableValuePrinting /* = false */) {
if (result)
*result = Value();
compRes = Interpreter::kSuccess;
int expectedIndent = m_InputValidator->getExpectedIndent();
if (expectedIndent)
compRes = Interpreter::kMoreInputExpected;
if (input_line.empty() ||
(input_line.size() == 1 && input_line.front() == '\n')) {
// just a blank line, nothing to do.
return expectedIndent;
}
// Check for and handle meta commands.
MetaParser parser(*m_MetaSema, input_line);
MetaSema::ActionResult actionResult = MetaSema::AR_Success;
if (!m_InputValidator->inBlockComment() &&
parser.isMetaCommand(actionResult, result)) {
if (parser.isQuitRequested())
return -1;
if (actionResult != MetaSema::AR_Success)
compRes = Interpreter::kFailure;
// ExpectedIndent might have changed after meta command.
return m_InputValidator->getExpectedIndent();
}
// Check if the current statement is now complete. If not, return to
// prompt for more.
if (m_InputValidator->validate(input_line) == InputValidator::kIncomplete) {
compRes = Interpreter::kMoreInputExpected;
return m_InputValidator->getExpectedIndent();
}
// We have a complete statement, compile and execute it.
std::string input;
m_InputValidator->reset(&input);
// if (m_Options.RawInput)
// compResLocal = m_Interp.declare(input);
// else
compRes = m_Interp.process(input, result, /*Transaction*/ nullptr,
disableValuePrinting);
return 0;
}
void MetaProcessor::cancelContinuation() const {
m_InputValidator->reset();
}
bool MetaProcessor::awaitingMoreInput() const {
return m_InputValidator->getLastResult() ==
InputValidator::ValidationResult::kIncomplete;
}
int MetaProcessor::getExpectedIndent() const {
return m_InputValidator->getExpectedIndent();
}
static Interpreter::CompilationResult reportIOErr(llvm::StringRef File,
const char* What) {
cling::errs() << "Error in cling::MetaProcessor: "
"cannot " << What << " input: '" << File << "'\n";
return Interpreter::kFailure;
}
Interpreter::CompilationResult
MetaProcessor::readInputFromFile(llvm::StringRef filename,
Value* result,
size_t posOpenCurly,
bool lineByLine) {
// FIXME: This will fail for Unicode BOMs (and seems really weird)
{
// check that it's not binary:
std::ifstream in(filename.str().c_str(), std::ios::in | std::ios::binary);
if (in.fail())
return reportIOErr(filename, "open");
char magic[1024] = {0};
in.read(magic, sizeof(magic));
size_t readMagic = in.gcount();
// Binary files < 300 bytes are rare, and below newlines etc make the
// heuristic unreliable.
if (!in.fail() && readMagic >= 300) {
llvm::StringRef magicStr(magic,in.gcount());
llvm::file_magic fileType
= llvm::identify_magic(magicStr);
if (fileType != llvm::file_magic::unknown)
return reportIOErr(filename, "read from binary");
unsigned printable = 0;
for (size_t i = 0; i < readMagic; ++i)
if (isprint(magic[i]))
++printable;
if (10 * printable < 5 * readMagic) {
// 50% printable for ASCII files should be a safe guess.
return reportIOErr(filename, "won't read from likely binary");
}
}
}
// Windows requires std::ifstream::binary to properly handle
// CRLF and LF line endings
std::ifstream in(filename.str().c_str(), std::ifstream::binary);
if (in.fail())
return reportIOErr(filename, "open");
in.seekg(0, std::ios::end);
if (in.fail())
return reportIOErr(filename, "seek");
size_t size = in.tellg();
if (in.fail())
return reportIOErr(filename, "tell");
in.seekg(0);
if (in.fail())
return reportIOErr(filename, "rewind");
std::string content(size, ' ');
in.read(&content[0], size);
if (in.fail())
return reportIOErr(filename, "read");
static const char whitespace[] = " \t\r\n";
if (content.length() > 2 && content[0] == '#' && content[1] == '!') {
// Convert shebang line to comment. That's nice because it doesn't
// change the content size, leaving posOpenCurly untouched.
content[0] = '/';
content[1] = '/';
}
if (posOpenCurly != (size_t)-1 && !content.empty()) {
assert(content[posOpenCurly] == '{'
&& "No curly at claimed position of opening curly!");
// hide the curly brace:
content[posOpenCurly] = ' ';
// and the matching closing '}'
size_t posCloseCurly = content.find_last_not_of(whitespace);
if (posCloseCurly != std::string::npos) {
if (content[posCloseCurly] == ';' && content[posCloseCurly-1] == '}') {
content[posCloseCurly--] = ' '; // replace ';' and enter next if
}
if (content[posCloseCurly] == '}') {
content[posCloseCurly] = ' '; // replace '}'
} else {
std::string::size_type posBlockClose = content.find_last_of('}');
if (posBlockClose != std::string::npos) {
content[posBlockClose] = ' '; // replace '}'
}
std::string::size_type posComment
= content.find_first_not_of(whitespace, posBlockClose);
if (posComment != std::string::npos
&& content[posComment] == '/' && content[posComment+1] == '/') {
// More text (comments) are okay after the last '}', but
// we can not easily find it to remove it (so we need to upgrade
// this code to better handle the case with comments or
// preprocessor code before and after the leading { and
// trailing })
while (posComment <= posCloseCurly) {
content[posComment++] = ' '; // replace '}' and comment
}
} else {
content[posCloseCurly] = '{';
// By putting the '{' back, we keep the code as consistent as
// the user wrote it ... but we should still warn that we not
// goint to treat this file an unamed macro.
cling::errs()
<< "Warning in cling::MetaProcessor: can not find the closing '}', "
<< llvm::sys::path::filename(filename)
<< " is not handled as an unamed script!\n";
} // did not find "//"
} // remove comments after the trailing '}'
} // find '}'
} // ignore outermost block
m_CurrentlyExecutingFile = filename;
bool topmost = !m_TopExecutingFile.data();
if (topmost)
m_TopExecutingFile = m_CurrentlyExecutingFile;
std::string path(filename.str());
#ifdef _WIN32
std::size_t p = 0;
while ((p = path.find('\\', p)) != std::string::npos) {
path.insert(p, "\\");
p += 2;
}
#endif
content.insert(0, "#line 2 \"" + path + "\" \n");
// We don't want to value print the results of a unnamed macro.
if (content.back() != ';')
content.append(";");
Interpreter::InputFlagsRAII RAII(m_Interp, Interpreter::kInputFromFile |
(lineByLine ? Interpreter::kIFFLineByLine : 0));
Interpreter::CompilationResult ret = Interpreter::kSuccess;
if (lineByLine) {
int rslt = 0;
std::string line;
std::stringstream ss(content);
while (std::getline(ss, line, '\n')) {
rslt = process(line, ret, result);
if (ret == Interpreter::kFailure)
break;
}
if (rslt) {
cling::errs() << "Error in cling::MetaProcessor: file "
<< llvm::sys::path::filename(filename)
<< " is incomplete (missing parenthesis or similar)!\n";
}
} else
ret = m_Interp.process(content, result);
m_CurrentlyExecutingFile = llvm::StringRef();
if (topmost)
m_TopExecutingFile = llvm::StringRef();
return ret;
}
void MetaProcessor::setStdStream(llvm::StringRef file, RedirectionScope scope,
bool append) {
assert((scope & kSTDOUT || scope & kSTDERR) && "Invalid RedirectionScope");
if (!m_RedirectOutput)
m_RedirectOutput.reset(new RedirectOutput);
m_RedirectOutput->redirect(file, append, scope);
if (m_RedirectOutput->empty())
m_RedirectOutput.reset();
}
void MetaProcessor::registerUnloadPoint(const Transaction* T,
llvm::StringRef filename) {
m_MetaSema->registerUnloadPoint(T, filename);
}
} // end namespace cling