233 changes: 188 additions & 45 deletions lldb/source/Expression/Materializer.cpp

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions lldb/source/Expression/UserExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_lldb_library(lldbPluginExpressionParserClang
ClangExpressionDeclMap.cpp
ClangExpressionParser.cpp
ClangExpressionSourceCode.cpp
ClangExpressionUtil.cpp
ClangExpressionVariable.cpp
ClangExternalASTSourceCallbacks.cpp
ClangFunctionCaller.cpp
Expand Down
173 changes: 154 additions & 19 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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))
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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++.
Expand All @@ -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());
}
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 27 additions & 0 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.cpp
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionUtil.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
38 changes: 36 additions & 2 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions lldb/test/API/commands/expression/expr_inside_lambda/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CXX_SOURCES := main.cpp

CXXFLAGS_EXTRAS := -std=c++14 -O0 -g

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -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"))
99 changes: 99 additions & 0 deletions lldb/test/API/commands/expression/expr_inside_lambda/main.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CXX_SOURCES := main.cpp
ENABLE_THREADS := YES

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Test that if we hit a breakpoint on a lambda capture
on two threads at the same time we stop only for
the correct one.
"""

import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *


class TestBreakOnLambdaCapture(TestBase):

NO_DEBUG_INFO_TESTCASE = True

def test_break_on_lambda_capture(self):
self.build()
self.main_source_file = lldb.SBFileSpec("main.cpp")

(target, process, main_thread, _) = lldbutil.run_to_source_breakpoint(self,
"First break", self.main_source_file)

# FIXME: This is working around a separate bug. If you hit a breakpoint and
# run an expression and it is the first expression you've ever run, on
# Darwin that will involve running the ObjC runtime parsing code, and we'll
# be in the middle of that when we do PerformAction on the other thread,
# which will cause the condition expression to fail. Calling another
# expression first works around this.
val_obj = main_thread.frame[0].EvaluateExpression("true")
self.assertSuccess(val_obj.GetError(), "Ran our expression successfully")
self.assertEqual(val_obj.value, "true", "Value was true.")

bkpt = target.BreakpointCreateBySourceRegex("Break here in the helper",
self.main_source_file);

bkpt.SetCondition("enable && usec == 1")
process.Continue()

# This is hard to test definitively, becuase it requires hitting
# a breakpoint on multiple threads at the same time. On Darwin, this
# will happen pretty much ever time we continue. What we are really
# asserting is that we only ever stop on one thread, so we approximate that
# by continuing 20 times and assert we only ever hit the first thread. Either
# this is a platform that only reports one hit at a time, in which case all
# this code is unused, or we actually didn't hit the other thread.

for idx in range(0, 20):
process.Continue()
for thread in process.threads:
if thread.id == main_thread.id:
self.assertEqual(thread.stop_reason, lldb.eStopReasonBreakpoint)
else:
self.assertEqual(thread.stop_reason, lldb.eStopReasonNone)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <chrono>
#include <cstdio>
#include <thread>

struct Foo {
bool enable = true;
uint32_t offset = 0;

void usleep_helper(uint32_t usec) {
[this, &usec] {
puts("Break here in the helper");
std::this_thread::sleep_for(
std::chrono::duration<unsigned int, std::milli>(offset + usec));
}();
}
};

void *background_thread(void *) {
Foo f;
for (;;) {
f.usleep_helper(2);
}
}

int main() {
std::puts("First break");
std::thread main_thread(background_thread, nullptr);
Foo f;
for (;;) {
f.usleep_helper(1);
}
}