mirror of
https://github.com/intel/llvm.git
synced 2026-01-16 05:32:28 +08:00
[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
This commit is contained in:
@@ -280,6 +280,23 @@ protected:
|
||||
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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,6 +9,7 @@ add_lldb_library(lldbPluginExpressionParserClang
|
||||
ClangExpressionDeclMap.cpp
|
||||
ClangExpressionParser.cpp
|
||||
ClangExpressionSourceCode.cpp
|
||||
ClangExpressionUtil.cpp
|
||||
ClangExpressionVariable.cpp
|
||||
ClangExternalASTSourceCallbacks.cpp
|
||||
ClangFunctionCaller.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,
|
||||
|
||||
@@ -530,6 +530,23 @@ private:
|
||||
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 @@ private:
|
||||
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.
|
||||
///
|
||||
|
||||
@@ -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 @@ public:
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,9 +78,19 @@ protected:
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
/// 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 @@ public:
|
||||
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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -198,6 +198,10 @@ private:
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
CXXFLAGS_EXTRAS := -std=c++14 -O0 -g
|
||||
|
||||
include Makefile.rules
|
||||
@@ -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"))
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user