[Lldb-commits] [lldb] 8184b25 - [LLDB][ClangExpression] Allow expression evaluation from within C++ Lambdas

Michael Buch via lldb-commits lldb-commits at lists.llvm.org
Fri Jul 22 00:02:53 PDT 2022


Author: Michael Buch
Date: 2022-07-22T08:02:09+01:00
New Revision: 8184b252cdab2fbe44f766d6de28b29ebe4c8753

URL: https://github.com/llvm/llvm-project/commit/8184b252cdab2fbe44f766d6de28b29ebe4c8753
DIFF: https://github.com/llvm/llvm-project/commit/8184b252cdab2fbe44f766d6de28b29ebe4c8753.diff

LOG: [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

Added: 
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h
    lldb/test/API/commands/expression/expr_inside_lambda/Makefile
    lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
    lldb/test/API/commands/expression/expr_inside_lambda/main.cpp

Modified: 
    lldb/include/lldb/Expression/UserExpression.h
    lldb/source/Expression/UserExpression.cpp
    lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h
    lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Expression/UserExpression.h b/lldb/include/lldb/Expression/UserExpression.h
index 3874a60e06f09..2d62fa37a24c2 100644
--- a/lldb/include/lldb/Expression/UserExpression.h
+++ b/lldb/include/lldb/Expression/UserExpression.h
@@ -280,6 +280,23 @@ class UserExpression : public Expression {
   static lldb::addr_t GetObjectPointer(lldb::StackFrameSP frame_sp,
                                        ConstString &object_name, Status &err);
 
+  /// Return ValueObject for a given variable name in the current stack frame
+  ///
+  /// \param[in] frame Current stack frame. When passed a 'nullptr', this
+  ///                  function returns an empty ValueObjectSP.
+  ///
+  /// \param[in] object_name Name of the variable in the current stack frame
+  ///                        for which we want the ValueObjectSP.
+  ///
+  /// \param[out] err Status object which will get set on error.
+  ///
+  /// \returns On success returns a ValueObjectSP corresponding to the variable
+  ///          with 'object_name' in the current 'frame'. Otherwise, returns
+  ///          'nullptr' (and sets the error status parameter 'err').
+  static lldb::ValueObjectSP
+  GetObjectPointerValueObject(lldb::StackFrameSP frame,
+                              ConstString const &object_name, Status &err);
+
   /// Populate m_in_cplusplus_method and m_in_objectivec_method based on the
   /// environment.
 

diff  --git a/lldb/source/Expression/UserExpression.cpp b/lldb/source/Expression/UserExpression.cpp
index f821603f03e56..186e414e68793 100644
--- a/lldb/source/Expression/UserExpression.cpp
+++ b/lldb/source/Expression/UserExpression.cpp
@@ -98,28 +98,34 @@ bool UserExpression::MatchesContext(ExecutionContext &exe_ctx) {
   return LockAndCheckContext(exe_ctx, target_sp, process_sp, frame_sp);
 }
 
-lldb::addr_t UserExpression::GetObjectPointer(lldb::StackFrameSP frame_sp,
-                                              ConstString &object_name,
-                                              Status &err) {
+lldb::ValueObjectSP UserExpression::GetObjectPointerValueObject(
+    lldb::StackFrameSP frame_sp, ConstString const &object_name, Status &err) {
   err.Clear();
 
   if (!frame_sp) {
     err.SetErrorStringWithFormat(
         "Couldn't load '%s' because the context is incomplete",
         object_name.AsCString());
-    return LLDB_INVALID_ADDRESS;
+    return {};
   }
 
   lldb::VariableSP var_sp;
   lldb::ValueObjectSP valobj_sp;
 
-  valobj_sp = frame_sp->GetValueForVariableExpressionPath(
+  return frame_sp->GetValueForVariableExpressionPath(
       object_name.GetStringRef(), lldb::eNoDynamicValues,
       StackFrame::eExpressionPathOptionCheckPtrVsMember |
           StackFrame::eExpressionPathOptionsNoFragileObjcIvar |
           StackFrame::eExpressionPathOptionsNoSyntheticChildren |
           StackFrame::eExpressionPathOptionsNoSyntheticArrayRange,
       var_sp, err);
+}
+
+lldb::addr_t UserExpression::GetObjectPointer(lldb::StackFrameSP frame_sp,
+                                              ConstString &object_name,
+                                              Status &err) {
+  auto valobj_sp =
+      GetObjectPointerValueObject(std::move(frame_sp), object_name, err);
 
   if (!err.Success() || !valobj_sp.get())
     return LLDB_INVALID_ADDRESS;

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
index 04f6cdf9d9bd5..e1c55dcfc3641 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
+++ b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
@@ -9,6 +9,7 @@ add_lldb_library(lldbPluginExpressionParserClang
   ClangExpressionDeclMap.cpp
   ClangExpressionParser.cpp
   ClangExpressionSourceCode.cpp
+  ClangExpressionUtil.cpp
   ClangExpressionVariable.cpp
   ClangExternalASTSourceCallbacks.cpp
   ClangFunctionCaller.cpp

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
index 4305a99823437..6ba03dad98d1a 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
@@ -9,10 +9,13 @@
 #include "ClangExpressionDeclMap.h"
 
 #include "ClangASTSource.h"
+#include "ClangExpressionUtil.h"
+#include "ClangExpressionVariable.h"
 #include "ClangModulesDeclVendor.h"
 #include "ClangPersistentVariables.h"
 #include "ClangUtil.h"
 
+#include "NameSearchContext.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
 #include "lldb/Core/Address.h"
 #include "lldb/Core/Module.h"
@@ -44,6 +47,7 @@
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/RegisterValue.h"
 #include "lldb/Utility/Status.h"
+#include "lldb/lldb-private-types.h"
 #include "lldb/lldb-private.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
@@ -62,6 +66,24 @@ using namespace clang;
 
 static const char *g_lldb_local_vars_namespace_cstr = "$__lldb_local_vars";
 
+namespace {
+/// A lambda is represented by Clang as an artifical class whose
+/// members are the lambda captures. If we capture a 'this' pointer,
+/// the artifical class will contain a member variable named 'this'.
+/// The function returns a ValueObject for the captured 'this' if such
+/// member exists. If no 'this' was captured, return a nullptr.
+lldb::ValueObjectSP GetCapturedThisValueObject(StackFrame *frame) {
+  assert(frame);
+
+  if (auto thisValSP = frame->FindVariable(ConstString("this")))
+    if (auto thisThisValSP =
+            thisValSP->GetChildMemberWithName(ConstString("this"), true))
+      return thisThisValSP;
+
+  return nullptr;
+}
+} // namespace
+
 ClangExpressionDeclMap::ClangExpressionDeclMap(
     bool keep_result_in_memory,
     Materializer::PersistentVariableDelegate *result_delegate,
@@ -394,6 +416,10 @@ bool ClangExpressionDeclMap::AddValueToStruct(const NamedDecl *decl,
       else if (parser_vars->m_lldb_var)
         offset = m_parser_vars->m_materializer->AddVariable(
             parser_vars->m_lldb_var, err);
+      else if (parser_vars->m_lldb_valobj_provider) {
+        offset = m_parser_vars->m_materializer->AddValueObject(
+            name, parser_vars->m_lldb_valobj_provider, err);
+      }
     }
 
     if (!err.Success())
@@ -795,6 +821,28 @@ void ClangExpressionDeclMap::LookUpLldbClass(NameSearchContext &context) {
       TypeSystemClang::DeclContextGetAsCXXMethodDecl(function_decl_ctx);
 
   if (method_decl) {
+    if (auto capturedThis = GetCapturedThisValueObject(frame)) {
+      // We're inside a lambda and we captured a 'this'.
+      // Import the outer class's AST instead of the
+      // (unnamed) lambda structure AST so unqualified
+      // member lookups are understood by the Clang parser.
+      //
+      // If we're in a lambda which didn't capture 'this',
+      // $__lldb_class will correspond to the lambda closure
+      // AST and references to captures will resolve like
+      // regular member varaiable accesses do.
+      TypeFromUser pointee_type =
+          capturedThis->GetCompilerType().GetPointeeType();
+
+      LLDB_LOG(log,
+               "  CEDM::FEVD Adding captured type ({0} for"
+               " $__lldb_class: {1}",
+               capturedThis->GetTypeName(), capturedThis->GetName());
+
+      AddContextClassType(context, pointee_type);
+      return;
+    }
+
     clang::CXXRecordDecl *class_decl = method_decl->getParent();
 
     QualType class_qual_type(class_decl->getTypeForDecl(), 0);
@@ -1053,6 +1101,30 @@ bool ClangExpressionDeclMap::LookupLocalVariable(
       context.m_found_variable = true;
     }
   }
+
+  // We're in a local_var_lookup but haven't found any local variables
+  // so far. When performing a variable lookup from within the context of
+  // a lambda, we count the lambda captures as local variables. Thus,
+  // see if we captured any variables with the requested 'name'.
+  if (!variable_found) {
+    auto find_capture = [](ConstString varname,
+                           StackFrame *frame) -> ValueObjectSP {
+      if (auto lambda = ClangExpressionUtil::GetLambdaValueObject(frame)) {
+        if (auto capture = lambda->GetChildMemberWithName(varname, true)) {
+          return capture;
+        }
+      }
+
+      return nullptr;
+    };
+
+    if (auto capture = find_capture(name, frame)) {
+      variable_found = true;
+      context.m_found_variable = true;
+      AddOneVariable(context, std::move(capture), std::move(find_capture));
+    }
+  }
+
   return variable_found;
 }
 
@@ -1493,25 +1565,15 @@ bool ClangExpressionDeclMap::GetVariableValue(VariableSP &var,
   return true;
 }
 
-void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context,
-                                            VariableSP var,
-                                            ValueObjectSP valobj) {
-  assert(m_parser_vars.get());
-
-  Log *log = GetLog(LLDBLog::Expressions);
-
-  TypeFromUser ut;
-  TypeFromParser pt;
-  Value var_location;
-
-  if (!GetVariableValue(var, var_location, &ut, &pt))
-    return;
-
+ClangExpressionVariable::ParserVars *
+ClangExpressionDeclMap::AddExpressionVariable(NameSearchContext &context,
+                                              TypeFromParser const &pt,
+                                              ValueObjectSP valobj) {
   clang::QualType parser_opaque_type =
       QualType::getFromOpaquePtr(pt.GetOpaqueQualType());
 
   if (parser_opaque_type.isNull())
-    return;
+    return nullptr;
 
   if (const clang::Type *parser_type = parser_opaque_type.getTypePtr()) {
     if (const TagType *tag_type = dyn_cast<TagType>(parser_type))
@@ -1538,16 +1600,89 @@ void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context,
   entity->EnableParserVars(GetParserID());
   ClangExpressionVariable::ParserVars *parser_vars =
       entity->GetParserVars(GetParserID());
+
   parser_vars->m_named_decl = var_decl;
-  parser_vars->m_llvm_value = nullptr;
-  parser_vars->m_lldb_value = var_location;
-  parser_vars->m_lldb_var = var;
 
   if (is_reference)
     entity->m_flags |= ClangExpressionVariable::EVTypeIsReference;
 
+  return parser_vars;
+}
+
+void ClangExpressionDeclMap::AddOneVariable(
+    NameSearchContext &context, ValueObjectSP valobj,
+    ValueObjectProviderTy valobj_provider) {
+  assert(m_parser_vars.get());
+  assert(valobj);
+
+  Log *log = GetLog(LLDBLog::Expressions);
+
+  Value var_location = valobj->GetValue();
+
+  TypeFromUser user_type = valobj->GetCompilerType();
+
+  TypeSystemClang *clang_ast =
+      llvm::dyn_cast_or_null<TypeSystemClang>(user_type.GetTypeSystem());
+
+  if (!clang_ast) {
+    LLDB_LOG(log, "Skipped a definition because it has no Clang AST");
+    return;
+  }
+
+  TypeFromParser parser_type = GuardedCopyType(user_type);
+
+  if (!parser_type) {
+    LLDB_LOG(log,
+             "Couldn't copy a variable's type into the parser's AST context");
+
+    return;
+  }
+
+  if (var_location.GetContextType() == Value::ContextType::Invalid)
+    var_location.SetCompilerType(parser_type);
+
+  ClangExpressionVariable::ParserVars *parser_vars =
+      AddExpressionVariable(context, parser_type, valobj);
+
+  if (!parser_vars)
+    return;
+
   LLDB_LOG(log, "  CEDM::FEVD Found variable {0}, returned\n{1} (original {2})",
-           decl_name, ClangUtil::DumpDecl(var_decl), ClangUtil::ToString(ut));
+           context.m_decl_name, ClangUtil::DumpDecl(parser_vars->m_named_decl),
+           ClangUtil::ToString(user_type));
+
+  parser_vars->m_llvm_value = nullptr;
+  parser_vars->m_lldb_value = std::move(var_location);
+  parser_vars->m_lldb_valobj_provider = std::move(valobj_provider);
+}
+
+void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context,
+                                            VariableSP var,
+                                            ValueObjectSP valobj) {
+  assert(m_parser_vars.get());
+
+  Log *log = GetLog(LLDBLog::Expressions);
+
+  TypeFromUser ut;
+  TypeFromParser pt;
+  Value var_location;
+
+  if (!GetVariableValue(var, var_location, &ut, &pt))
+    return;
+
+  ClangExpressionVariable::ParserVars *parser_vars =
+      AddExpressionVariable(context, pt, std::move(valobj));
+
+  if (!parser_vars)
+    return;
+
+  LLDB_LOG(log, "  CEDM::FEVD Found variable {0}, returned\n{1} (original {2})",
+           context.m_decl_name, ClangUtil::DumpDecl(parser_vars->m_named_decl),
+           ClangUtil::ToString(ut));
+
+  parser_vars->m_llvm_value = nullptr;
+  parser_vars->m_lldb_value = var_location;
+  parser_vars->m_lldb_var = var;
 }
 
 void ClangExpressionDeclMap::AddOneVariable(NameSearchContext &context,

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
index f968f859cc720..bf7646ccaedf0 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
@@ -530,6 +530,23 @@ class ClangExpressionDeclMap : public ClangASTSource {
                         TypeFromUser *found_type = nullptr,
                         TypeFromParser *parser_type = nullptr);
 
+  /// Use the NameSearchContext to generate a Decl for the given LLDB
+  /// ValueObject, and put it in the list of found entities.
+  ///
+  /// Helper function used by the other AddOneVariable APIs.
+  ///
+  /// \param[in,out] context
+  ///     The NameSearchContext to use when constructing the Decl.
+  ///
+  /// \param[in] pt
+  ///     The CompilerType of the variable we're adding a Decl for.
+  ///
+  /// \param[in] var
+  ///     The LLDB ValueObject that needs a Decl.
+  ClangExpressionVariable::ParserVars *
+  AddExpressionVariable(NameSearchContext &context, TypeFromParser const &pt,
+                        lldb::ValueObjectSP valobj);
+
   /// Use the NameSearchContext to generate a Decl for the given LLDB
   /// Variable, and put it in the Tuple list.
   ///
@@ -544,6 +561,20 @@ class ClangExpressionDeclMap : public ClangASTSource {
   void AddOneVariable(NameSearchContext &context, lldb::VariableSP var,
                       lldb::ValueObjectSP valobj);
 
+  /// Use the NameSearchContext to generate a Decl for the given ValueObject
+  /// and put it in the list of found entities.
+  ///
+  /// \param[in,out] context
+  ///     The NameSearchContext to use when constructing the Decl.
+  ///
+  /// \param[in] valobj
+  ///     The ValueObject that needs a Decl.
+  ///
+  /// \param[in] valobj_provider Callback that fetches a ValueObjectSP
+  ///            from the specified frame
+  void AddOneVariable(NameSearchContext &context, lldb::ValueObjectSP valobj,
+                      ValueObjectProviderTy valobj_provider);
+
   /// Use the NameSearchContext to generate a Decl for the given persistent
   /// variable, and put it in the list of found entities.
   ///

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
index 5168f637c4431..56c00b35ba11b 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
@@ -8,6 +8,8 @@
 
 #include "ClangExpressionSourceCode.h"
 
+#include "ClangExpressionUtil.h"
+
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/FileManager.h"
 #include "clang/Basic/SourceManager.h"
@@ -27,6 +29,7 @@
 #include "lldb/Target/StackFrame.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/StreamString.h"
+#include "lldb/lldb-forward.h"
 
 using namespace lldb_private;
 
@@ -200,6 +203,34 @@ class TokenVerifier {
     return m_tokens.find(token) != m_tokens.end();
   }
 };
+
+// If we're evaluating from inside a lambda that captures a 'this' pointer,
+// add a "using" declaration to 'stream' for each capture used in the
+// expression (tokenized by 'verifier').
+//
+// If no 'this' capture exists, generate no using declarations. Instead
+// capture lookups will get resolved by the same mechanism as class member
+// variable lookup. That's because Clang generates an unnamed structure
+// representing the lambda closure whose members are the captured variables.
+void AddLambdaCaptureDecls(StreamString &stream, StackFrame *frame,
+                           TokenVerifier const &verifier) {
+  assert(frame);
+
+  if (auto thisValSP = ClangExpressionUtil::GetLambdaValueObject(frame)) {
+    uint32_t numChildren = thisValSP->GetNumChildren();
+    for (uint32_t i = 0; i < numChildren; ++i) {
+      auto childVal = thisValSP->GetChildAtIndex(i, true);
+      ConstString childName(childVal ? childVal->GetName() : ConstString(""));
+
+      if (!childName.IsEmpty() && verifier.hasToken(childName.GetStringRef()) &&
+          childName != "this") {
+        stream.Printf("using $__lldb_local_vars::%s;\n",
+                      childName.GetCString());
+      }
+    }
+  }
+}
+
 } // namespace
 
 TokenVerifier::TokenVerifier(std::string body) {
@@ -264,16 +295,24 @@ TokenVerifier::TokenVerifier(std::string body) {
   }
 }
 
-void ClangExpressionSourceCode::AddLocalVariableDecls(
-    const lldb::VariableListSP &var_list_sp, StreamString &stream,
-    const std::string &expr) const {
+void ClangExpressionSourceCode::AddLocalVariableDecls(StreamString &stream,
+                                                      const std::string &expr,
+                                                      StackFrame *frame) const {
+  assert(frame);
   TokenVerifier tokens(expr);
 
+  lldb::VariableListSP var_list_sp = frame->GetInScopeVariableList(false, true);
+
   for (size_t i = 0; i < var_list_sp->GetSize(); i++) {
     lldb::VariableSP var_sp = var_list_sp->GetVariableAtIndex(i);
 
     ConstString var_name = var_sp->GetName();
 
+    if (var_name == "this" && m_wrap_kind == WrapKind::CppMemberFunction) {
+      AddLambdaCaptureDecls(stream, frame, tokens);
+
+      continue;
+    }
 
     // We can check for .block_descriptor w/o checking for langauge since this
     // is not a valid identifier in either C or C++.
@@ -288,9 +327,6 @@ void ClangExpressionSourceCode::AddLocalVariableDecls(
     if ((var_name == "self" || var_name == "_cmd") && is_objc)
       continue;
 
-    if (var_name == "this" && m_wrap_kind == WrapKind::CppMemberFunction)
-      continue;
-
     stream.Printf("using $__lldb_local_vars::%s;\n", var_name.AsCString());
   }
 }
@@ -376,10 +412,8 @@ bool ClangExpressionSourceCode::GetText(
 
     if (add_locals)
       if (target->GetInjectLocalVariables(&exe_ctx)) {
-        lldb::VariableListSP var_list_sp =
-            frame->GetInScopeVariableList(false, true);
-        AddLocalVariableDecls(var_list_sp, lldb_local_var_decls,
-                              force_add_all_locals ? "" : m_body);
+        AddLocalVariableDecls(lldb_local_var_decls,
+                              force_add_all_locals ? "" : m_body, frame);
       }
   }
 

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
index 54ae837fb30ff..f721bb2f319e1 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
@@ -78,9 +78,19 @@ class ClangExpressionSourceCode : public ExpressionSourceCode {
                             Wrapping wrap, WrapKind wrap_kind);
 
 private:
-  void AddLocalVariableDecls(const lldb::VariableListSP &var_list_sp,
-                             StreamString &stream,
-                             const std::string &expr) const;
+  /// Writes "using" declarations for local variables into the specified stream.
+  ///
+  /// Behaviour is undefined if 'frame == nullptr'.
+  ///
+  /// \param[out] stream Stream that this function generates "using"
+  ///             declarations into.
+  ///
+  /// \param[in]  expr Expression source that we're evaluating.
+  ///
+  /// \param[in]  frame StackFrame which carries information about the local
+  ///             variables that we're generating "using" declarations for.
+  void AddLocalVariableDecls(StreamString &stream, const std::string &expr,
+                             StackFrame *frame) const;
 
   /// String marking the start of the user expression.
   std::string m_start_marker;

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp
new file mode 100644
index 0000000000000..9b490e1c036e6
--- /dev/null
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp
@@ -0,0 +1,27 @@
+//===-- ClangExpressionUtil.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangExpressionUtil.h"
+
+#include "lldb/Core/ValueObject.h"
+#include "lldb/Target/StackFrame.h"
+#include "lldb/Utility/ConstString.h"
+
+namespace lldb_private {
+namespace ClangExpressionUtil {
+lldb::ValueObjectSP GetLambdaValueObject(StackFrame *frame) {
+  assert(frame);
+
+  if (auto this_val_sp = frame->FindVariable(ConstString("this")))
+    if (this_val_sp->GetChildMemberWithName(ConstString("this"), true))
+      return this_val_sp;
+
+  return nullptr;
+}
+} // namespace ClangExpressionUtil
+} // namespace lldb_private

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h
new file mode 100644
index 0000000000000..fb8b857256c0b
--- /dev/null
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h
@@ -0,0 +1,30 @@
+//===-- ClangExpressionUtil.h -----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_CLANGEXPRESSIONUTIL_H
+#define LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_CLANGEXPRESSIONUTIL_H
+
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+namespace ClangExpressionUtil {
+/// Returns a ValueObject for the lambda class in the current frame
+///
+/// To represent a lambda, Clang generates an artificial class
+/// whose members are the captures and whose operator() is the
+/// lambda implementation. If we capture a 'this' pointer,
+/// the artifical class will contain a member variable named 'this'.
+///
+/// This method returns the 'this' pointer to the artificial lambda
+/// class if a real 'this' was captured. Otherwise, returns nullptr.
+lldb::ValueObjectSP GetLambdaValueObject(StackFrame *frame);
+
+} // namespace ClangExpressionUtil
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_CLANGEXPRESSIONHELPER_H

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h
index 7bb68e78373f0..c7d9e05269fa3 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionVariable.h
@@ -116,7 +116,7 @@ class ClangExpressionVariable : public ExpressionVariable {
   /// The following values should not live beyond parsing
   class ParserVars {
   public:
-    ParserVars() : m_lldb_value(), m_lldb_var() {}
+    ParserVars() = default;
 
     const clang::NamedDecl *m_named_decl =
         nullptr; ///< The Decl corresponding to this variable
@@ -129,6 +129,12 @@ class ClangExpressionVariable : public ExpressionVariable {
     const lldb_private::Symbol *m_lldb_sym =
         nullptr; ///< The original symbol for this
                  /// variable, if it was a symbol
+
+    /// Callback that provides a ValueObject for the
+    /// specified frame. Used by the materializer for
+    /// re-fetching ValueObjects when materializing
+    /// ivars.
+    ValueObjectProviderTy m_lldb_valobj_provider;
   };
 
 private:

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 78b8bf11220ae..7145e7804e685 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -872,6 +872,34 @@ bool ClangUserExpression::Complete(ExecutionContext &exe_ctx,
   return true;
 }
 
+lldb::addr_t ClangUserExpression::GetCppObjectPointer(
+    lldb::StackFrameSP frame_sp, ConstString &object_name, Status &err) {
+  auto valobj_sp =
+      GetObjectPointerValueObject(std::move(frame_sp), object_name, err);
+
+  // We're inside a C++ class method. This could potentially be an unnamed
+  // lambda structure. If the lambda captured a "this", that should be
+  // the object pointer.
+  if (auto thisChildSP =
+          valobj_sp->GetChildMemberWithName(ConstString("this"), true)) {
+    valobj_sp = thisChildSP;
+  }
+
+  if (!err.Success() || !valobj_sp.get())
+    return LLDB_INVALID_ADDRESS;
+
+  lldb::addr_t ret = valobj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
+
+  if (ret == LLDB_INVALID_ADDRESS) {
+    err.SetErrorStringWithFormat(
+        "Couldn't load '%s' because its value couldn't be evaluated",
+        object_name.AsCString());
+    return LLDB_INVALID_ADDRESS;
+  }
+
+  return ret;
+}
+
 bool ClangUserExpression::AddArguments(ExecutionContext &exe_ctx,
                                        std::vector<lldb::addr_t> &args,
                                        lldb::addr_t struct_address,
@@ -906,8 +934,14 @@ bool ClangUserExpression::AddArguments(ExecutionContext &exe_ctx,
           address_type != eAddressTypeLoad)
         object_ptr_error.SetErrorString("Can't get context object's "
                                         "debuggee address");
-    } else
-      object_ptr = GetObjectPointer(frame_sp, object_name, object_ptr_error);
+    } else {
+      if (m_in_cplusplus_method) {
+        object_ptr =
+            GetCppObjectPointer(frame_sp, object_name, object_ptr_error);
+      } else {
+        object_ptr = GetObjectPointer(frame_sp, object_name, object_ptr_error);
+      }
+    }
 
     if (!object_ptr_error.Success()) {
       exe_ctx.GetTargetRef().GetDebugger().GetAsyncOutputStream()->Printf(

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
index 30cdd2f3e990e..4d5458f1807d3 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -198,6 +198,10 @@ class ClangUserExpression : public LLVMUserExpression {
                         ExecutionContext &exe_ctx,
                         std::vector<std::string> modules_to_import,
                         bool for_completion);
+
+  lldb::addr_t GetCppObjectPointer(lldb::StackFrameSP frame,
+                                   ConstString &object_name, Status &err);
+
   /// Defines how the current expression should be wrapped.
   ClangExpressionSourceCode::WrapKind GetWrapKind() const;
   bool SetupPersistentState(DiagnosticManager &diagnostic_manager,

diff  --git a/lldb/test/API/commands/expression/expr_inside_lambda/Makefile b/lldb/test/API/commands/expression/expr_inside_lambda/Makefile
new file mode 100644
index 0000000000000..2bf27e03bf505
--- /dev/null
+++ b/lldb/test/API/commands/expression/expr_inside_lambda/Makefile
@@ -0,0 +1,5 @@
+CXX_SOURCES := main.cpp
+
+CXXFLAGS_EXTRAS := -std=c++14 -O0 -g
+
+include Makefile.rules

diff  --git a/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py b/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
new file mode 100644
index 0000000000000..9b8937aaa0c23
--- /dev/null
+++ b/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
@@ -0,0 +1,124 @@
+""" 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"))

diff  --git a/lldb/test/API/commands/expression/expr_inside_lambda/main.cpp b/lldb/test/API/commands/expression/expr_inside_lambda/main.cpp
new file mode 100644
index 0000000000000..3808f3616bffc
--- /dev/null
+++ b/lldb/test/API/commands/expression/expr_inside_lambda/main.cpp
@@ -0,0 +1,99 @@
+#include <cassert>
+#include <cstdio>
+
+namespace {
+int global_var = -5;
+} // namespace
+
+struct Baz {
+  virtual ~Baz() = default;
+
+  virtual int baz_virt() = 0;
+
+  int base_base_var = 12;
+};
+
+struct Bar : public Baz {
+  virtual ~Bar() = default;
+
+  virtual int baz_virt() override {
+    base_var = 10;
+    return 1;
+  }
+
+  int base_var = 15;
+};
+
+struct Foo : public Bar {
+  int class_var = 9;
+  int shadowed = -137;
+  int *class_ptr;
+
+  virtual ~Foo() = default;
+
+  virtual int baz_virt() override {
+    shadowed = -1;
+    return 2;
+  }
+
+  void method() {
+    int local_var = 137;
+    int shadowed;
+    class_ptr = &local_var;
+    auto lambda = [&shadowed, this, &local_var,
+                   local_var_copy = local_var]() mutable {
+      int lambda_local_var = 5;
+      shadowed = 5;
+      class_var = 109;
+      --base_var;
+      --base_base_var;
+      std::puts("break here");
+
+      auto nested_lambda = [this, &lambda_local_var, local_var] {
+        std::puts("break here");
+        lambda_local_var = 0;
+      };
+
+      nested_lambda();
+      --local_var_copy;
+      std::puts("break here");
+
+      struct LocalLambdaClass {
+        int lambda_class_local = -12345;
+        Foo *outer_ptr;
+
+        void inner_method() {
+          auto lambda = [this] {
+            std::puts("break here");
+            lambda_class_local = -2;
+            outer_ptr->class_var *= 2;
+          };
+
+          lambda();
+        }
+      };
+
+      LocalLambdaClass l;
+      l.outer_ptr = this;
+      l.inner_method();
+    };
+    lambda();
+  }
+
+  void non_capturing_method() {
+    int local = 5;
+    int local2 = 10;
+
+    class_var += [=] {
+      std::puts("break here");
+      return local + local2;
+    }();
+  }
+};
+
+int main() {
+  Foo f;
+  f.method();
+  f.non_capturing_method();
+  return global_var;
+}


        


More information about the lldb-commits mailing list