Skip to content

Commit fbe47a5

Browse files
committed
Fix crash if transactions are unloaded past the first printing of an expression.
Fix crash when RuntimePrintValue.h cannot be loaded. Merge value-printing transactions with the transaction that invoked it. The latter fixes poor functionality in the .undo system where the user must have knowledge of how many transactions are added when an expression is printed.
1 parent 34a2f22 commit fbe47a5

File tree

6 files changed

+219
-13
lines changed

6 files changed

+219
-13
lines changed

include/cling/Interpreter/Interpreter.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ namespace cling {
102102
~StateDebuggerRAII();
103103
};
104104

105+
///\brief Merge all transactions between construction and destruction
106+
/// Use Prev to merge the Transactions with the last Transaction (true)
107+
/// or the first Transaction created after instantiation (false).
108+
class TransactionMergerRAII {
109+
IncrementalParser& m_IncrParser;
110+
const Transaction* m_T;
111+
112+
public:
113+
TransactionMergerRAII(Interpreter* Interp);
114+
~TransactionMergerRAII();
115+
};
116+
105117
///\brief Describes the return result of the different routines that do the
106118
/// incremental compilation.
107119
///
@@ -199,6 +211,7 @@ namespace cling {
199211

200212
enum {
201213
kStdStringTransaction = 0, // Transaction known to contain std::string
214+
kPrintValueTransaction, // Transaction that included RuntimePrintValue.h
202215
kNumTransactions
203216
};
204217
mutable const Transaction* m_CachedTrns[kNumTransactions] = {};
@@ -780,6 +793,15 @@ namespace cling {
780793
[](const clang::PresumedLoc&) { return false;}) const;
781794

782795
friend class runtime::internal::LifetimeHandler;
796+
797+
///\brief Used by valuePrinterInternal::printValueInternal to mark when the
798+
/// value printing headers were included.
799+
///
800+
///\returns reference to m_CachedTrns[kPrintValueTransaction]
801+
///
802+
const Transaction*& printValueTransaction() {
803+
return m_CachedTrns[kPrintValueTransaction];
804+
}
783805
};
784806
} // namespace cling
785807

lib/Interpreter/IncrementalParser.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,65 @@ namespace cling {
278278
return m_Consumer->getTransaction();
279279
}
280280

281+
void
282+
IncrementalParser::mergeTransactionsAfter(const Transaction *T) {
283+
bool Found = false;
284+
llvm::SmallVector<Transaction*, 8> Merge;
285+
for (auto Itr = m_Transactions.rbegin(), End = m_Transactions.rend();
286+
Itr != End; ++Itr) {
287+
Transaction *Cur = *Itr;
288+
Merge.push_back(Cur);
289+
if ((Found = (Cur == T)))
290+
break;
291+
}
292+
if (!Found) {
293+
clang::DiagnosticsEngine& Diags = m_Interpreter->getDiagnostics();
294+
const auto ID = Diags.getCustomDiagID(clang::DiagnosticsEngine::Warning,
295+
"Transaction was not found.");
296+
Diags.Report(m_Interpreter->getSourceLocation(), ID);
297+
return; // Unused: nullptr
298+
}
299+
if (Merge.empty()) {
300+
// Trying to merge the last Transaction with nothing following.
301+
return; // Unused: getCurrentTransaction();
302+
}
303+
assert(!T->isNestedTransaction() && "New parent cannot be a child.");
304+
305+
auto Itr = Merge.rbegin();
306+
Transaction *Parent = *Itr;
307+
++Itr;
308+
for (auto End = Merge.rend(); Itr != End; ++Itr) {
309+
Transaction *CurChild = *Itr;
310+
// Try to keep everything current
311+
if (m_Consumer->getTransaction() == CurChild)
312+
m_Consumer->setTransaction(Parent);
313+
314+
// Keep parents in place
315+
while (CurChild->getParent())
316+
CurChild = CurChild->getParent();
317+
318+
// FIXME: Propogate child's error state into parent. Note the CurChild
319+
// error state will have to be saved here, as once addNestedTransaction
320+
// has finished it can no longer be retrieved.
321+
//
322+
// Just force diag states to match until there's a decent test case
323+
assert((CurChild->getIssuedDiags() != Transaction::kErrors ||
324+
Parent->getIssuedDiags() == Transaction::kErrors)
325+
&& "Cannot merge transactions with different error states");
326+
327+
Parent->addNestedTransaction(CurChild);
328+
329+
// Move whatever the CurChild.Next is into Parent
330+
Parent->setNext(const_cast<Transaction*>(CurChild->getNext()));
331+
// And make sure the CurChild.Next is empty
332+
CurChild->setNext(nullptr);
333+
334+
m_Transactions.pop_back();
335+
}
336+
assert(Parent->getNext() == nullptr && "Parent still has next");
337+
return; // Unused: parent;
338+
}
339+
281340
SourceLocation IncrementalParser::getLastMemoryBufferEndLoc() const {
282341
const SourceManager& SM = getCI()->getSourceManager();
283342
SourceLocation Result = SM.getLocForStartOfFile(m_VirtualFileID);

lib/Interpreter/IncrementalParser.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ namespace cling {
176176
return m_Transactions.back();
177177
}
178178

179+
///\brief Merge transactions after the one given.
180+
///
181+
///\param[in] T - transactions after which to merge
182+
///\returns the parent transaction of the merge.
183+
///
184+
void mergeTransactionsAfter(const Transaction *T);
185+
179186
///\brief Returns the currently active transaction.
180187
///
181188
const Transaction* getCurrentTransaction() const;

lib/Interpreter/Interpreter.cpp

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ namespace cling {
141141
}
142142
}
143143

