Files
clang-p2996/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
Michael Buch 8184b252cd [LLDB][ClangExpression] Allow expression evaluation from within C++ Lambdas
This patch adds support for evaluating expressions which reference
a captured `this` from within the context of a C++ lambda expression.
Currently LLDB doesn't provide Clang with enough information to
determine that we're inside a lambda expression and are allowed to
access variables on a captured `this`; instead Clang simply fails
to parse the expression.

There are two problems to solve here:
1. Make sure `clang::Sema` doesn't reject the expression due to an
illegal member access.
2. Materialize all the captured variables/member variables required
to evaluate the expression.

To address (1), we currently import the outer structure's AST context
onto `$__lldb_class`, making the `contextClass` and the `NamingClass`
match, a requirement by `clang::Sema::BuildPossibleImplicitMemberExpr`.

To address (2), we inject all captured variables as locals into the
expression source code.

**Testing**

* Added API test
2022-07-22 08:02:09 +01:00

125 lines
5.6 KiB
Python

""" Test that evaluating expressions from within C++ lambdas works
Particularly, we test the case of capturing "this" and
using members of the captured object in expression evaluation
while we're on a breakpoint inside a lambda.
"""
import lldb
from lldbsuite.test.lldbtest import *
class ExprInsideLambdaTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
def expectExprError(self, expr : str, expected : str):
frame = self.thread.GetFrameAtIndex(0)
value = frame.EvaluateExpression(expr)
errmsg = value.GetError().GetCString()
self.assertIn(expected, errmsg)
def test_expr_inside_lambda(self):
"""Test that lldb evaluating expressions inside lambda expressions works correctly."""
self.build()
(target, process, self.thread, bkpt) = \
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.cpp"))
# Inside 'Foo::method'
# Check access to captured 'this'
self.expect_expr("class_var", result_type="int", result_value="109")
self.expect_expr("this->class_var", result_type="int", result_value="109")
# Check that captured shadowed variables take preference over the
# corresponding member variable
self.expect_expr("shadowed", result_type="int", result_value="5")
self.expect_expr("this->shadowed", result_type="int", result_value="-137")
# Check access to local captures
self.expect_expr("local_var", result_type="int", result_value="137")
self.expect_expr("*class_ptr", result_type="int", result_value="137")
# Check access to base class variables
self.expect_expr("base_var", result_type="int", result_value="14")
self.expect_expr("base_base_var", result_type="int", result_value="11")
# Check access to global variable
self.expect_expr("global_var", result_type="int", result_value="-5")
# Check access to multiple captures/member variables
self.expect_expr("(shadowed + this->shadowed) * (base_base_var + local_var - class_var)",
result_type="int", result_value="-5148")
# Check base-class function call
self.expect_expr("baz_virt()", result_type="int", result_value="2")
self.expect_expr("base_var", result_type="int", result_value="14")
self.expect_expr("this->shadowed", result_type="int", result_value="-1")
# 'p this' should yield 'struct Foo*'
frame = self.thread.GetFrameAtIndex(0)
outer_class_addr = frame.GetValueForVariablePath("this->this")
self.expect_expr("this", result_value=outer_class_addr.GetValue())
lldbutil.continue_to_breakpoint(process, bkpt)
# Inside 'nested_lambda'
# Check access to captured 'this'. Should still be 'struct Foo*'
self.expect_expr("class_var", result_type="int", result_value="109")
self.expect_expr("global_var", result_type="int", result_value="-5")
self.expect_expr("this", result_value=outer_class_addr.GetValue())
# Check access to captures
self.expect_expr("lambda_local_var", result_type="int", result_value="5")
self.expect_expr("local_var", result_type="int", result_value="137")
# Check access to variable in previous frame which we didn't capture
self.expectExprError("local_var_copy", "use of undeclared identifier")
lldbutil.continue_to_breakpoint(process, bkpt)
# By-ref mutates source variable
self.expect_expr("lambda_local_var", result_type="int", result_value="0")
# By-value doesn't mutate source variable
self.expect_expr("local_var_copy", result_type="int", result_value="136")
self.expect_expr("local_var", result_type="int", result_value="137")
lldbutil.continue_to_breakpoint(process, bkpt)
# Inside 'LocalLambdaClass::inner_method'
# Check access to captured 'this'
self.expect_expr("lambda_class_local", result_type="int", result_value="-12345")
self.expect_expr("this->lambda_class_local", result_type="int", result_value="-12345")
self.expect_expr("outer_ptr->class_var", result_type="int", result_value="109")
# 'p this' should yield 'struct LocalLambdaClass*'
frame = self.thread.GetFrameAtIndex(0)
local_class_addr = frame.GetValueForVariablePath("this->this")
self.assertNotEqual(local_class_addr, outer_class_addr)
self.expect_expr("this", result_value=local_class_addr.GetValue())
# Can still access global variable
self.expect_expr("global_var", result_type="int", result_value="-5")
# Check access to outer top-level structure's members
self.expectExprError("class_var", ("use of non-static data member"
" 'class_var' of 'Foo' from nested type"))
self.expectExprError("base_var", ("use of non-static data member"
" 'base_var'"))
self.expectExprError("local_var", ("use of non-static data member 'local_var'"
" of '' from nested type 'LocalLambdaClass'"))
# Inside non_capturing_method
lldbutil.continue_to_breakpoint(process, bkpt)
self.expect_expr("local", result_type="int", result_value="5")
self.expect_expr("local2", result_type="int", result_value="10")
self.expect_expr("local2 * local", result_type="int", result_value="50")
self.expectExprError("class_var", ("use of non-static data member"
" 'class_var' of 'Foo' from nested type"))