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:
Jerome Beclin 2013-08-21 12:25:48 +02:00 committed by sftnight
parent 3b64a6b98a
commit ead0f8d091
6 changed files with 264 additions and 2 deletions

View File

@ -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; }

View File

@ -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) {

View File

@ -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")) {

View File

@ -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();

View File

@ -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) {

View File

@ -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.
///