144+
Interpreter::TransactionMergerRAII::TransactionMergerRAII(Interpreter* I) :
145+
m_IncrParser(*I->m_IncrParser), m_T(m_IncrParser.getLastTransaction()) {}
146+
147+
Interpreter::TransactionMergerRAII::~TransactionMergerRAII() {
148+
m_IncrParser.mergeTransactionsAfter(m_T);
149+
}
150+
144151
const Parser& Interpreter::getParser() const {
145152
return *m_IncrParser->getParser();
146153
}
@@ -1193,20 +1200,42 @@ namespace cling {
11931200
return kSuccess;
11941201
}
11951202

1203+
const Transaction *printTBefore = m_CachedTrns[kPrintValueTransaction];
1204+
11961205
Value resultV;
11971206
if (!V)
11981207
V = &resultV;
11991208
if (!lastT->getWrapperFD()) // no wrapper to run
12001209
return Interpreter::kSuccess;
1201-
else if (RunFunction(lastT->getWrapperFD(), V) < kExeFirstError){
1202-
if (lastT->getCompilationOpts().ValuePrinting
1203-
!= CompilationOptions::VPDisabled
1204-
&& V->isValid()
1205-
// the !V->needsManagedAllocation() case is handled by
1206-
// dumpIfNoStorage.
1207-
&& V->needsManagedAllocation())
1208-
V->dump();
1209-
return Interpreter::kSuccess;
1210+
else {
1211+
ExecutionResult rslt = RunFunction(lastT->getWrapperFD(), V);
1212+
1213+
// If print value Transaction has changed value, then lastT is now
1214+
// the transaction that holds the transaction(s) of loading up the
1215+
// value printer and must be recorded as such.
1216+
if (printTBefore != m_CachedTrns[kPrintValueTransaction]) {
1217+
assert(m_CachedTrns[kPrintValueTransaction] != nullptr
1218+
&& "Value printer failed to load after success?");
1219+
// As the stack is unwound from recursive calls to EvaluateInternal
1220+
// we merge the subsequent transactions into the current one.
1221+
// Then m_CachedTrns[kPrintValue] is marked as this transaction as it
1222+
// is the first known Transaction that caused the printer to be loaded.
1223+
m_IncrParser->mergeTransactionsAfter(lastT);
1224+
m_CachedTrns[kPrintValueTransaction] = lastT;
1225+
assert(!m_CachedTrns[kPrintValueTransaction]->isNestedTransaction() &&
1226+
"Print value transaction is not topmost parent");
1227+
}
1228+
1229+
if ( rslt < kExeFirstError) {
1230+
if (lastT->getCompilationOpts().ValuePrinting
1231+
!= CompilationOptions::VPDisabled
1232+
&& V->isValid()
1233+
// the !V->needsManagedAllocation() case is handled by
1234+
// dumpIfNoStorage.
1235+
&& V->needsManagedAllocation())
1236+
V->dump();
1237+
return Interpreter::kSuccess;
1238+
}
12101239
}
12111240
return Interpreter::kSuccess;
12121241
}
@@ -1355,6 +1384,10 @@ namespace cling {
13551384
<< i << " of " << numberOfTransactions << "\n";
13561385
return;
13571386
}
1387+
// Mark Interpreter as not having loaded 'RuntimePrintValue.h'
1388+
if (T == m_CachedTrns[kPrintValueTransaction])
1389+
m_CachedTrns[kPrintValueTransaction] = nullptr;
1390+
13581391
unload(*T);
13591392
}
13601393
}

