Implement cling (clang) internal state verification.
There are cases where cling needs to 'rollback' its state to a previous one. Examples for that are cling's error recovery and code unloading. This can become easily tricky to implement. One step reducing the complexity is the verification of the 'interesting' data structures. Implement .storeState and .compareState commands, which dump the internal compiler data structures like AST (implemented), included files (in progress) and lookup tables (unimplemented yet). The information is dumped into a file and then that file is diff-ed to the one taken with .compareState. If they were identical all files are deleted otherwise a .diff file with the differences remains for debug.
This commit is contained in:
parent
3b64a6b98a
commit
ead0f8d091
@ -195,6 +195,14 @@ namespace cling {
|
||||
///
|
||||
bool m_PrintIR;
|
||||
|
||||
///\brief Flag toggling the state storing on or off.
|
||||
///
|
||||
bool m_StoreState;
|
||||
|
||||
///\brief Flag toggling the state comparing on or off.
|
||||
///
|
||||
bool m_CompareState;
|
||||
|
||||
///\brief Flag toggling the dynamic scopes on or off.
|
||||
///
|
||||
bool m_DynamicLookupEnabled;
|
||||
@ -443,6 +451,19 @@ namespace cling {
|
||||
///
|
||||
void DumpIncludePath();
|
||||
|
||||
///\brief Prints the interpreter state in new file
|
||||
///
|
||||
///\param[in] name - The name of the temporary file where the state will
|
||||
/// be printed
|
||||
///
|
||||
void storeInterpreterState(const std::string& name) const;
|
||||
|
||||
///\brief Compare the actual interpreter state with the one stored
|
||||
/// previously.
|
||||
///\param[in] name - The name of the previously stored file
|
||||
///
|
||||
void compareInterpreterState(const std::string& name) const;
|
||||
|
||||
///\brief Compiles the given input.
|
||||
///
|
||||
/// This interface helps to run everything that cling can run. From
|
||||
@ -605,6 +626,12 @@ namespace cling {
|
||||
bool isPrintingIR() const { return m_PrintIR; }
|
||||
void enablePrintIR(bool print = true) { m_PrintIR = print; }
|
||||
|
||||
bool isStoringState() const { return m_StoreState; }
|
||||
void enableStoreState(bool store = true) { m_StoreState = store; }
|
||||
|
||||
bool isComparingState() const { return m_CompareState; }
|
||||
void enableCompareState(bool compare = true) { m_CompareState = compare; }
|
||||
|
||||
void enableDynamicLookup(bool value = true);
|
||||
bool isDynamicLookupEnabled() const { return m_DynamicLookupEnabled; }
|
||||
|
||||
|
@ -38,9 +38,18 @@
|
||||
#include "llvm/Support/DynamicLibrary.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/raw_os_ostream.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef WIN32
|
||||
@ -311,6 +320,158 @@ namespace cling {
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::storeInterpreterState(const std::string& name) const {
|
||||
ASTContext& C = getSema().getASTContext();
|
||||
TranslationUnitDecl* TU = C.getTranslationUnitDecl();
|
||||
unsigned Indentation = 0;
|
||||
bool PrintInstantiation = false;
|
||||
std::string ErrMsg;
|
||||
llvm::sys::Path Filename = llvm::sys::Path::GetCurrentDirectory();
|
||||
if (Filename.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
//Test that the filename isn't already used
|
||||
std::string testFilename = name + "AST.diff";
|
||||
Filename.appendComponent(testFilename);
|
||||
if (llvm::sys::fs::exists(Filename.str())) {
|
||||
llvm::errs() << Filename.str() << "\n";
|
||||
llvm::errs() << "Filename already exists. Please choose a new one \n";
|
||||
exit (1);
|
||||
}
|
||||
else {
|
||||
std::string rename = name + "AST.tmp";
|
||||
Filename.eraseComponent();
|
||||
Filename.appendComponent(rename);
|
||||
std::ofstream ofs (Filename.c_str(), std::ofstream::out);
|
||||
llvm::raw_os_ostream Out(ofs);
|
||||
clang::PrintingPolicy policy = C.getPrintingPolicy();
|
||||
TU->print(Out, policy, Indentation, PrintInstantiation);
|
||||
Out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::compareInterpreterState(const std::string& name) const {
|
||||
// Store new state
|
||||
std::string compareTo = name + "cmp";
|
||||
storeInterpreterState(compareTo);
|
||||
// Diff between the two existing file
|
||||
llvm::sys::Path tmpDir1 = llvm::sys::Path::GetCurrentDirectory();
|
||||
std::string ErrMsg;
|
||||
if (tmpDir1.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
llvm::sys::Path stateFile1 = tmpDir1;
|
||||
std::string state1 = name + "AST.tmp";
|
||||
stateFile1.appendComponent(state1);
|
||||
llvm::sys::Path tmpDir2 = llvm::sys::Path::GetCurrentDirectory();
|
||||
if (tmpDir2.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
llvm::sys::Path stateFile2 = tmpDir2;
|
||||
std::string state2 = name + "cmpAST.tmp";
|
||||
stateFile2.appendComponent(state2);
|
||||
std::string command = "diff -u " + stateFile1.str() + " "
|
||||
+ stateFile2.str();
|
||||
// printing the results
|
||||
#ifndef LLVM_ON_WIN32
|
||||
FILE* pipe = popen(command.c_str(), "r");
|
||||
if (!pipe) {
|
||||
perror( "Error" );
|
||||
}
|
||||
char buffer[128];
|
||||
std::string result = "";
|
||||
while(!feof(pipe)) {
|
||||
if(fgets(buffer, 128, pipe) != NULL)
|
||||
result += buffer;
|
||||
}
|
||||
pclose(pipe);
|
||||
if(!result.empty()){
|
||||
std::string ErrMsg;
|
||||
llvm::sys::Path DiffFile = llvm::sys::Path::GetCurrentDirectory();
|
||||
if (DiffFile.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
// Test if nameAST.diff already exists
|
||||
std::string file;
|
||||
llvm::sys::Path testFile = llvm::sys::Path::GetCurrentDirectory();
|
||||
if (testFile.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
std::string testName = name + "AST.diff";
|
||||
testFile.appendComponent(testName);
|
||||
if (llvm::sys::fs::exists(testFile.str())){
|
||||
file = name + "1AST.diff";
|
||||
}
|
||||
else {
|
||||
file = name + "AST.diff";
|
||||
}
|
||||
DiffFile.appendComponent(file);
|
||||
std::ofstream ofs (DiffFile.c_str(), std::ofstream::out);
|
||||
llvm::raw_os_ostream Out(ofs);
|
||||
Out << result;
|
||||
Out.flush();
|
||||
llvm::errs() << "File with AST differencies stored in: ";
|
||||
llvm::errs() << file << "\n";
|
||||
llvm::errs() << DiffFile.c_str();
|
||||
llvm::errs() << "\n";
|
||||
}
|
||||
#else
|
||||
FILE* pipe = _popen(command.c_str(), "r");
|
||||
if (!pipe) {
|
||||
perror( "Error" );
|
||||
}
|
||||
char buffer[128];
|
||||
std::string result = "";
|
||||
while(!feof(pipe)) {
|
||||
if(fgets(buffer, 128, pipe) != NULL)
|
||||
result += buffer;
|
||||
}
|
||||
_pclose(pipe);
|
||||
if(!result.empty()){
|
||||
std::string ErrMsg;
|
||||
llvm::sys::Path DiffFile = llvm::sys::Path::GetCurrentDirectory();
|
||||
if (DiffFile.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
// Test if nameAST.diff already exists
|
||||
std::string file;
|
||||
llvm::sys::Path testFile = llvm::sys::Path::GetCurrentDirectory();
|
||||
if (testFile.isEmpty()) {
|
||||
llvm::errs() << "Error: " << ErrMsg << "\n";
|
||||
return;
|
||||
}
|
||||
std::string testName = name + "AST.diff";
|
||||
testFile.appendComponent(testName);
|
||||
if (llvm::sys::fs::exists(testFile.str())){
|
||||
file = name + "1AST.diff";
|
||||
}
|
||||
else {
|
||||
file = name + "AST.diff";
|
||||
}
|
||||
DiffFile.appendComponent(file);
|
||||
std::ofstream ofs (DiffFile.c_str(), std::ofstream::out);
|
||||
llvm::raw_os_ostream Out(ofs);
|
||||
Out.flush();
|
||||
llvm::errs() << "File with AST differencies stored in: ";
|
||||
llvm::errs() << file << "\n";
|
||||
llvm::errs() << DiffFile.c_str();
|
||||
llvm::errs() << "\n";
|
||||
}
|
||||
#endif
|
||||
std::string command2 = stateFile1.str();
|
||||
std::string command3 = stateFile2.str();
|
||||
int result1 = std::remove(command2.c_str());
|
||||
int result2 = std::remove(command3.c_str());
|
||||
if((result1 != 0) && (result2 != 0))
|
||||
perror( "Error deleting files were AST were stored" );
|
||||
}
|
||||
|
||||
// Adapted from clang/lib/Frontend/CompilerInvocation.cpp
|
||||
void Interpreter::GetIncludePaths(llvm::SmallVectorImpl<std::string>& incpaths,
|
||||
bool withSystem, bool withFlags) {
|
||||
|
@ -111,7 +111,8 @@ namespace cling {
|
||||
|| isdynamicExtensionsCommand()
|
||||
|| ishelpCommand() || isfileExCommand() || isfilesCommand() || isClassCommand()
|
||||
|| isgCommand() || isTypedefCommand() || isprintIRCommand()
|
||||
|| isShellCommand(actionResult, resultValue);
|
||||
|| isShellCommand(actionResult, resultValue)
|
||||
|| isstoreStateCommand() || iscompareStateCommand();
|
||||
}
|
||||
|
||||
// L := 'L' FilePath
|
||||
@ -281,6 +282,54 @@ namespace cling {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaParser::isstoreStateCommand() {
|
||||
if (getCurTok().is(tok::ident) &&
|
||||
getCurTok().getIdent().equals("storeState")) {
|
||||
//MetaSema::SwitchMode mode = MetaSema::kToggle;
|
||||
consumeToken();
|
||||
skipWhitespace();
|
||||
if (!getCurTok().is(tok::quote))
|
||||
return false; // FIXME: Issue proper diagnostics
|
||||
|
||||
consumeToken();
|
||||
if (!getCurTok().is(tok::ident))
|
||||
return false; // FIXME: Issue proper diagnostics
|
||||
|
||||
std::string ident = getCurTok().getIdent();
|
||||
consumeToken();
|
||||
if (!getCurTok().is(tok::quote))
|
||||
return false; // FIXME: Issue proper diagnostics
|
||||
|
||||
m_Actions->actOnstoreStateCommand(ident);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaParser::iscompareStateCommand() {
|
||||
if (getCurTok().is(tok::ident) &&
|
||||
getCurTok().getIdent().equals("compareState")) {
|
||||
//MetaSema::SwitchMode mode = MetaSema::kToggle;
|
||||
consumeToken();
|
||||
skipWhitespace();
|
||||
if (!getCurTok().is(tok::quote))
|
||||
return false; // FIXME: Issue proper diagnostics
|
||||
|
||||
consumeToken();
|
||||
if (!getCurTok().is(tok::ident))
|
||||
return false; // FIXME: Issue proper diagnostics
|
||||
|
||||
std::string ident = getCurTok().getIdent();
|
||||
consumeToken();
|
||||
if (!getCurTok().is(tok::quote))
|
||||
return false; // FIXME: Issue proper diagnostics
|
||||
|
||||
m_Actions->actOncompareStateCommand(ident);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaParser::isdynamicExtensionsCommand() {
|
||||
if (getCurTok().is(tok::ident) &&
|
||||
getCurTok().getIdent().equals("dynamicExtensions")) {
|
||||
|
@ -30,7 +30,8 @@ namespace cling {
|
||||
// ICommand | OCommand | RawInputCommand |
|
||||
// PrintASTCommand | DynamicExtensionsCommand | HelpCommand |
|
||||
// FileExCommand | FilesCommand | ClassCommand |
|
||||
// GCommand | PrintIRCommand
|
||||
// GCommand | PrintIRCommand | StoreStateCommand |
|
||||
// CompareStateCommand
|
||||
// LCommand := 'L' FilePath
|
||||
// qCommand := 'q'
|
||||
// XCommand := 'x' FilePath[ArgList] | 'X' FilePath[ArgList]
|
||||
@ -40,6 +41,8 @@ namespace cling {
|
||||
// RawInputCommand := 'rawInput' [Constant]
|
||||
// PrintASTCommand := 'printAST' [Constant]
|
||||
// PrintIRCommand := 'printIR' [Constant]
|
||||
// StoreStateCommand := 'storeState' "Ident"
|
||||
// CompareStateCommand := 'compareState' "Ident"
|
||||
// DynamicExtensionsCommand := 'dynamicExtensions' [Constant]
|
||||
// HelpCommand := 'help'
|
||||
// FileExCommand := 'fileEx'
|
||||
@ -81,6 +84,8 @@ namespace cling {
|
||||
bool israwInputCommand();
|
||||
bool isprintASTCommand();
|
||||
bool isprintIRCommand();
|
||||
bool isstoreStateCommand();
|
||||
bool iscompareStateCommand();
|
||||
bool isdynamicExtensionsCommand();
|
||||
bool ishelpCommand();
|
||||
bool isfileExCommand();
|
||||
|
@ -107,6 +107,14 @@ namespace cling {
|
||||
m_Interpreter.enablePrintIR(mode);
|
||||
}
|
||||
|
||||
void MetaSema::actOnstoreStateCommand(llvm::StringRef name) const {
|
||||
m_Interpreter.storeInterpreterState(name);
|
||||
}
|
||||
|
||||
void MetaSema::actOncompareStateCommand(llvm::StringRef name) const {
|
||||
m_Interpreter.compareInterpreterState(name);
|
||||
}
|
||||
|
||||
void MetaSema::actOndynamicExtensionsCommand(SwitchMode mode/* = kToggle*/)
|
||||
const {
|
||||
if (mode == kToggle) {
|
||||
|
@ -109,6 +109,18 @@ namespace cling {
|
||||
///
|
||||
void actOnprintIRCommand(SwitchMode mode = kToggle) const;
|
||||
|
||||
///\brief Store the interpreter's state.
|
||||
///
|
||||
///\param[in] name - Name of the file where the state will be stored
|
||||
///
|
||||
void actOnstoreStateCommand(llvm::StringRef name) const;
|
||||
|
||||
///\brief Compare the interpreter's state with the one previously stored
|
||||
///
|
||||
///\param[in] name - Name of the file where the previous state was stored
|
||||
///
|
||||
void actOncompareStateCommand(llvm::StringRef name) const;
|
||||
|
||||
///\brief Switches on/off the experimental dynamic extensions (dynamic
|
||||
/// scopes) and late binding.
|
||||
///
|
||||
|
Loading…
x
Reference in New Issue
Block a user