Skip to content

Commit 1647088

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 a0a89cb commit 1647088

File tree

6 files changed

+212
-13
lines changed

6 files changed

+212
-13
lines changed

include/cling/Interpreter/Interpreter.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ 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 TransactionMerge {
109+
IncrementalParser& m_IncrParser;
110+
const Transaction* m_Current;
111+
const bool m_Prev;
112+
113+
public:
114+
TransactionMerge(Interpreter* Interp, bool Prev);
115+
~TransactionMerge();
116+
};
117+
105118
///\brief Describes the return result of the different routines that do the
106119
/// incremental compilation.
107120
///
@@ -199,6 +212,7 @@ namespace cling {
199212

200213
enum {
201214
kStdStringTransaction = 0, // Transaction known to contain std::string
215+
kPrintValueTransaction, // Transaction that included RuntimePrintValue.h
202216
kNumTransactions
203217
};
204218
mutable const Transaction* m_CachedTrns[kNumTransactions] = {};
@@ -780,6 +794,15 @@ namespace cling {
780794
[](const clang::PresumedLoc&) { return false;}) const;
781795

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

lib/Interpreter/IncrementalParser.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,55 @@ namespace cling {
280280
return m_Consumer->getTransaction();
281281
}
282282

283+
const Transaction*
284+
IncrementalParser::mergeTransactionsAfter(const Transaction *T, bool Prev) {
285+
llvm::SmallVector<Transaction*, 8> merge;
286+
for (std::deque<Transaction*>::reverse_iterator itr = m_Transactions.rbegin(),
287+
end = m_Transactions.rend(); itr != end; ++itr) {
288+
Transaction *cur = *itr;
289+
merge.push_back(cur);
290+
if (cur==T) {
291+
if (!Prev)
292+
merge.pop_back();
293+
break;
294+
}
295+
}
296+
if (merge.size()>1) {
297+
llvm::SmallVector<Transaction*, 8>::reverse_iterator itr = merge.rbegin();
298+
Transaction *parent = *itr;
299+
++itr;
300+
for (llvm::SmallVector<Transaction*, 8>::reverse_iterator end = merge.rend();
301+
itr != end; ++itr) {
302+
Transaction *cur = *itr;
303+
// Try to keep everything current
304+
if (m_Consumer->getTransaction()==cur)
305+
m_Consumer->setTransaction(parent);
306+
if (cur==parent->getNext()) // Make sure parent->next isn't a child
307+
parent->setNext(const_cast<Transaction*>(cur->getNext()));
308+
while (cur->getParent()) // Keep parents in place
309+
cur = cur->getParent();
310+
311+
// Lets just make them match until there's a decent test case
312+
assert((cur->getIssuedDiags() != Transaction::kErrors ||
313+
parent->getIssuedDiags() == Transaction::kErrors)
314+
&& "Cannot merge transactions with different error states");
315+
parent->addNestedTransaction(cur);
316+
317+
// TODO: Propogate child's error state into parent.
318+
//
319+
// Once addNestedTransaction finishes we can't get the child's errors
320+
// const Transaction::IssuedDiags childErr = cur->getIssuedDiags();
321+
// parent->addNestedTransaction(cur);
322+
// if (issued < parent->getIssuedDiags())
323+
// parent->setIssuedDiags(childErr);
324+
325+
m_Transactions.pop_back();
326+
}
327+
return parent;
328+
}
329+
return getCurrentTransaction();
330+
}
331+
283332
SourceLocation IncrementalParser::getLastMemoryBufferEndLoc() const {
284333
const SourceManager& SM = getCI()->getSourceManager();
285334
SourceLocation Result = SM.getLocForStartOfFile(m_VirtualFileID);

lib/Interpreter/IncrementalParser.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ namespace cling {
175175
return m_Transactions.back();
176176
}
177177

178+
///\brief Merge transactions after the one given.
179+
///
180+
///\param[in] T - transactions after which to merge
181+
///\param[in] prev - merge into transaction prior to T or T itself
182+
///\returns the parent transaction of the merge.
183+
///
184+
const Transaction* mergeTransactionsAfter(const Transaction *T, bool prev);
185+
178186
///\brief Returns the currently active transaction.
179187
///
180188
const Transaction* getCurrentTransaction() const;

lib/Interpreter/Interpreter.cpp

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

144+
Interpreter::TransactionMerge::TransactionMerge(Interpreter *interp,
145+
bool prev) :
146+
m_IncrParser(*interp->m_IncrParser),
147+
m_Current(m_IncrParser.getLastTransaction()), m_Prev(prev) {
148+
}
149+
150+
Interpreter::TransactionMerge::~TransactionMerge() {
151+
m_IncrParser.mergeTransactionsAfter(m_Current, m_Prev);
152+
}
153+
144154
const Parser& Interpreter::getParser() const {
145155
return *m_IncrParser->getParser();
146156
}
@@ -1193,20 +1203,40 @@ namespace cling {
11931203
return kSuccess;
11941204
}
11951205

1206+
const Transaction *prntT = m_CachedTrns[kPrintValueTransaction];
1207+
11961208
Value resultV;
11971209
if (!V)
11981210
V = &resultV;
11991211
if (!lastT->getWrapperFD()) // no wrapper to run
12001212
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;
1213+
else {
1214+
ExecutionResult rslt = RunFunction(lastT->getWrapperFD(), V);
1215+
1216+
// If print value Transaction has changed value, then lastT is now
1217+
// the transaction that holds the transaction(s) of loading up the
1218+
// value printer and must be recorded as such.
1219+
if ( prntT != m_CachedTrns[kPrintValueTransaction] ) {
1220+
assert(m_CachedTrns[kPrintValueTransaction] != nullptr
1221+
&& "Value printer failed to load after success?");
1222+
// As the stack is unwinded from recursive calls to EvaluateInternal
1223+
// we merge the subsequent transactions into the current one.
1224+
// Then m_CachedTrns[kPrintValue] is marked as this transaction as it
1225+
// is the first known Transaction that caused the printer to be loaded.
1226+
m_IncrParser->mergeTransactionsAfter(lastT, true);
1227+
m_CachedTrns[kPrintValueTransaction] = lastT;
1228+
}
1229+
1230+
if ( rslt < kExeFirstError) {
1231+
if (lastT->getCompilationOpts().ValuePrinting
1232+
!= CompilationOptions::VPDisabled
1233+
&& V->isValid()
1234+
// the !V->needsManagedAllocation() case is handled by
1235+
// dumpIfNoStorage.
1236+
&& V->needsManagedAllocation())
1237+
V->dump();
1238+
return Interpreter::kSuccess;
1239+
}
12101240
}
12111241
return Interpreter::kSuccess;
12121242
}
@@ -1355,6 +1385,10 @@ namespace cling {
13551385
<< i << " of " << numberOfTransactions << "\n";
13561386
return;
13571387
}
1388+
// Mark Interpreter as not having loaded 'RuntimePrintValue.h'
1389+
if (T == m_CachedTrns[kPrintValueTransaction])
1390+
m_CachedTrns[kPrintValueTransaction] = nullptr;
1391+
13581392
unload(*T);
13591393
}
13601394
}

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::TransactionMerge M(Interp, true);
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)