lib/Interpreter/ValuePrinter.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -863,12 +863,30 @@ namespace cling {
863863

864864
std::string printValueInternal(const Value &V) {
865865
assert(V.getInterpreter() && "Invalid cling::Value");
866-
static bool includedRuntimePrintValue = false; // initialized only once as a static function variable
866+
867867
// Include "RuntimePrintValue.h" only on the first printing.
868868
// This keeps the interpreter lightweight and reduces the startup time.
869-
if (!includedRuntimePrintValue) {
870-
V.getInterpreter()->declare("#include \"cling/Interpreter/RuntimePrintValue.h\"");
871-
includedRuntimePrintValue = true;
869+
// But user can undo past the transaction that invoked this, so whether
870+
// we are first or not is known by the interpreter.
871+
//
872+
// Additionally add any transactions that occur in this scope as
873+
// children to the transaction that invoked us
874+
875+
Interpreter* Interp = V.getInterpreter();
876+
Interpreter::TransactionMergerRAII M(Interp);
877+
const Transaction*& T = Interp->printValueTransaction();
878+
if (!T) {
879+
// DiagnosticErrorTrap Trap(Interp->getSema().getDiagnostics());
880+
Interp->declare("#include \"cling/Interpreter/RuntimePrintValue.h\"",
881+
const_cast<Transaction**>(&T));
882+
if (!T) {
883+
// Should also check T->getIssuedDiags() == Transaction::kErrors)?
884+
885+
// It's redundant, but nicer to see the error at the bottom.
886+
// if (!Trap.hasErrorOccurred())
887+
cling::errs() << "RuntimePrintValue.h could not be loaded.\n";
888+
return kUndefined;
889+
}
872890
}
873891
return printUnpackedClingValue(V);
874892
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//------------------------------------------------------------------------------
2+
// CLING - the C++ LLVM-based InterpreterG :)
3+
//
4+
// This file is dual-licensed: you can choose to license it under the University
5+
// of Illinois Open Source License or the GNU Lesser General Public License. See
6+
// LICENSE.TXT for details.
7+
//------------------------------------------------------------------------------
8+
9+
// RUN: cat %s | %cling -Xclang -verify 2>&1 | FileCheck %s
10+
// Test undoPrinter
11+
12+
.stats undo
13+
// CHECK: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
14+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
15+
16+
const char *message = "winkey";
17+
18+
message
19+
// CHECK-NEXT: (const char *) "winkey"
20+
21+
.undo
22+
23+
// Make sure we can still print
24+
message
25+
// CHECK-NEXT: (const char *) "winkey"
26+
27+
.undo
28+
.stats undo
29+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
30+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
31+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
32+
33+
message
34+
// CHECK-NEXT: (const char *) "winkey"
35+
36+
.undo // print message
37+
.undo // decalre message
38+
.stats undo
39+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
40+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
41+
42+
#include "cling/Interpreter/Interpreter.h"
43+
44+
gCling->echo("1;");
45+
// CHECK-NEXT: (int) 1
46+
47+
.stats undo
48+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
49+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
50+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
51+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
52+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
53+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
54+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
55+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
56+
57+
.undo
58+
.stats undo
59+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
60+
// CHECK-NEXT: ` <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
61+
// CHECK-NEXT: <cling::Transaction* 0x{{[0-9a-f]+}} isEmpty=0 isCommitted=1>
62+
63+
gCling->echo("1;");
64+
// CHECK-NEXT: (int) 1
65+
66+
// expected-no-diagnostics
67+
.q

0 commit comments

Comments
 (0)