//------------------------------------------------------------------------------ // CLING - the C++ LLVM-based InterpreterG :) // author: Axel Naumann // // 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 "Display.h" #include "InputValidator.h" #include "MetaParser.h" #include "MetaSema.h" #include "cling/Interpreter/Interpreter.h" #include "cling/Interpreter/Value.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Preprocessor.h" #include "llvm/Support/Path.h" #include #include #include #include #ifndef WIN32 #include #else #include #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #endif using namespace clang; namespace cling { MetaProcessor::MaybeRedirectOutputRAII::MaybeRedirectOutputRAII( MetaProcessor* p) :m_MetaProcessor(p), m_isCurrentlyRedirecting(0) { StringRef redirectionFile; if (!m_MetaProcessor->m_PrevStdoutFileName.empty()) { redirectionFile = m_MetaProcessor->m_PrevStdoutFileName.back(); redirect(stdout, redirectionFile.str(), kSTDOUT); } if (!m_MetaProcessor->m_PrevStderrFileName.empty()) { redirectionFile = m_MetaProcessor->m_PrevStderrFileName.back(); // Deal with the case 2>&1 and 2&>1 if (strcmp(redirectionFile.data(), "_IO_2_1_stdout_") == 0) { // If out is redirected to a file. if (!m_MetaProcessor->m_PrevStdoutFileName.empty()) { redirectionFile = m_MetaProcessor->m_PrevStdoutFileName.back(); } else { unredirect(m_MetaProcessor->m_backupFDStderr, STDERR_FILENO, stderr); } } redirect(stderr, redirectionFile.str(), kSTDERR); } } void MetaProcessor::MaybeRedirectOutputRAII::redirect(FILE* file, const std::string& fileName, MetaProcessor::RedirectionScope scope) { if (!fileName.empty()) { FILE* redirectionFile = freopen(fileName.c_str(), "a", file); if (!redirectionFile) { llvm::errs()<<"cling::MetaProcessor::MaybeRedirectOutputRAII::redirect:" " Not succefully reopened the redirection file " << fileName.c_str() << "\n."; } else { m_isCurrentlyRedirecting |= scope; } } } void MetaProcessor::MaybeRedirectOutputRAII::pop() { if (m_isCurrentlyRedirecting & kSTDOUT) { unredirect(m_MetaProcessor->m_backupFDStdout, STDOUT_FILENO, stdout); } if (m_isCurrentlyRedirecting & kSTDERR) { unredirect(m_MetaProcessor->m_backupFDStderr, STDERR_FILENO, stderr); } } void MetaProcessor::MaybeRedirectOutputRAII::unredirect(int backupFD, int expectedFD, FILE* file) { // Switch back to previous file after line is processed. // Flush the current content if there is any. if (!feof(file)) { fflush(file); } // Copy the original fd for the std. if (dup2(backupFD, expectedFD) != expectedFD) { llvm::errs() << "cling::MetaProcessor::unredirect " << "The unredirection file descriptor not valid " << backupFD << ".\n"; } } MetaProcessor::MetaProcessor(Interpreter& interp, raw_ostream& outs) : m_Interp(interp), m_Outs(&outs) { m_InputValidator.reset(new InputValidator()); m_MetaParser.reset(new MetaParser(new MetaSema(interp, *this))); m_backupFDStdout = copyFileDescriptor(STDOUT_FILENO); m_backupFDStderr = copyFileDescriptor(STDERR_FILENO); } MetaProcessor::~MetaProcessor() {} int MetaProcessor::process(const char* input_text, Interpreter::CompilationResult& compRes, Value* result) { if (result) *result = Value(); compRes = Interpreter::kSuccess; int expectedIndent = m_InputValidator->getExpectedIndent(); if (expectedIndent) compRes = Interpreter::kMoreInputExpected; if (!input_text || !input_text[0]) { // nullptr / empty string, nothing to do. return expectedIndent; } std::string input_line(input_text); if (input_line == "\n") { // just a blank line, nothing to do. return expectedIndent; } // Check for and handle meta commands. m_MetaParser->enterNewInputLine(input_line); MetaSema::ActionResult actionResult = MetaSema::AR_Success; if (m_MetaParser->isMetaCommand(actionResult, result)) { if (m_MetaParser->isQuitRequested()) return -1; if (actionResult != MetaSema::AR_Success) compRes = Interpreter::kFailure; return expectedIndent; } // 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->getInput(); m_InputValidator->reset(); // if (m_Options.RawInput) // compResLocal = m_Interp.declare(input); // else compRes = m_Interp.process(input, result); return 0; } void MetaProcessor::cancelContinuation() const { m_InputValidator->reset(); } int MetaProcessor::getExpectedIndent() const { return m_InputValidator->getExpectedIndent(); } Interpreter::CompilationResult MetaProcessor::readInputFromFile(llvm::StringRef filename, Value* result, bool ignoreOutmostBlock /*=false*/) { { // check that it's not binary: std::ifstream in(filename.str().c_str(), std::ios::in | std::ios::binary); char magic[1024] = {0}; in.read(magic, sizeof(magic)); size_t readMagic = in.gcount(); if (readMagic >= 4) { llvm::StringRef magicStr(magic,in.gcount()); llvm::sys::fs::file_magic fileType = llvm::sys::fs::identify_magic(magicStr); if (fileType != llvm::sys::fs::file_magic::unknown) { llvm::errs() << "Error in cling::MetaProcessor: " "cannot read input from a binary file!\n"; return Interpreter::kFailure; } 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. llvm::errs() << "Error in cling::MetaProcessor: " "cannot read input from a (likely) binary file!\n" << printable; return Interpreter::kFailure; } } } std::ifstream in(filename.str().c_str()); in.seekg(0, std::ios::end); size_t size = in.tellg(); std::string content(size, ' '); in.seekg(0); in.read(&content[0], size); if (ignoreOutmostBlock && !content.empty()) { static const char whitespace[] = " \t\r\n"; std::string::size_type posNonWS = content.find_first_not_of(whitespace); // Handle comments before leading { while (content[posNonWS] == '/' && content[posNonWS+1] == '/') { // Remove the comment line posNonWS = content.find_first_of('\n', posNonWS+2)+1; } std::string::size_type replaced = posNonWS; if (posNonWS != std::string::npos) { if (content[posNonWS] == '{') { // hide the curly brace: content[posNonWS] = ' '; // and the matching closing '}' posNonWS = content.find_last_not_of(whitespace); if (posNonWS != std::string::npos) { if (content[posNonWS] == ';' && content[posNonWS-1] == '}') { content[posNonWS--] = ' '; // replace ';' and enter next if } if (content[posNonWS] == '}') { content[posNonWS] = ' '; // replace '}' } else { std::string::size_type posComment = content.find_last_of('}'); if (content[posComment] == '}') { content[posComment] = ' '; // replace '}' } posComment = content.find_first_not_of(whitespace, posComment); if (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 <= posNonWS) { content[posComment++] = ' '; // replace '}' and comment } } else { content[replaced] = '{'; // 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. llvm::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 '}' } // have '{' } // have non-whitespace } // ignore outmost block std::string strFilename(filename.str()); m_CurrentlyExecutingFile = strFilename; bool topmost = !m_TopExecutingFile.data(); if (topmost) m_TopExecutingFile = m_CurrentlyExecutingFile; Interpreter::CompilationResult ret; if (process(content.c_str(), ret, result)) { // Input file has to be complete. llvm::errs() << "Error in cling::MetaProcessor: file " << llvm::sys::path::filename(filename) << " is incomplete (missing parenthesis or similar)!\n"; ret = Interpreter::kFailure; } m_CurrentlyExecutingFile = llvm::StringRef(); if (topmost) m_TopExecutingFile = llvm::StringRef(); return ret; } void MetaProcessor::setFileStream(llvm::StringRef file, bool append, int fd, llvm::SmallVector, 2>& prevFileStack) { // If we have a fileName to redirect to store it. if (!file.empty()) { prevFileStack.push_back(file); // pop and push a null terminating 0. // SmallVectorImpl does not have a c_str(), thus instead of casting to // a SmallString we null terminate the data that we have and pop the // 0 char back. prevFileStack.back().push_back(0); prevFileStack.back().pop_back(); if (!append) { FILE * f; if (!(f = fopen(file.data(), "w"))) { llvm::errs() << "cling::MetaProcessor::setFileStream:" " The file path " << file.data() << "is not valid."; } else { fclose(f); } } // Else unredirection, so switch to the previous file. } else { // If there is no previous file on the stack we pop the file if (!prevFileStack.empty()) { prevFileStack.pop_back(); } } } void MetaProcessor::setStdStream(llvm::StringRef file, RedirectionScope stream, bool append) { if (stream & kSTDOUT) { setFileStream(file, append, STDOUT_FILENO, m_PrevStdoutFileName); } if (stream & kSTDERR) { setFileStream(file, append, STDERR_FILENO, m_PrevStderrFileName); } } int MetaProcessor::copyFileDescriptor(int fd) { int backupFD = dup(fd); if (backupFD < 0) { llvm::errs() << "MetaProcessor::copyFileDescriptor: Duplicating the file" " descriptor " << fd << "resulted in an error." " Will not be able to unredirect."; } return backupFD; } } // end namespace cling