Index: lldb/include/lldb/Expression/IRInterpreter.h =================================================================== --- lldb/include/lldb/Expression/IRInterpreter.h +++ lldb/include/lldb/Expression/IRInterpreter.h @@ -11,6 +11,7 @@ #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Stream.h" +#include "lldb/Utility/Timeout.h" #include "lldb/lldb-public.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Pass.h" @@ -44,7 +45,8 @@ lldb_private::Status &error, lldb::addr_t stack_frame_bottom, lldb::addr_t stack_frame_top, - lldb_private::ExecutionContext &exe_ctx); + lldb_private::ExecutionContext &exe_ctx, + lldb_private::Timeout timeout); private: static bool supportsFunction(llvm::Function &llvm_function, Index: lldb/source/Expression/IRInterpreter.cpp =================================================================== --- lldb/source/Expression/IRInterpreter.cpp +++ lldb/source/Expression/IRInterpreter.cpp @@ -443,7 +443,8 @@ "Interpreter couldn't allocate memory"; static const char *memory_write_error = "Interpreter couldn't write to memory"; static const char *memory_read_error = "Interpreter couldn't read from memory"; -static const char *infinite_loop_error = "Interpreter ran for too many cycles"; +static const char *timeout_error = + "Reached timeout while interpreting expression"; static const char *too_many_functions_error = "Interpreter doesn't handle modules with multiple function bodies."; @@ -636,7 +637,8 @@ lldb_private::Status &error, lldb::addr_t stack_frame_bottom, lldb::addr_t stack_frame_top, - lldb_private::ExecutionContext &exe_ctx) { + lldb_private::ExecutionContext &exe_ctx, + lldb_private::Timeout timeout) { lldb_private::Log *log( lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); @@ -676,11 +678,23 @@ frame.MakeArgument(&*ai, ptr); } - uint32_t num_insts = 0; - frame.Jump(&function.front()); - while (frame.m_ii != frame.m_ie && (++num_insts < 4096)) { + using clock = std::chrono::steady_clock; + // Contains the time when the timeout for interpreting is reached (if there + // was a timeout specified). + llvm::Optional end_time; + if (timeout && timeout->count() != 0) + end_time = clock::now() + *timeout; + + while (frame.m_ii != frame.m_ie) { + // Reached the timeout, so stop interpreting. + if (end_time && clock::now() >= *end_time) { + error.SetErrorToGenericError(); + error.SetErrorString(timeout_error); + return false; + } + const Instruction *inst = &*frame.m_ii; LLDB_LOGF(log, "Interpreting %s", PrintValue(inst).c_str()); @@ -1528,11 +1542,5 @@ ++frame.m_ii; } - if (num_insts >= 4096) { - error.SetErrorToGenericError(); - error.SetErrorString(infinite_loop_error); - return false; - } - return false; } Index: lldb/source/Expression/LLVMUserExpression.cpp =================================================================== --- lldb/source/Expression/LLVMUserExpression.cpp +++ lldb/source/Expression/LLVMUserExpression.cpp @@ -116,9 +116,18 @@ function_stack_bottom = m_stack_frame_bottom; function_stack_top = m_stack_frame_top; + Timeout interpreter_timeout = options.GetTimeout(); + // FIXME: The IRInterpreter can't be interrupted by the user in any way, + // so disabling the expression timeout means that an expression with an + // infinite loop would just make LLDB unusable. For now, translate + // 'no timeout' to 30 seconds to prevent that. Users can still specify + // a longer timeout manually if they need more runtime. + if (!interpreter_timeout || interpreter_timeout->count() == 0) + interpreter_timeout = std::chrono::seconds(30); + IRInterpreter::Interpret(*module, *function, args, *m_execution_unit_sp, interpreter_error, function_stack_bottom, - function_stack_top, exe_ctx); + function_stack_top, exe_ctx, interpreter_timeout); if (!interpreter_error.Success()) { diagnostic_manager.Printf(eDiagnosticSeverityError, Index: lldb/test/API/commands/expression/ir-interpreter/TestIRInterpreter.py =================================================================== --- lldb/test/API/commands/expression/ir-interpreter/TestIRInterpreter.py +++ lldb/test/API/commands/expression/ir-interpreter/TestIRInterpreter.py @@ -3,6 +3,7 @@ """ +import time import unittest2 import lldb @@ -16,6 +17,42 @@ mydir = TestBase.compute_mydir(__file__) NO_DEBUG_INFO_TESTCASE = True + def time_expression(self, expr, options): + start = time.time() + res = self.target.EvaluateExpression(expr, options) + return res, time.time() - start + + def test_interpreter_timeout(self): + """Test the timeout logic in the IRInterpreter.""" + self.build() + self.target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(self.target, VALID_TARGET) + + # A non-trivial infinite loop. + inf_loop = "for (unsigned i = 0; i < 100; ++i) --i; 1" + timeout_error = "Timeout when interpreting expression" + options = lldb.SBExpressionOptions() + # This is an IRInterpreter specific test, so disable the JIT. + options.SetAllowJIT(False) + + # When interpreting, no timeout means 30 seconds. + # See LLVMUserExpression::DoExecute for the rationale. + options.SetTimeoutInMicroSeconds(0) + res, duration_sec = self.time_expression(inf_loop, options) + self.assertIn(timeout_error, str(res.GetError())) + # We shouldn't time out before 30 seconds. Depending on the machine + # load the expression might take quite some time, so give the time + # a generous upper bound. + self.assertGreaterEqual(duration_sec, 30) + self.assertLess(duration_sec, 120) + + # Try a simple one second timeout. + options.SetTimeoutInMicroSeconds(1000000) + res, duration_sec = self.time_expression(inf_loop, options) + self.assertIn(timeout_error, str(res.GetError())) + self.assertGreaterEqual(duration_sec, 1) + self.assertLess(duration_sec, 30) + def setUp(self): # Call super's setUp(). TestBase.setUp(self)