[Lldb-commits] [lldb] [lldb][Expression] Emit hint to use --cpp-ignore-context-qualifiers (PR #177927)
Michael Buch via lldb-commits
lldb-commits at lists.llvm.org
Fri Jan 30 03:24:56 PST 2026
https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/177927
>From 3c91007a8726ff43a455a5e113ee47438f819966 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 21 Jan 2026 16:12:03 +0000
Subject: [PATCH 1/8] [lldb][Expression] Make __lldb_expr function qualifiers
match source context
This was originally removed in `8bdcd522510f923185cdfaec66c4a78d0a0d38c0` under the assumption this wasn't required (i.e., LLDB should just always pretend it's in a mutable context). But since function qualifiers affect overloading in C++, this assumption can lead to unexpected expression evaluator behaviour. Instead, in this patch we try to add function qualifiers to `$__lldb_class::$__lldb_expr` that resemble the qualifiers in the method that we're stopped in.
---
.../Clang/ClangExpressionDeclMap.cpp | 8 +-
.../Clang/ClangExpressionSourceCode.cpp | 43 +++++--
.../TestExprInsideLambdas.py | 4 +-
.../API/lang/cpp/const_this/TestConstThis.py | 10 +-
.../const_method/Makefile | 3 +
.../const_method/TestExprInConstMethod.py | 105 ++++++++++++++++++
.../const_method/main.cpp | 49 ++++++++
.../const_volatile_method/Makefile | 3 +
.../TestExprInConstVolatileMethod.py | 60 ++++++++++
.../const_volatile_method/main.cpp | 43 +++++++
.../cv_qualified_objects/Makefile | 3 +
.../TestExprOnCVQualifiedObjects.py | 21 ++++
.../cv_qualified_objects/main.cpp | 25 +++++
.../fixit/Makefile | 3 +
.../fixit/TestExprInConstMethodWithFixit.py | 42 +++++++
.../fixit/main.cpp | 22 ++++
.../non_const_method/Makefile | 3 +
.../TestExprInNonConstMethod.py | 82 ++++++++++++++
.../non_const_method/main.cpp | 49 ++++++++
.../template_const_method/Makefile | 3 +
.../TestExprInTemplateConstMethod.py | 105 ++++++++++++++++++
.../template_const_method/main.cpp | 51 +++++++++
.../template_non_const_method/Makefile | 3 +
.../TestExprInTemplateNonConstMethod.py | 82 ++++++++++++++
.../template_non_const_method/main.cpp | 49 ++++++++
lldb/test/API/lang/cpp/this/TestCPPThis.py | 16 ++-
26 files changed, 864 insertions(+), 23 deletions(-)
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/main.cpp
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/main.cpp
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/TestExprOnCVQualifiedObjects.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/main.cpp
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/main.cpp
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/main.cpp
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/main.cpp
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/Makefile
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
create mode 100644 lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/main.cpp
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
index 24a5dc5362964..41d2d2cb23146 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
@@ -844,6 +844,12 @@ void ClangExpressionDeclMap::LookUpLldbClass(NameSearchContext &context) {
QualType class_qual_type = m_ast_context->getCanonicalTagType(class_decl);
+ // The synthesized __lldb_expr will adopt the qualifiers from this class
+ // type. Make sure we use the qualifiers of the method that we're currently
+ // stopped in.
+ class_qual_type.addFastQualifiers(
+ method_decl->getMethodQualifiers().getFastQualifiers());
+
TypeFromUser class_user_type(
class_qual_type.getAsOpaquePtr(),
function_decl_ctx.GetTypeSystem()->weak_from_this());
@@ -1991,7 +1997,7 @@ void ClangExpressionDeclMap::AddContextClassType(NameSearchContext &context,
std::array<CompilerType, 1> args{void_clang_type.GetPointerType()};
CompilerType method_type = m_clang_ast_context->CreateFunctionType(
- void_clang_type, args, false, 0);
+ void_clang_type, args, false, ut.GetTypeQualifiers());
const bool is_virtual = false;
const bool is_static = false;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
index cfe187ffc4114..ca4199faa3841 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
@@ -10,6 +10,7 @@
#include "ClangExpressionUtil.h"
+#include "clang/AST/TypeBase.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
@@ -189,6 +190,28 @@ static void AddMacros(const DebugMacros *dm, CompileUnit *comp_unit,
}
}
+/// Return qualifers of the current C++ method.
+static clang::Qualifiers GetFrameCVQualifiers(StackFrame *frame) {
+ if (!frame)
+ return {};
+
+ auto this_sp = frame->FindVariable(ConstString("this"));
+ if (!this_sp)
+ return {};
+
+ // Lambdas that capture 'this' have a member variable called 'this'. The class
+ // context of __lldb_expr for a lambda is the class type of the 'this' capture
+ // (not the anonymous lambda structure). So use the qualifiers of the captured
+ // 'this'.
+ if (auto this_this_sp = this_sp->GetChildMemberWithName("this"))
+ return clang::Qualifiers::fromCVRMask(
+ this_this_sp->GetCompilerType().GetPointeeType().GetTypeQualifiers());
+
+ // Not in a lambda. Return 'this' qualifiers.
+ return clang::Qualifiers::fromCVRMask(
+ this_sp->GetCompilerType().GetPointeeType().GetTypeQualifiers());
+}
+
lldb_private::ClangExpressionSourceCode::ClangExpressionSourceCode(
llvm::StringRef filename, llvm::StringRef name, llvm::StringRef prefix,
llvm::StringRef body, Wrapping wrap, WrapKind wrap_kind)
@@ -463,15 +486,17 @@ bool ClangExpressionSourceCode::GetText(
lldb_local_var_decls.GetData(), tagged_body.c_str());
break;
case WrapKind::CppMemberFunction:
- wrap_stream.Printf("%s"
- "void \n"
- "$__lldb_class::%s(void *$__lldb_arg) \n"
- "{ \n"
- " %s; \n"
- "%s"
- "} \n",
- module_imports.c_str(), m_name.c_str(),
- lldb_local_var_decls.GetData(), tagged_body.c_str());
+ wrap_stream.Printf(
+ "%s"
+ "void \n"
+ "$__lldb_class::%s(void *$__lldb_arg) %s \n"
+ "{ \n"
+ " %s; \n"
+ "%s"
+ "} \n",
+ module_imports.c_str(), m_name.c_str(),
+ GetFrameCVQualifiers(exe_ctx.GetFramePtr()).getAsString().c_str(),
+ lldb_local_var_decls.GetData(), tagged_body.c_str());
break;
case WrapKind::ObjCInstanceMethod:
wrap_stream.Printf(
diff --git a/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py b/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
index e35cfa6a289a7..0a7683b310f43 100644
--- a/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
+++ b/lldb/test/API/commands/expression/expr_inside_lambda/TestExprInsideLambdas.py
@@ -127,8 +127,8 @@ def test_expr_inside_lambda(self):
# 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("local", result_type="const int", result_value="5")
+ self.expect_expr("local2", result_type="const int", result_value="10")
self.expect_expr("local2 * local", result_type="int", result_value="50")
self.expectExprError(
diff --git a/lldb/test/API/lang/cpp/const_this/TestConstThis.py b/lldb/test/API/lang/cpp/const_this/TestConstThis.py
index 4b7d3aadb62ab..c2df61fde2b58 100644
--- a/lldb/test/API/lang/cpp/const_this/TestConstThis.py
+++ b/lldb/test/API/lang/cpp/const_this/TestConstThis.py
@@ -11,10 +11,9 @@ def run_class_tests(self):
# Expression not referencing context class.
self.expect_expr("1 + 1", result_type="int", result_value="2")
# Referencing context class.
- # FIXME: This and the expression below should return const types.
- self.expect_expr("member", result_type="int", result_value="3")
+ self.expect_expr("member", result_type="const int", result_value="3")
# Check the type of context class.
- self.expect_expr("this", result_type="ContextClass *")
+ self.expect_expr("this", result_type="const ContextClass *")
def test_member_func(self):
self.build()
@@ -36,10 +35,9 @@ def run_template_class_tests(self):
# Expression not referencing context class.
self.expect_expr("1 + 1", result_type="int", result_value="2")
# Referencing context class.
- # FIXME: This and the expression below should return const types.
- self.expect_expr("member", result_type="int", result_value="4")
+ self.expect_expr("member", result_type="const int", result_value="4")
# Check the type of context class.
- self.expect_expr("this", result_type="TemplatedContextClass<int> *")
+ self.expect_expr("this", result_type="const TemplatedContextClass<int> *")
def test_template_member_func(self):
self.build()
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
new file mode 100644
index 0000000000000..0d8f477dce1fd
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
@@ -0,0 +1,105 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "Break: const_method begin", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr("bar()", result_value="2", result_type="int")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect(
+ "expression m_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+ self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: const_method no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect(
+ "expression x = 7.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: const_method mutable no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("x = 7.0; x", result_value="7")
+
+ lldbutil.continue_to_source_breakpoint(
+ self, process, "Break: const_method lambda", lldb.SBFileSpec("main.cpp")
+ )
+
+ # FIXME: mutating this capture should be disallowed in a non-mutable lambda.
+ self.expect_expr("y = 8.0")
+ self.expect_expr("bar()", result_value="2", result_type="int")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect(
+ "expression m_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+ self.expect_expr("m_mem", result_value="-2")
+ self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: const_method mutable lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("y = 9.0")
+ self.expect_expr("bar()", result_value="2", result_type="int")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect(
+ "expression m_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+ self.expect_expr("m_mem", result_value="-2")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/main.cpp
new file mode 100644
index 0000000000000..7cb74767b458a
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/main.cpp
@@ -0,0 +1,49 @@
+#include <cassert>
+#include <cstdio>
+
+struct Foo {
+ double bar() { return 5.0; }
+
+ int bar() const { return 2; }
+
+ int const_method() const {
+ auto x = bar();
+ assert(x == 2);
+ std::puts("Break: const_method begin");
+
+ [x] {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: const_method no-this lambda");
+ }();
+
+ [x]() mutable {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: const_method mutable no-this lambda");
+ }();
+
+ [this, y = x] {
+ auto x = bar() + y;
+ std::puts("Break: const_method lambda");
+ }();
+
+ [this, y = x]() mutable {
+ auto x = bar() + y;
+ std::puts("Break: const_method mutable lambda");
+ }();
+
+ return 120;
+ }
+
+ float m_mem = -2.0;
+ const float m_const_mem = -3.0;
+};
+
+int main() {
+ const Foo f;
+ f.bar();
+
+ Foo f2;
+ f2.bar();
+
+ return Foo{}.const_method();
+}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
new file mode 100644
index 0000000000000..f20b9d8164841
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
@@ -0,0 +1,60 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "Break here: const", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr("bar()", result_type="double", result_value="5")
+ self.expect_expr("const_volatile_method()")
+ self.expect_expr("const_method()")
+ self.expect(
+ "expression volatile_method()",
+ error=True,
+ substrs=["has type 'const Foo'", "but function is not marked const"],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self, process, "Break here: volatile", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr(
+ "bar()", result_type="const char *", result_summary='"volatile_bar"'
+ )
+ self.expect_expr("const_volatile_method()")
+ self.expect(
+ "expression const_method()",
+ error=True,
+ substrs=["has type 'volatile Foo'", "but function is not marked volatile"],
+ )
+ self.expect_expr("volatile_method()")
+
+ lldbutil.continue_to_source_breakpoint(
+ self, process, "Break here: const volatile", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr("bar()", result_type="int", result_value="2")
+ self.expect_expr("other_cv_method()")
+
+ self.expect(
+ "expression const_method()",
+ error=True,
+ substrs=[
+ "has type 'const volatile Foo'",
+ "but function is not marked const or volatile",
+ ],
+ )
+ self.expect(
+ "expression volatile_method()",
+ error=True,
+ substrs=[
+ "has type 'const volatile Foo'",
+ "but function is not marked const or volatile",
+ ],
+ )
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/main.cpp
new file mode 100644
index 0000000000000..da0f7e7d1be95
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/main.cpp
@@ -0,0 +1,43 @@
+#include <cassert>
+#include <cstdio>
+
+struct Foo {
+ double bar() const { return 5.0; }
+ const char *bar() volatile { return "volatile_bar"; }
+ int bar() volatile const { return 2; }
+
+ int volatile_method() volatile {
+ std::puts("Break here: volatile");
+ return 0;
+ }
+ int const_method() const {
+ std::puts("Break here: const");
+ return 0;
+ }
+ int other_cv_method() const volatile { return 20; }
+
+ int const_volatile_method() const volatile {
+ auto x = bar();
+ assert(x == 2);
+ other_cv_method();
+
+ std::puts("Break here: const volatile");
+
+ return 120;
+ }
+};
+
+int main() {
+ const Foo f;
+ f.bar();
+ f.const_method();
+
+ volatile Foo f2;
+ f2.bar();
+ f2.volatile_method();
+
+ const volatile Foo f3;
+ f3.bar();
+
+ return Foo{}.const_volatile_method();
+}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/TestExprOnCVQualifiedObjects.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/TestExprOnCVQualifiedObjects.py
new file mode 100644
index 0000000000000..0f661471df749
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/TestExprOnCVQualifiedObjects.py
@@ -0,0 +1,21 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ lldbutil.run_to_source_breakpoint(
+ self,
+ "Break here",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("f.bar()", result_type="double", result_value="5")
+ self.expect_expr("cf.bar()", result_type="int", result_value="2")
+ self.expect_expr("vf.bar()", result_type="short", result_value="8")
+ self.expect_expr(
+ "cvf.bar()", result_type="const char *", result_summary='"volatile"'
+ )
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/main.cpp
new file mode 100644
index 0000000000000..eb7f8b82f2695
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/cv_qualified_objects/main.cpp
@@ -0,0 +1,25 @@
+#include <cstdio>
+
+struct Foo {
+ double bar() { return 5.0; }
+ int bar() const { return 2; }
+ short bar() volatile { return 8; }
+ char const *bar() const volatile { return "volatile"; }
+
+ float m_mem = -2.0;
+ const float m_const_mem = -3.0;
+};
+
+int main() {
+ Foo f;
+ const Foo cf;
+ volatile Foo vf;
+ const volatile Foo cvf;
+
+ f.bar();
+ cf.bar();
+ vf.bar();
+ cvf.bar();
+
+ std::puts("Break here");
+}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py
new file mode 100644
index 0000000000000..3c239c89d0ca7
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py
@@ -0,0 +1,42 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "Break here", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect(
+ "expression m_bar->method()",
+ error=True,
+ substrs=[
+ "member reference type 'const Bar' is not a pointer",
+ "but function is not marked const",
+ ],
+ )
+
+ # Two fix-its
+ self.expect(
+ "expression -- m_bar->method() + m_bar->method()",
+ error=True,
+ substrs=[
+ "member reference type 'const Bar' is not a pointer",
+ "but function is not marked const",
+ "member reference type 'const Bar' is not a pointer",
+ "but function is not marked const",
+ ],
+ )
+
+ self.expect(
+ "expression m_bar->method() + blah",
+ error=True,
+ substrs=[
+ "member reference type 'const Bar' is not a pointer",
+ "but function is not marked const",
+ ],
+ )
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/main.cpp
new file mode 100644
index 0000000000000..1dab96e2986da
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/main.cpp
@@ -0,0 +1,22 @@
+#include <cassert>
+#include <cstdio>
+
+struct Bar {
+ void method() {}
+};
+
+struct Foo {
+ int const_method() const {
+ std::puts("Break here");
+
+ return 120;
+ }
+
+ Bar m_bar;
+};
+
+int main() {
+ Foo{}.m_bar.method();
+
+ return Foo{}.const_method();
+}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
new file mode 100644
index 0000000000000..835487f3bb7e0
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
@@ -0,0 +1,82 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "Break: non_const_method begin", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr("bar()", result_value="5", result_type="double")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: non_const_method no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect(
+ "expression x = 7.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: non_const_method mutable no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("x = 7.0; x", result_value="7")
+
+ lldbutil.continue_to_source_breakpoint(
+ self, process, "Break: non_const_method lambda", lldb.SBFileSpec("main.cpp")
+ )
+
+ # FIXME: mutating this capture should be disallowed in a non-mutable lambda.
+ self.expect_expr("y = 8.0")
+ self.expect_expr("bar()", result_value="5", result_type="double")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect_expr("m_mem = 2.0; m_mem", result_value="2")
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: non_const_method mutable lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("y = 9.0")
+ self.expect_expr("bar()", result_value="5", result_type="double")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect_expr("m_mem = 4.0; m_mem", result_value="4")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/main.cpp
new file mode 100644
index 0000000000000..141f63b6f2f24
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/main.cpp
@@ -0,0 +1,49 @@
+#include <cassert>
+#include <cstdio>
+
+struct Foo {
+ double bar() { return 5.0; }
+
+ int bar() const { return 2; }
+
+ int non_const_method() {
+ auto x = bar();
+ assert(x == 5.0);
+ std::puts("Break: non_const_method begin");
+
+ [x] {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: non_const_method no-this lambda");
+ }();
+
+ [x]() mutable {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: non_const_method mutable no-this lambda");
+ }();
+
+ [this, y = x] {
+ auto x = bar() + y;
+ std::puts("Break: non_const_method lambda");
+ }();
+
+ [this, y = x]() mutable {
+ auto x = bar() + y;
+ std::puts("Break: non_const_method mutable lambda");
+ }();
+
+ return 120;
+ }
+
+ float m_mem = -2.0;
+ const float m_const_mem = -3.0;
+};
+
+int main() {
+ const Foo f;
+ f.bar();
+
+ Foo f2;
+ f2.bar();
+
+ return Foo{}.non_const_method();
+}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
new file mode 100644
index 0000000000000..0d8f477dce1fd
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
@@ -0,0 +1,105 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "Break: const_method begin", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr("bar()", result_value="2", result_type="int")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect(
+ "expression m_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+ self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: const_method no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect(
+ "expression x = 7.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: const_method mutable no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("x = 7.0; x", result_value="7")
+
+ lldbutil.continue_to_source_breakpoint(
+ self, process, "Break: const_method lambda", lldb.SBFileSpec("main.cpp")
+ )
+
+ # FIXME: mutating this capture should be disallowed in a non-mutable lambda.
+ self.expect_expr("y = 8.0")
+ self.expect_expr("bar()", result_value="2", result_type="int")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect(
+ "expression m_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+ self.expect_expr("m_mem", result_value="-2")
+ self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: const_method mutable lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("y = 9.0")
+ self.expect_expr("bar()", result_value="2", result_type="int")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect(
+ "expression m_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+ self.expect_expr("m_mem", result_value="-2")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/main.cpp
new file mode 100644
index 0000000000000..8238f2a52f5aa
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/main.cpp
@@ -0,0 +1,51 @@
+#include <cassert>
+#include <cstdio>
+
+struct Foo {
+ double bar() { return 5.0; }
+
+ int bar() const { return 2; }
+
+ void non_const_method() {}
+
+ int template_const_method() const {
+ auto x = bar();
+ assert(x == 2);
+ std::puts("Break: const_method begin");
+
+ [x] {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: const_method no-this lambda");
+ }();
+
+ [x]() mutable {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: const_method mutable no-this lambda");
+ }();
+
+ [this, y = x] {
+ auto x = bar() + y;
+ std::puts("Break: const_method lambda");
+ }();
+
+ [this, y = x]() mutable {
+ auto x = bar() + y;
+ std::puts("Break: const_method mutable lambda");
+ }();
+
+ return 120;
+ }
+
+ float m_mem = -2.0;
+ const float m_const_mem = -3.0;
+};
+
+int main() {
+ const Foo f;
+ f.bar();
+
+ Foo f2;
+ f2.bar();
+
+ return Foo{}.template_const_method();
+}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/Makefile b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
new file mode 100644
index 0000000000000..835487f3bb7e0
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
@@ -0,0 +1,82 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ def test(self):
+ self.build()
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "Break: non_const_method begin", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.expect_expr("bar()", result_value="5", result_type="double")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: non_const_method no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect(
+ "expression x = 7.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: non_const_method mutable no-this lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("x = 7.0; x", result_value="7")
+
+ lldbutil.continue_to_source_breakpoint(
+ self, process, "Break: non_const_method lambda", lldb.SBFileSpec("main.cpp")
+ )
+
+ # FIXME: mutating this capture should be disallowed in a non-mutable lambda.
+ self.expect_expr("y = 8.0")
+ self.expect_expr("bar()", result_value="5", result_type="double")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect_expr("m_mem = 2.0; m_mem", result_value="2")
+
+ lldbutil.continue_to_source_breakpoint(
+ self,
+ process,
+ "Break: non_const_method mutable lambda",
+ lldb.SBFileSpec("main.cpp"),
+ )
+
+ self.expect_expr("y = 9.0")
+ self.expect_expr("bar()", result_value="5", result_type="double")
+ self.expect(
+ "expression m_const_mem = 2.0",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member",
+ "with const-qualified type",
+ ],
+ )
+ self.expect_expr("m_mem = 4.0; m_mem", result_value="4")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/main.cpp b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/main.cpp
new file mode 100644
index 0000000000000..1632555c69d01
--- /dev/null
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/main.cpp
@@ -0,0 +1,49 @@
+#include <cassert>
+#include <cstdio>
+
+struct Foo {
+ double bar() { return 5.0; }
+
+ int bar() const { return 2; }
+
+ int template_non_const_method() {
+ auto x = bar();
+ assert(x == 5.0);
+ std::puts("Break: non_const_method begin");
+
+ [x] {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: non_const_method no-this lambda");
+ }();
+
+ [x]() mutable {
+ std::puts("Keep on multiple lines...");
+ std::puts("Break: non_const_method mutable no-this lambda");
+ }();
+
+ [this, y = x] {
+ auto x = bar() + y;
+ std::puts("Break: non_const_method lambda");
+ }();
+
+ [this, y = x]() mutable {
+ auto x = bar() + y;
+ std::puts("Break: non_const_method mutable lambda");
+ }();
+
+ return 120;
+ }
+
+ float m_mem = -2.0;
+ const float m_const_mem = -3.0;
+};
+
+int main() {
+ const Foo f;
+ f.bar();
+
+ Foo f2;
+ f2.bar();
+
+ return Foo{}.template_non_const_method();
+}
diff --git a/lldb/test/API/lang/cpp/this/TestCPPThis.py b/lldb/test/API/lang/cpp/this/TestCPPThis.py
index c9dcf30b53443..f127f80663578 100644
--- a/lldb/test/API/lang/cpp/this/TestCPPThis.py
+++ b/lldb/test/API/lang/cpp/this/TestCPPThis.py
@@ -1,6 +1,7 @@
"""
Tests that C++ member and static variables are available where they should be.
"""
+
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
@@ -37,18 +38,23 @@ def test_with_run_command(self):
self.runCmd("process continue")
- # This would be disallowed if we enforced const. But we don't.
- self.expect("expression -- m_a = 2", startstr="(int) $1 = 2")
+ self.expect(
+ "expression -- m_a = 2",
+ error=True,
+ substrs=[
+ "cannot assign to non-static data member within const member function"
+ ],
+ )
- self.expect("expression -- (int)getpid(); m_a", startstr="(int) $2 = 2")
+ self.expect("expression -- (int)getpid(); m_a", startstr="(const int) $1 = 3")
self.runCmd("process continue")
- self.expect("expression -- s_a", startstr="(int) $3 = 5")
+ self.expect("expression -- s_a", startstr="(int) $2 = 5")
self.runCmd("process continue")
- self.expect("expression -- m_a", startstr="(int) $4 = 2")
+ self.expect("expression -- m_a", startstr="(int) $3 = 3")
def set_breakpoint(self, line):
lldbutil.run_break_set_by_file_and_line(
>From 2843854c202383f1f09eb33af40e59ad82a06626 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 26 Jan 2026 08:45:56 +0000
Subject: [PATCH 2/8] [lldb][test] Add SBExpressionOptions parameter to
expect_expr
(cherry picked from commit aaf3b8d90de29d548a718bb4b5415bcdb55ba164)
---
lldb/packages/Python/lldbsuite/test/lldbtest.py | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 6bb4516948da5..6034eca3b93f2 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -2575,6 +2575,7 @@ def expect_expr(
result_value=None,
result_type=None,
result_children=None,
+ options=None,
):
"""
Evaluates the given expression and verifies the result.
@@ -2584,6 +2585,7 @@ def expect_expr(
:param result_type: The type that the expression result should have. None if the type should not be checked.
:param result_children: The expected children of the expression result
as a list of ValueChecks. None if the children shouldn't be checked.
+ :param options: Expression evaluation options. None if a default set of options should be used.
"""
self.assertTrue(
expr.strip() == expr,
@@ -2591,13 +2593,15 @@ def expect_expr(
)
frame = self.frame()
- options = lldb.SBExpressionOptions()
- # Disable fix-its that tests don't pass by accident.
- options.SetAutoApplyFixIts(False)
+ if not options:
+ options = lldb.SBExpressionOptions()
- # Set the usual default options for normal expressions.
- options.SetIgnoreBreakpoints(True)
+ # Disable fix-its that tests don't pass by accident.
+ options.SetAutoApplyFixIts(False)
+
+ # Set the usual default options for normal expressions.
+ options.SetIgnoreBreakpoints(True)
if self.frame().IsValid():
options.SetLanguage(frame.GuessLanguage())
>From 0d0fb12a4714367fb16b650b9981516ab41bb234 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 23 Jan 2026 14:49:56 +0000
Subject: [PATCH 3/8] [lldb][Expression] Add --ignore-const-context expression
evaluation option
Depends on:
* https://github.com/llvm/llvm-project/pull/177920
* https://github.com/llvm/llvm-project/pull/177922
In https://github.com/llvm/llvm-project/pull/177922 we make expressions
run in C++ member functions honor the function qualifiers of the current
stop context. E.g., this means we can no longer run non-const member
functions when stopped in a const-member function.
To ensure users can still do this if they really need/want to, we
provide an option to not honor the qualifiers at all, leaving the
`__lldb_expr` as the least qualified, allowing it to call any
function/mutate any members.
---
.../interface/SBExpressionOptionsDocstrings.i | 6 ++++
lldb/include/lldb/API/SBExpressionOptions.h | 4 +++
lldb/include/lldb/Target/Target.h | 14 ++++++++
lldb/source/API/SBExpressionOptions.cpp | 12 +++++++
.../Commands/CommandObjectExpression.cpp | 5 +++
.../source/Commands/CommandObjectExpression.h | 1 +
lldb/source/Commands/Options.td | 8 +++++
.../Clang/ClangExpressionDeclMap.cpp | 9 +++--
.../Clang/ClangExpressionDeclMap.h | 15 +++++++-
.../Clang/ClangExpressionSourceCode.cpp | 34 +++++++++++--------
.../Clang/ClangExpressionSourceCode.h | 4 +--
.../Clang/ClangUserExpression.cpp | 9 ++---
.../Clang/ClangUserExpression.h | 8 ++---
.../Clang/ClangUtilityFunction.cpp | 2 +-
.../const_method/TestExprInConstMethod.py | 30 +++++++++++++++-
.../TestExprInConstVolatileMethod.py | 25 ++++++++++++++
.../TestExprInNonConstMethod.py | 4 +++
.../TestExprInTemplateConstMethod.py | 8 ++++-
.../TestExprInTemplateNonConstMethod.py | 4 +++
lldb/test/API/lang/cpp/this/TestCPPThis.py | 7 ++--
.../Expression/ClangExpressionDeclMapTest.cpp | 2 +-
21 files changed, 177 insertions(+), 34 deletions(-)
diff --git a/lldb/bindings/interface/SBExpressionOptionsDocstrings.i b/lldb/bindings/interface/SBExpressionOptionsDocstrings.i
index 2bb562778db79..df69b56bf5cc4 100644
--- a/lldb/bindings/interface/SBExpressionOptionsDocstrings.i
+++ b/lldb/bindings/interface/SBExpressionOptionsDocstrings.i
@@ -61,3 +61,9 @@
%feature("docstring", "Sets whether to JIT an expression if it cannot be interpreted."
) lldb::SBExpressionOptions::SetAllowJIT;
+
+%feature("docstring", "Sets whether to ignore the CV-qualifiers of the scope during expression evaluation."
+) lldb::SBExpressionOptions::SetIgnoreContextQualifiers;
+
+%feature("docstring", "Gets whether to ignore the CV-qualifiers of the scope during expression evaluation."
+) lldb::SBExpressionOptions::GetIgnoreContextQualifiers;
diff --git a/lldb/include/lldb/API/SBExpressionOptions.h b/lldb/include/lldb/API/SBExpressionOptions.h
index a9e929a4c0bd9..d404d1d949bb5 100644
--- a/lldb/include/lldb/API/SBExpressionOptions.h
+++ b/lldb/include/lldb/API/SBExpressionOptions.h
@@ -107,6 +107,10 @@ class LLDB_API SBExpressionOptions {
// Sets whether we will JIT an expression if it cannot be interpreted
void SetAllowJIT(bool allow);
+ bool GetIgnoreContextQualifiers();
+
+ void SetIgnoreContextQualifiers(bool b = true);
+
protected:
lldb_private::EvaluateExpressionOptions *get() const;
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 812a638910b3b..c9012902465fd 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -481,6 +481,14 @@ class EvaluateExpressionOptions {
void SetIsForUtilityExpr(bool b) { m_running_utility_expression = b; }
+ bool GetIgnoreContextQualifiers() const {
+ return m_ignore_context_qualifierss;
+ }
+
+ void SetIgnoreContextQualifiers(bool val) {
+ m_ignore_context_qualifierss = val;
+ }
+
private:
ExecutionPolicy m_execution_policy = default_execution_policy;
SourceLanguage m_language;
@@ -518,6 +526,12 @@ class EvaluateExpressionOptions {
/// used for symbol/function lookup before any other context (except for
/// the module corresponding to the current frame).
SymbolContextList m_preferred_lookup_contexts;
+
+ /// If true, evaluates the expression without taking into account the
+ /// CV-qualifiers of the scope. E.g., this would permit calling a
+ /// non-const C++ method when stopped in a const-method (which would be
+ /// disallowed by C++ language rules).
+ bool m_ignore_context_qualifierss = false;
};
// Target
diff --git a/lldb/source/API/SBExpressionOptions.cpp b/lldb/source/API/SBExpressionOptions.cpp
index 15ed403eaaea1..86c0fc1ed3ca4 100644
--- a/lldb/source/API/SBExpressionOptions.cpp
+++ b/lldb/source/API/SBExpressionOptions.cpp
@@ -256,6 +256,18 @@ void SBExpressionOptions::SetAllowJIT(bool allow) {
: eExecutionPolicyNever);
}
+bool SBExpressionOptions::GetIgnoreContextQualifiers() {
+ LLDB_INSTRUMENT_VA(this);
+
+ return m_opaque_up->GetIgnoreContextQualifiers();
+}
+
+void SBExpressionOptions::SetIgnoreContextQualifiers(bool b) {
+ LLDB_INSTRUMENT_VA(this, b);
+
+ m_opaque_up->SetIgnoreContextQualifiers(b);
+}
+
EvaluateExpressionOptions *SBExpressionOptions::get() const {
return m_opaque_up.get();
}
diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp
index 4919bd3639d3e..ab1dcd7d82cbf 100644
--- a/lldb/source/Commands/CommandObjectExpression.cpp
+++ b/lldb/source/Commands/CommandObjectExpression.cpp
@@ -44,6 +44,9 @@ Status CommandObjectExpression::CommandOptions::SetOptionValue(
const int short_option = GetDefinitions()[option_idx].short_option;
switch (short_option) {
+ case 'Q':
+ ignore_context_qualifiers = true;
+ break;
case 'l':
language = Language::GetLanguageTypeFromString(option_arg);
if (language == eLanguageTypeUnknown) {
@@ -191,6 +194,7 @@ void CommandObjectExpression::CommandOptions::OptionParsingStarting(
top_level = false;
allow_jit = true;
suppress_persistent_result = eLazyBoolCalculate;
+ ignore_context_qualifiers = false;
}
llvm::ArrayRef<OptionDefinition>
@@ -213,6 +217,7 @@ CommandObjectExpression::CommandOptions::GetEvaluateExpressionOptions(
options.SetExecutionPolicy(
allow_jit ? EvaluateExpressionOptions::default_execution_policy
: lldb_private::eExecutionPolicyNever);
+ options.SetIgnoreContextQualifiers(ignore_context_qualifiers);
bool auto_apply_fixits;
if (this->auto_apply_fixits == eLazyBoolCalculate)
diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h
index 6fccf10e5dbc1..6d60a674e5708 100644
--- a/lldb/source/Commands/CommandObjectExpression.h
+++ b/lldb/source/Commands/CommandObjectExpression.h
@@ -57,6 +57,7 @@ class CommandObjectExpression : public CommandObjectRaw,
LanguageRuntimeDescriptionDisplayVerbosity m_verbosity;
LazyBool auto_apply_fixits;
LazyBool suppress_persistent_result;
+ bool ignore_context_qualifiers;
};
CommandObjectExpression(CommandInterpreter &interpreter);
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index d96354a39b8b8..c3dddd0f64c2c 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -778,6 +778,14 @@ let Command = "expression" in {
Desc<"Persist expression result in a variable for subsequent use. "
"Expression results will be labeled with $-prefixed variables, "
"e.g. $0, $1, etc.">;
+ def ignore_context_qualifiers
+ : Option<"ignore-context-qualifiers", "Q">,
+ Groups<[1, 2]>,
+ Desc<"When specified, evaluates the expression without taking into "
+ "account the type ${Q}ualifiers of the scope. E.g., this would "
+ "permit "
+ "calling a non-const C++ method when stopped in a const-method "
+ "(which would be disallowed by C++ language rules).">;
}
let Command = "frame diag" in {
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
index 41d2d2cb23146..8bc82368e7b9e 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
@@ -88,10 +88,12 @@ ClangExpressionDeclMap::ClangExpressionDeclMap(
bool keep_result_in_memory,
Materializer::PersistentVariableDelegate *result_delegate,
const lldb::TargetSP &target,
- const std::shared_ptr<ClangASTImporter> &importer, ValueObject *ctx_obj)
+ const std::shared_ptr<ClangASTImporter> &importer, ValueObject *ctx_obj,
+ bool ignore_context_qualifiers)
: ClangASTSource(target, importer), m_found_entities(), m_struct_members(),
m_keep_result_in_memory(keep_result_in_memory),
- m_result_delegate(result_delegate), m_ctx_obj(ctx_obj), m_parser_vars(),
+ m_result_delegate(result_delegate), m_ctx_obj(ctx_obj),
+ m_ignore_context_qualifiers(ignore_context_qualifiers), m_parser_vars(),
m_struct_vars() {
EnableStructVars();
}
@@ -1997,7 +1999,8 @@ void ClangExpressionDeclMap::AddContextClassType(NameSearchContext &context,
std::array<CompilerType, 1> args{void_clang_type.GetPointerType()};
CompilerType method_type = m_clang_ast_context->CreateFunctionType(
- void_clang_type, args, false, ut.GetTypeQualifiers());
+ void_clang_type, args, false,
+ m_ignore_context_qualifiers ? 0 : ut.GetTypeQualifiers());
const bool is_virtual = false;
const bool is_static = false;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
index dddc5a06c9051..8643882ed254d 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h
@@ -78,11 +78,18 @@ class ClangExpressionDeclMap : public ClangASTSource {
/// \param[in] ctx_obj
/// If not empty, then expression is evaluated in context of this object.
/// See the comment to `UserExpression::Evaluate` for details.
+ ///
+ /// \param[in] ignore_context_qualifiers
+ /// If \c true, evaluates the expression without taking into account the
+ /// CV-qualifiers of the scope. E.g., this would permit calling a
+ /// non-const C++ method when stopped in a const-method (which would be
+ /// disallowed by C++ language rules).
ClangExpressionDeclMap(
bool keep_result_in_memory,
Materializer::PersistentVariableDelegate *result_delegate,
const lldb::TargetSP &target,
- const std::shared_ptr<ClangASTImporter> &importer, ValueObject *ctx_obj);
+ const std::shared_ptr<ClangASTImporter> &importer, ValueObject *ctx_obj,
+ bool ignore_context_qualifiers);
/// Destructor
~ClangExpressionDeclMap() override;
@@ -306,6 +313,12 @@ class ClangExpressionDeclMap : public ClangASTSource {
///For details see the comment to
///`UserExpression::Evaluate`.
+ /// If \c true, evaluates the expression without taking into account the
+ /// CV-qualifiers of the scope. E.g., this would permit calling a
+ /// non-const C++ method when stopped in a const-method (which would be
+ /// disallowed by C++ language rules).
+ bool m_ignore_context_qualifiers = false;
+
/// The following values should not live beyond parsing
class ParserVars {
public:
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
index ca4199faa3841..c711367cb6177 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
@@ -363,9 +363,12 @@ void ClangExpressionSourceCode::AddLocalVariableDecls(StreamString &stream,
}
}
-bool ClangExpressionSourceCode::GetText(
- std::string &text, ExecutionContext &exe_ctx, bool add_locals,
- bool force_add_all_locals, llvm::ArrayRef<std::string> modules) const {
+bool ClangExpressionSourceCode::GetText(std::string &text,
+ ExecutionContext &exe_ctx,
+ bool add_locals,
+ bool force_add_all_locals,
+ llvm::ArrayRef<std::string> modules,
+ bool ignore_context_qualifiers) const {
const char *target_specific_defines = "typedef signed char BOOL;\n";
std::string module_macros;
llvm::raw_string_ostream module_macros_stream(module_macros);
@@ -486,17 +489,20 @@ bool ClangExpressionSourceCode::GetText(
lldb_local_var_decls.GetData(), tagged_body.c_str());
break;
case WrapKind::CppMemberFunction:
- wrap_stream.Printf(
- "%s"
- "void \n"
- "$__lldb_class::%s(void *$__lldb_arg) %s \n"
- "{ \n"
- " %s; \n"
- "%s"
- "} \n",
- module_imports.c_str(), m_name.c_str(),
- GetFrameCVQualifiers(exe_ctx.GetFramePtr()).getAsString().c_str(),
- lldb_local_var_decls.GetData(), tagged_body.c_str());
+ wrap_stream.Printf("%s"
+ "void \n"
+ "$__lldb_class::%s(void *$__lldb_arg) %s \n"
+ "{ \n"
+ " %s; \n"
+ "%s"
+ "} \n",
+ module_imports.c_str(), m_name.c_str(),
+ ignore_context_qualifiers
+ ? ""
+ : GetFrameCVQualifiers(exe_ctx.GetFramePtr())
+ .getAsString()
+ .c_str(),
+ lldb_local_var_decls.GetData(), tagged_body.c_str());
break;
case WrapKind::ObjCInstanceMethod:
wrap_stream.Printf(
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
index f721bb2f319e1..914d405139193 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.h
@@ -63,8 +63,8 @@ class ClangExpressionSourceCode : public ExpressionSourceCode {
///
/// \return true iff the source code was successfully generated.
bool GetText(std::string &text, ExecutionContext &exe_ctx, bool add_locals,
- bool force_add_all_locals,
- llvm::ArrayRef<std::string> modules) const;
+ bool force_add_all_locals, llvm::ArrayRef<std::string> modules,
+ bool ignore_context_qualifiers) const;
// Given a string returned by GetText, find the beginning and end of the body
// passed to CreateWrapped. Return true if the bounds could be found. This
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 2cbbae11bd18a..2436468569958 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -416,7 +416,8 @@ void ClangUserExpression::CreateSourceCode(
m_filename, prefix, m_expr_text, GetWrapKind()));
if (!m_source_code->GetText(m_transformed_text, exe_ctx, !m_ctx_obj,
- for_completion, modules_to_import)) {
+ for_completion, modules_to_import,
+ m_options.GetIgnoreContextQualifiers())) {
diagnostic_manager.PutString(lldb::eSeverityError,
"couldn't construct expression body");
return;
@@ -950,8 +951,8 @@ char ClangUserExpression::ClangUserExpressionHelper::ID;
void ClangUserExpression::ClangUserExpressionHelper::ResetDeclMap(
ExecutionContext &exe_ctx,
Materializer::PersistentVariableDelegate &delegate,
- bool keep_result_in_memory,
- ValueObject *ctx_obj) {
+ bool keep_result_in_memory, ValueObject *ctx_obj,
+ bool ignore_context_qualifiers) {
std::shared_ptr<ClangASTImporter> ast_importer;
auto *state = exe_ctx.GetTargetSP()->GetPersistentExpressionStateForLanguage(
lldb::eLanguageTypeC);
@@ -961,7 +962,7 @@ void ClangUserExpression::ClangUserExpressionHelper::ResetDeclMap(
}
m_expr_decl_map_up = std::make_unique<ClangExpressionDeclMap>(
keep_result_in_memory, &delegate, exe_ctx.GetTargetSP(), ast_importer,
- ctx_obj);
+ ctx_obj, ignore_context_qualifiers);
}
clang::ASTConsumer *
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
index 7c0c6a0147e2a..b3c31529446ad 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -71,8 +71,8 @@ class ClangUserExpression : public LLVMUserExpression {
void ResetDeclMap(ExecutionContext &exe_ctx,
Materializer::PersistentVariableDelegate &result_delegate,
- bool keep_result_in_memory,
- ValueObject *ctx_obj);
+ bool keep_result_in_memory, ValueObject *ctx_obj,
+ bool ignore_context_qualifiers);
/// Return the object that the parser should allow to access ASTs. May be
/// NULL if the ASTs do not need to be transformed.
@@ -167,8 +167,8 @@ class ClangUserExpression : public LLVMUserExpression {
Materializer::PersistentVariableDelegate &result_delegate,
bool keep_result_in_memory) {
m_type_system_helper.ResetDeclMap(exe_ctx, result_delegate,
- keep_result_in_memory,
- m_ctx_obj);
+ keep_result_in_memory, m_ctx_obj,
+ m_options.GetIgnoreContextQualifiers());
}
lldb::ExpressionVariableSP
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
index e6983066a12fa..112ce9be7bd1a 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
@@ -187,5 +187,5 @@ void ClangUtilityFunction::ClangUtilityFunctionHelper::ResetDeclMap(
}
m_expr_decl_map_up = std::make_unique<ClangExpressionDeclMap>(
keep_result_in_memory, nullptr, exe_ctx.GetTargetSP(), ast_importer,
- nullptr);
+ nullptr, /*ignore_context_qualifiers=*/false);
}
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
index 0d8f477dce1fd..d73b6563b29a6 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
@@ -27,6 +27,26 @@ def test(self):
"cannot assign to non-static data member within const member function"
],
)
+ self.expect_expr("m_mem", result_value="-2")
+
+ # Test short and long --ignore-context-qualifiers option.
+ self.expect(
+ "expression --ignore-context-qualifiers -- m_mem = 3.0",
+ error=False,
+ )
+ self.expect_expr("m_mem", result_value="3")
+
+ self.expect(
+ "expression -Q -- m_mem = 4.0",
+ error=False,
+ )
+ self.expect_expr("m_mem", result_value="4")
+
+ # Test --ignore-context-qualifiers via SBExpressionOptions.
+ options = lldb.SBExpressionOptions()
+ options.SetIgnoreContextQualifiers()
+ self.expect_expr("m_mem = -2.0; m_mem", options=options, result_value="-2")
+
self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
lldbutil.continue_to_source_breakpoint(
@@ -43,6 +63,9 @@ def test(self):
"cannot assign to non-static data member within const member function"
],
)
+ self.expect_expr("x", result_value="2")
+
+ self.expect_expr("x = -5; x", options=options, result_value="-5")
lldbutil.continue_to_source_breakpoint(
self,
@@ -76,6 +99,9 @@ def test(self):
],
)
self.expect_expr("m_mem", result_value="-2")
+
+ self.expect_expr("m_mem = -1; m_mem", options=options, result_value="-1")
+
self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
lldbutil.continue_to_source_breakpoint(
@@ -102,4 +128,6 @@ def test(self):
"cannot assign to non-static data member within const member function"
],
)
- self.expect_expr("m_mem", result_value="-2")
+ self.expect_expr("m_mem", result_value="-1")
+
+ self.expect_expr("m_mem = -2; m_mem", options=options, result_value="-2")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
index f20b9d8164841..0069b42f5da10 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
@@ -20,6 +20,16 @@ def test(self):
substrs=["has type 'const Foo'", "but function is not marked const"],
)
+ options = lldb.SBExpressionOptions()
+ options.SetIgnoreContextQualifiers()
+ options.SetIgnoreBreakpoints(True)
+ self.expect_expr("volatile_method()", options=options)
+ self.expect(
+ "expression --ignore-context-qualifiers -- bar()",
+ error=True,
+ substrs=["call to member function 'bar' is ambiguous"],
+ )
+
lldbutil.continue_to_source_breakpoint(
self, process, "Break here: volatile", lldb.SBFileSpec("main.cpp")
)
@@ -35,6 +45,13 @@ def test(self):
)
self.expect_expr("volatile_method()")
+ self.expect_expr("const_method()", options=options)
+ self.expect(
+ "expression --ignore-context-qualifiers -- bar()",
+ error=True,
+ substrs=["call to member function 'bar' is ambiguous"],
+ )
+
lldbutil.continue_to_source_breakpoint(
self, process, "Break here: const volatile", lldb.SBFileSpec("main.cpp")
)
@@ -58,3 +75,11 @@ def test(self):
"but function is not marked const or volatile",
],
)
+
+ self.expect_expr("const_method()", options=options)
+ self.expect_expr("volatile_method()", options=options)
+ self.expect(
+ "expression --ignore-context-qualifiers -- bar()",
+ error=True,
+ substrs=["call to member function 'bar' is ambiguous"],
+ )
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
index 835487f3bb7e0..78fe27508d10f 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
@@ -36,6 +36,10 @@ def test(self):
],
)
+ options = lldb.SBExpressionOptions()
+ options.SetIgnoreContextQualifiers()
+ self.expect_expr("x = 6.0; x", options=options, result_value="6")
+
lldbutil.continue_to_source_breakpoint(
self,
process,
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
index 0d8f477dce1fd..8489f09b69bba 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
@@ -29,6 +29,10 @@ def test(self):
)
self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
+ options = lldb.SBExpressionOptions()
+ options.SetIgnoreContextQualifiers()
+ self.expect_expr("m_mem = -2.0; m_mem", options=options, result_value="-2")
+
lldbutil.continue_to_source_breakpoint(
self,
process,
@@ -43,6 +47,7 @@ def test(self):
"cannot assign to non-static data member within const member function"
],
)
+ self.expect_expr("x = -7.0; x", options=options, result_value="-7")
lldbutil.continue_to_source_breakpoint(
self,
@@ -77,6 +82,7 @@ def test(self):
)
self.expect_expr("m_mem", result_value="-2")
self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
+ self.expect_expr("m_mem = -8.0; m_mem", options=options, result_value="-8")
lldbutil.continue_to_source_breakpoint(
self,
@@ -102,4 +108,4 @@ def test(self):
"cannot assign to non-static data member within const member function"
],
)
- self.expect_expr("m_mem", result_value="-2")
+ self.expect_expr("m_mem = -11.0; m_mem", options=options, result_value="-11")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
index 835487f3bb7e0..78fe27508d10f 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
@@ -36,6 +36,10 @@ def test(self):
],
)
+ options = lldb.SBExpressionOptions()
+ options.SetIgnoreContextQualifiers()
+ self.expect_expr("x = 6.0; x", options=options, result_value="6")
+
lldbutil.continue_to_source_breakpoint(
self,
process,
diff --git a/lldb/test/API/lang/cpp/this/TestCPPThis.py b/lldb/test/API/lang/cpp/this/TestCPPThis.py
index f127f80663578..5a59bf3631e82 100644
--- a/lldb/test/API/lang/cpp/this/TestCPPThis.py
+++ b/lldb/test/API/lang/cpp/this/TestCPPThis.py
@@ -47,14 +47,17 @@ def test_with_run_command(self):
)
self.expect("expression -- (int)getpid(); m_a", startstr="(const int) $1 = 3")
+ self.expect(
+ "expression --ignore-context-qualifiers -- m_a = 2", startstr="(int) $2 = 2"
+ )
self.runCmd("process continue")
- self.expect("expression -- s_a", startstr="(int) $2 = 5")
+ self.expect("expression -- s_a", startstr="(int) $3 = 5")
self.runCmd("process continue")
- self.expect("expression -- m_a", startstr="(int) $3 = 3")
+ self.expect("expression -- m_a", startstr="(int) $4 = 2")
def set_breakpoint(self, line):
lldbutil.run_break_set_by_file_and_line(
diff --git a/lldb/unittests/Expression/ClangExpressionDeclMapTest.cpp b/lldb/unittests/Expression/ClangExpressionDeclMapTest.cpp
index 1c07119d4497f..61905ee8df862 100644
--- a/lldb/unittests/Expression/ClangExpressionDeclMapTest.cpp
+++ b/lldb/unittests/Expression/ClangExpressionDeclMapTest.cpp
@@ -23,7 +23,7 @@ namespace {
struct FakeClangExpressionDeclMap : public ClangExpressionDeclMap {
FakeClangExpressionDeclMap(const std::shared_ptr<ClangASTImporter> &importer)
: ClangExpressionDeclMap(false, nullptr, lldb::TargetSP(), importer,
- nullptr) {
+ nullptr, /*ignore_context_qualifiers=*/false) {
m_holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast");
m_scratch_context = m_holder->GetAST();
}
>From 22281467869ac68d8e2463c232d72dbb8ec8ae80 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 30 Jan 2026 11:12:13 +0000
Subject: [PATCH 4/8] fixup! rename option to more c++ specific name
---
.../interface/SBExpressionOptionsDocstrings.i | 8 ++++----
lldb/include/lldb/API/SBExpressionOptions.h | 4 ++--
lldb/include/lldb/Target/Target.h | 12 +++++++-----
lldb/source/API/SBExpressionOptions.cpp | 8 ++++----
lldb/source/Commands/CommandObjectExpression.cpp | 6 +++---
lldb/source/Commands/CommandObjectExpression.h | 2 +-
lldb/source/Commands/Options.td | 9 ++++-----
.../ExpressionParser/Clang/ClangUserExpression.cpp | 2 +-
.../ExpressionParser/Clang/ClangUserExpression.h | 6 +++---
.../const_method/TestExprInConstMethod.py | 6 +++---
.../TestExprInConstVolatileMethod.py | 8 ++++----
.../non_const_method/TestExprInNonConstMethod.py | 2 +-
.../TestExprInTemplateConstMethod.py | 2 +-
.../TestExprInTemplateNonConstMethod.py | 2 +-
lldb/test/API/lang/cpp/this/TestCPPThis.py | 2 +-
15 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/lldb/bindings/interface/SBExpressionOptionsDocstrings.i b/lldb/bindings/interface/SBExpressionOptionsDocstrings.i
index df69b56bf5cc4..639c2ebddee01 100644
--- a/lldb/bindings/interface/SBExpressionOptionsDocstrings.i
+++ b/lldb/bindings/interface/SBExpressionOptionsDocstrings.i
@@ -62,8 +62,8 @@
%feature("docstring", "Sets whether to JIT an expression if it cannot be interpreted."
) lldb::SBExpressionOptions::SetAllowJIT;
-%feature("docstring", "Sets whether to ignore the CV-qualifiers of the scope during expression evaluation."
-) lldb::SBExpressionOptions::SetIgnoreContextQualifiers;
+%feature("docstring", "Sets whether to ignore the CV-qualifiers of the scope during expression evaluation in C++."
+) lldb::SBExpressionOptions::SetCppIgnoreContextQualifiers;
-%feature("docstring", "Gets whether to ignore the CV-qualifiers of the scope during expression evaluation."
-) lldb::SBExpressionOptions::GetIgnoreContextQualifiers;
+%feature("docstring", "Gets whether to ignore the CV-qualifiers of the scope during expression evaluation in C++."
+) lldb::SBExpressionOptions::GetCppIgnoreContextQualifiers;
diff --git a/lldb/include/lldb/API/SBExpressionOptions.h b/lldb/include/lldb/API/SBExpressionOptions.h
index d404d1d949bb5..286bc073e7417 100644
--- a/lldb/include/lldb/API/SBExpressionOptions.h
+++ b/lldb/include/lldb/API/SBExpressionOptions.h
@@ -107,9 +107,9 @@ class LLDB_API SBExpressionOptions {
// Sets whether we will JIT an expression if it cannot be interpreted
void SetAllowJIT(bool allow);
- bool GetIgnoreContextQualifiers();
+ bool GetCppIgnoreContextQualifiers();
- void SetIgnoreContextQualifiers(bool b = true);
+ void SetCppIgnoreContextQualifiers(bool b = true);
protected:
lldb_private::EvaluateExpressionOptions *get() const;
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index c9012902465fd..bfb9fb0aedd47 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -481,12 +481,12 @@ class EvaluateExpressionOptions {
void SetIsForUtilityExpr(bool b) { m_running_utility_expression = b; }
- bool GetIgnoreContextQualifiers() const {
- return m_ignore_context_qualifierss;
+ bool GetCppIgnoreContextQualifiers() const {
+ return m_cpp_ignore_context_qualifierss;
}
- void SetIgnoreContextQualifiers(bool val) {
- m_ignore_context_qualifierss = val;
+ void SetCppIgnoreContextQualifiers(bool val) {
+ m_cpp_ignore_context_qualifierss = val;
}
private:
@@ -531,7 +531,9 @@ class EvaluateExpressionOptions {
/// CV-qualifiers of the scope. E.g., this would permit calling a
/// non-const C++ method when stopped in a const-method (which would be
/// disallowed by C++ language rules).
- bool m_ignore_context_qualifierss = false;
+ ///
+ /// FIXME: move this to a language-specific dictionary of options.
+ bool m_cpp_ignore_context_qualifierss = false;
};
// Target
diff --git a/lldb/source/API/SBExpressionOptions.cpp b/lldb/source/API/SBExpressionOptions.cpp
index 86c0fc1ed3ca4..3f6b791f9cfae 100644
--- a/lldb/source/API/SBExpressionOptions.cpp
+++ b/lldb/source/API/SBExpressionOptions.cpp
@@ -256,16 +256,16 @@ void SBExpressionOptions::SetAllowJIT(bool allow) {
: eExecutionPolicyNever);
}
-bool SBExpressionOptions::GetIgnoreContextQualifiers() {
+bool SBExpressionOptions::GetCppIgnoreContextQualifiers() {
LLDB_INSTRUMENT_VA(this);
- return m_opaque_up->GetIgnoreContextQualifiers();
+ return m_opaque_up->GetCppIgnoreContextQualifiers();
}
-void SBExpressionOptions::SetIgnoreContextQualifiers(bool b) {
+void SBExpressionOptions::SetCppIgnoreContextQualifiers(bool b) {
LLDB_INSTRUMENT_VA(this, b);
- m_opaque_up->SetIgnoreContextQualifiers(b);
+ m_opaque_up->SetCppIgnoreContextQualifiers(b);
}
EvaluateExpressionOptions *SBExpressionOptions::get() const {
diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp
index ab1dcd7d82cbf..efc0df6cd738e 100644
--- a/lldb/source/Commands/CommandObjectExpression.cpp
+++ b/lldb/source/Commands/CommandObjectExpression.cpp
@@ -45,7 +45,7 @@ Status CommandObjectExpression::CommandOptions::SetOptionValue(
switch (short_option) {
case 'Q':
- ignore_context_qualifiers = true;
+ cpp_ignore_context_qualifiers = true;
break;
case 'l':
language = Language::GetLanguageTypeFromString(option_arg);
@@ -194,7 +194,7 @@ void CommandObjectExpression::CommandOptions::OptionParsingStarting(
top_level = false;
allow_jit = true;
suppress_persistent_result = eLazyBoolCalculate;
- ignore_context_qualifiers = false;
+ cpp_ignore_context_qualifiers = false;
}
llvm::ArrayRef<OptionDefinition>
@@ -217,7 +217,7 @@ CommandObjectExpression::CommandOptions::GetEvaluateExpressionOptions(
options.SetExecutionPolicy(
allow_jit ? EvaluateExpressionOptions::default_execution_policy
: lldb_private::eExecutionPolicyNever);
- options.SetIgnoreContextQualifiers(ignore_context_qualifiers);
+ options.SetCppIgnoreContextQualifiers(cpp_ignore_context_qualifiers);
bool auto_apply_fixits;
if (this->auto_apply_fixits == eLazyBoolCalculate)
diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h
index 6d60a674e5708..0439ddffce925 100644
--- a/lldb/source/Commands/CommandObjectExpression.h
+++ b/lldb/source/Commands/CommandObjectExpression.h
@@ -57,7 +57,7 @@ class CommandObjectExpression : public CommandObjectRaw,
LanguageRuntimeDescriptionDisplayVerbosity m_verbosity;
LazyBool auto_apply_fixits;
LazyBool suppress_persistent_result;
- bool ignore_context_qualifiers;
+ bool cpp_ignore_context_qualifiers;
};
CommandObjectExpression(CommandInterpreter &interpreter);
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index c3dddd0f64c2c..72bb5df4dddac 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -779,13 +779,12 @@ let Command = "expression" in {
"Expression results will be labeled with $-prefixed variables, "
"e.g. $0, $1, etc.">;
def ignore_context_qualifiers
- : Option<"ignore-context-qualifiers", "Q">,
+ : Option<"cpp-ignore-context-qualifiers", "Q">,
Groups<[1, 2]>,
Desc<"When specified, evaluates the expression without taking into "
- "account the type ${Q}ualifiers of the scope. E.g., this would "
- "permit "
- "calling a non-const C++ method when stopped in a const-method "
- "(which would be disallowed by C++ language rules).">;
+ "account the type ${Q}ualifiers of the scope. In C++, this would "
+ "permit calling a non-const method when stopped in a const-method "
+ "(which would be disallowed by language rules).">;
}
let Command = "frame diag" in {
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 2436468569958..634cdec918057 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -417,7 +417,7 @@ void ClangUserExpression::CreateSourceCode(
if (!m_source_code->GetText(m_transformed_text, exe_ctx, !m_ctx_obj,
for_completion, modules_to_import,
- m_options.GetIgnoreContextQualifiers())) {
+ m_options.GetCppIgnoreContextQualifiers())) {
diagnostic_manager.PutString(lldb::eSeverityError,
"couldn't construct expression body");
return;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
index b3c31529446ad..7f1c1ddcad942 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -166,9 +166,9 @@ class ClangUserExpression : public LLVMUserExpression {
void ResetDeclMap(ExecutionContext &exe_ctx,
Materializer::PersistentVariableDelegate &result_delegate,
bool keep_result_in_memory) {
- m_type_system_helper.ResetDeclMap(exe_ctx, result_delegate,
- keep_result_in_memory, m_ctx_obj,
- m_options.GetIgnoreContextQualifiers());
+ m_type_system_helper.ResetDeclMap(
+ exe_ctx, result_delegate, keep_result_in_memory, m_ctx_obj,
+ m_options.GetCppIgnoreContextQualifiers());
}
lldb::ExpressionVariableSP
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
index d73b6563b29a6..b501836a46483 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
@@ -29,9 +29,9 @@ def test(self):
)
self.expect_expr("m_mem", result_value="-2")
- # Test short and long --ignore-context-qualifiers option.
+ # Test short and long --cpp-ignore-context-qualifiers option.
self.expect(
- "expression --ignore-context-qualifiers -- m_mem = 3.0",
+ "expression --cpp-ignore-context-qualifiers -- m_mem = 3.0",
error=False,
)
self.expect_expr("m_mem", result_value="3")
@@ -42,7 +42,7 @@ def test(self):
)
self.expect_expr("m_mem", result_value="4")
- # Test --ignore-context-qualifiers via SBExpressionOptions.
+ # Test --cpp-ignore-context-qualifiers via SBExpressionOptions.
options = lldb.SBExpressionOptions()
options.SetIgnoreContextQualifiers()
self.expect_expr("m_mem = -2.0; m_mem", options=options, result_value="-2")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
index 0069b42f5da10..40e5bff0aa60c 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
@@ -21,11 +21,11 @@ def test(self):
)
options = lldb.SBExpressionOptions()
- options.SetIgnoreContextQualifiers()
+ options.SetCppIgnoreContextQualifiers()
options.SetIgnoreBreakpoints(True)
self.expect_expr("volatile_method()", options=options)
self.expect(
- "expression --ignore-context-qualifiers -- bar()",
+ "expression --cpp-ignore-context-qualifiers -- bar()",
error=True,
substrs=["call to member function 'bar' is ambiguous"],
)
@@ -47,7 +47,7 @@ def test(self):
self.expect_expr("const_method()", options=options)
self.expect(
- "expression --ignore-context-qualifiers -- bar()",
+ "expression --cpp-ignore-context-qualifiers -- bar()",
error=True,
substrs=["call to member function 'bar' is ambiguous"],
)
@@ -79,7 +79,7 @@ def test(self):
self.expect_expr("const_method()", options=options)
self.expect_expr("volatile_method()", options=options)
self.expect(
- "expression --ignore-context-qualifiers -- bar()",
+ "expression --cpp-ignore-context-qualifiers -- bar()",
error=True,
substrs=["call to member function 'bar' is ambiguous"],
)
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
index 78fe27508d10f..c26239adb34ca 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
@@ -37,7 +37,7 @@ def test(self):
)
options = lldb.SBExpressionOptions()
- options.SetIgnoreContextQualifiers()
+ options.SetCppIgnoreContextQualifiers()
self.expect_expr("x = 6.0; x", options=options, result_value="6")
lldbutil.continue_to_source_breakpoint(
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
index 8489f09b69bba..e4cd5ea759f60 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
@@ -30,7 +30,7 @@ def test(self):
self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
options = lldb.SBExpressionOptions()
- options.SetIgnoreContextQualifiers()
+ options.SetCppIgnoreContextQualifiers()
self.expect_expr("m_mem = -2.0; m_mem", options=options, result_value="-2")
lldbutil.continue_to_source_breakpoint(
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
index 78fe27508d10f..c26239adb34ca 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
@@ -37,7 +37,7 @@ def test(self):
)
options = lldb.SBExpressionOptions()
- options.SetIgnoreContextQualifiers()
+ options.SetCppIgnoreContextQualifiers()
self.expect_expr("x = 6.0; x", options=options, result_value="6")
lldbutil.continue_to_source_breakpoint(
diff --git a/lldb/test/API/lang/cpp/this/TestCPPThis.py b/lldb/test/API/lang/cpp/this/TestCPPThis.py
index 5a59bf3631e82..cb462b0ae68de 100644
--- a/lldb/test/API/lang/cpp/this/TestCPPThis.py
+++ b/lldb/test/API/lang/cpp/this/TestCPPThis.py
@@ -48,7 +48,7 @@ def test_with_run_command(self):
self.expect("expression -- (int)getpid(); m_a", startstr="(const int) $1 = 3")
self.expect(
- "expression --ignore-context-qualifiers -- m_a = 2", startstr="(int) $2 = 2"
+ "expression --cpp-ignore-context-qualifiers -- m_a = 2", startstr="(int) $2 = 2"
)
self.runCmd("process continue")
>From fdf85929b68019496ac77c0ecd0705effb3f6d8a Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 30 Jan 2026 11:21:53 +0000
Subject: [PATCH 5/8] fixup! python format
---
lldb/test/API/lang/cpp/this/TestCPPThis.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lldb/test/API/lang/cpp/this/TestCPPThis.py b/lldb/test/API/lang/cpp/this/TestCPPThis.py
index cb462b0ae68de..3d87eba8fe05a 100644
--- a/lldb/test/API/lang/cpp/this/TestCPPThis.py
+++ b/lldb/test/API/lang/cpp/this/TestCPPThis.py
@@ -48,7 +48,8 @@ def test_with_run_command(self):
self.expect("expression -- (int)getpid(); m_a", startstr="(const int) $1 = 3")
self.expect(
- "expression --cpp-ignore-context-qualifiers -- m_a = 2", startstr="(int) $2 = 2"
+ "expression --cpp-ignore-context-qualifiers -- m_a = 2",
+ startstr="(int) $2 = 2",
)
self.runCmd("process continue")
>From 7fde8f675b273a2cb0f46b0bb21f5aef6bb0401d Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 30 Jan 2026 11:24:16 +0000
Subject: [PATCH 6/8] fixup! fix typo
---
lldb/include/lldb/Target/Target.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index bfb9fb0aedd47..20d7a2afcc712 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -482,11 +482,11 @@ class EvaluateExpressionOptions {
void SetIsForUtilityExpr(bool b) { m_running_utility_expression = b; }
bool GetCppIgnoreContextQualifiers() const {
- return m_cpp_ignore_context_qualifierss;
+ return m_cpp_ignore_context_qualifiers;
}
void SetCppIgnoreContextQualifiers(bool val) {
- m_cpp_ignore_context_qualifierss = val;
+ m_cpp_ignore_context_qualifiers = val;
}
private:
@@ -533,7 +533,7 @@ class EvaluateExpressionOptions {
/// disallowed by C++ language rules).
///
/// FIXME: move this to a language-specific dictionary of options.
- bool m_cpp_ignore_context_qualifierss = false;
+ bool m_cpp_ignore_context_qualifiers = false;
};
// Target
>From 1f8fa9068a2e4cf5deff1ebc84f8d150b7c04348 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 23 Jan 2026 15:21:39 +0000
Subject: [PATCH 7/8] [lldb][Expression] Emit hint to use
--ignore-const-context
Depends on:
* https://github.com/llvm/llvm-project/pull/177921
* https://github.com/llvm/llvm-project/pull/177926
(only last commit is relevant for this review)
---
lldb/include/lldb/Expression/UserExpression.h | 10 +++++
lldb/source/Expression/UserExpression.cpp | 3 ++
.../Clang/ClangUserExpression.cpp | 41 +++++++++++++++++++
.../Clang/ClangUserExpression.h | 4 ++
.../const_method/TestExprInConstMethod.py | 24 ++++++-----
.../TestExprInConstVolatileMethod.py | 14 ++++++-
.../fixit/TestExprInConstMethodWithFixit.py | 27 +++++++++++-
.../TestExprInNonConstMethod.py | 3 +-
.../TestExprInTemplateConstMethod.py | 12 ++++--
.../TestExprInTemplateNonConstMethod.py | 3 +-
lldb/test/API/lang/cpp/this/TestCPPThis.py | 3 +-
11 files changed, 124 insertions(+), 20 deletions(-)
diff --git a/lldb/include/lldb/Expression/UserExpression.h b/lldb/include/lldb/Expression/UserExpression.h
index 2fde73dafa035..977a0adef1cbe 100644
--- a/lldb/include/lldb/Expression/UserExpression.h
+++ b/lldb/include/lldb/Expression/UserExpression.h
@@ -313,6 +313,16 @@ class UserExpression : public Expression {
lldb::ProcessSP &process_sp,
lldb::StackFrameSP &frame_sp);
+ /// Called by expression evaluator when a parse error occurs. Gives this
+ /// UserExpression object a chance to inspect and adjust the error diagnostics
+ /// contained in the specified \c diagnostic_manager.
+ ///
+ /// \param[in,out] diagnostic_manager DiagnosticManager manager holding the
+ /// parse error diagnostics. This function may mutate the diagnostics.
+ ///
+ virtual void
+ FixupParseErrorDiagnostics(DiagnosticManager &diagnostic_manager) const {}
+
/// The address the process is stopped in.
Address m_address;
/// The text of the expression, as typed by the user.
diff --git a/lldb/source/Expression/UserExpression.cpp b/lldb/source/Expression/UserExpression.cpp
index 16a6218759f8f..d39bcced48390 100644
--- a/lldb/source/Expression/UserExpression.cpp
+++ b/lldb/source/Expression/UserExpression.cpp
@@ -323,6 +323,9 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
}
if (!parse_success) {
+ if (user_expression_sp)
+ user_expression_sp->FixupParseErrorDiagnostics(diagnostic_manager);
+
if (target->GetEnableNotifyAboutFixIts() && fixed_expression &&
!fixed_expression->empty()) {
std::string fixit =
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 634cdec918057..edb1b336858a0 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -27,6 +27,7 @@
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
+#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/ExpressionSourceCode.h"
#include "lldb/Expression/IRExecutionUnit.h"
#include "lldb/Expression/IRInterpreter.h"
@@ -55,6 +56,7 @@
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/BinaryFormat/Dwarf.h"
@@ -946,6 +948,45 @@ lldb::ExpressionVariableSP ClangUserExpression::GetResultAfterDematerialization(
return m_result_delegate.GetVariable();
}
+void ClangUserExpression::FixupParseErrorDiagnostics(
+ DiagnosticManager &diagnostic_manager) const {
+ const bool is_fixable_cvr_error = llvm::any_of(
+ diagnostic_manager.Diagnostics(),
+ [](std::unique_ptr<Diagnostic> const &diag) {
+ switch (diag->GetCompilerID()) {
+ case clang::diag::err_member_function_call_bad_cvr:
+ return true;
+ case clang::diag::err_typecheck_assign_const:
+ // FIXME: can we split this particular error into a separate
+ // diagnostic ID so we don't need to scan the error message?
+ return diag->GetDetail().message.find(
+ "within const member function") != std::string::npos;
+ default:
+ return false;
+ }
+ });
+
+ // Nothing to report.
+ if (!is_fixable_cvr_error)
+ return;
+
+ // If the user already tried ignoring function qualifiers but
+ // the expression still failed, we don't want to suggest the hint again.
+ if (m_options.GetIgnoreContextQualifiers()) {
+ // Hard to prove that we don't get here so don't emit a diagnostic n
+ // non-asserts builds. But we do want a signal in asserts builds.
+ assert(false &&
+ "IgnoreContextQualifiers didn't resolve compiler diagnostic.");
+ return;
+ }
+
+ diagnostic_manager.AddDiagnostic(
+ "Possibly trying to mutate object in a const context. Try "
+ "running the expression with: expression --ignore-context-qualifiers "
+ "-- <your expression>",
+ lldb::eSeverityInfo, eDiagnosticOriginLLDB);
+}
+
char ClangUserExpression::ClangUserExpressionHelper::ID;
void ClangUserExpression::ClangUserExpressionHelper::ResetDeclMap(
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
index 7f1c1ddcad942..5b5687320d3d7 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -179,6 +179,10 @@ class ClangUserExpression : public LLVMUserExpression {
llvm::StringRef GetFilename() const { return m_filename; }
+protected:
+ void FixupParseErrorDiagnostics(
+ DiagnosticManager &diagnostic_manager) const override;
+
private:
/// Populate m_in_cplusplus_method and m_in_objectivec_method based on the
/// environment.
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
index b501836a46483..8e601bbb4a402 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_method/TestExprInConstMethod.py
@@ -16,15 +16,16 @@ def test(self):
"expression m_const_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member",
- "with const-qualified type",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
+ matching=False,
)
self.expect(
"expression m_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("m_mem", result_value="-2")
@@ -60,7 +61,8 @@ def test(self):
"expression x = 7.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("x", result_value="2")
@@ -87,15 +89,16 @@ def test(self):
"expression m_const_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member",
- "with const-qualified type",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
+ matching=False,
)
self.expect(
"expression m_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("m_mem", result_value="-2")
@@ -117,15 +120,16 @@ def test(self):
"expression m_const_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member",
- "with const-qualified type",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
+ matching=False,
)
self.expect(
"expression m_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("m_mem", result_value="-1")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
index 40e5bff0aa60c..432a042378f7e 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/const_volatile_method/TestExprInConstVolatileMethod.py
@@ -17,7 +17,11 @@ def test(self):
self.expect(
"expression volatile_method()",
error=True,
- substrs=["has type 'const Foo'", "but function is not marked const"],
+ substrs=[
+ "has type 'const Foo'",
+ "but function is not marked const",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
+ ],
)
options = lldb.SBExpressionOptions()
@@ -41,7 +45,11 @@ def test(self):
self.expect(
"expression const_method()",
error=True,
- substrs=["has type 'volatile Foo'", "but function is not marked volatile"],
+ substrs=[
+ "has type 'volatile Foo'",
+ "but function is not marked volatile",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
+ ],
)
self.expect_expr("volatile_method()")
@@ -65,6 +73,7 @@ def test(self):
substrs=[
"has type 'const volatile Foo'",
"but function is not marked const or volatile",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect(
@@ -73,6 +82,7 @@ def test(self):
substrs=[
"has type 'const volatile Foo'",
"but function is not marked const or volatile",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py
index 3c239c89d0ca7..9461c41a6129f 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/fixit/TestExprInConstMethodWithFixit.py
@@ -17,10 +17,11 @@ def test(self):
substrs=[
"member reference type 'const Bar' is not a pointer",
"but function is not marked const",
+ "Possibly trying to mutate object in a const context. Try running the expression with",
],
)
- # Two fix-its
+ # Two fix-its...
self.expect(
"expression -- m_bar->method() + m_bar->method()",
error=True,
@@ -32,11 +33,35 @@ def test(self):
],
)
+ # ...only emit a single hint.
+ self.assertEqual(
+ self.res.GetError().count(
+ "Possibly trying to mutate object in a const context."
+ ),
+ 1,
+ )
+
+ self.expect(
+ "expression -Q -- m_bar->method()",
+ error=True,
+ substrs=["Evaluated this expression after applying Fix-It(s):"],
+ )
+
self.expect(
"expression m_bar->method() + blah",
error=True,
substrs=[
"member reference type 'const Bar' is not a pointer",
"but function is not marked const",
+ "Possibly trying to mutate object in a const context. Try running the expression with",
+ ],
+ )
+
+ self.expect(
+ "expression -K -- m_bar->method() + blah",
+ error=True,
+ substrs=[
+ "Possibly trying to mutate object in a const context. Try running the expression with",
],
+ matching=False,
)
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
index c26239adb34ca..162952a34202a 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/non_const_method/TestExprInNonConstMethod.py
@@ -32,7 +32,8 @@ def test(self):
"expression x = 7.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
index e4cd5ea759f60..0e491fa155d18 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_const_method/TestExprInTemplateConstMethod.py
@@ -24,7 +24,8 @@ def test(self):
"expression m_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("((Foo*)this)->bar()", result_type="double", result_value="5")
@@ -44,7 +45,8 @@ def test(self):
"expression x = 7.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("x = -7.0; x", options=options, result_value="-7")
@@ -77,7 +79,8 @@ def test(self):
"expression m_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("m_mem", result_value="-2")
@@ -105,7 +108,8 @@ def test(self):
"expression m_mem = 2.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
self.expect_expr("m_mem = -11.0; m_mem", options=options, result_value="-11")
diff --git a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
index c26239adb34ca..162952a34202a 100644
--- a/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
+++ b/lldb/test/API/lang/cpp/expression-context-qualifiers/template_non_const_method/TestExprInTemplateNonConstMethod.py
@@ -32,7 +32,8 @@ def test(self):
"expression x = 7.0",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "note: Possibly trying to mutate object in a const context. Try running the expression with",
],
)
diff --git a/lldb/test/API/lang/cpp/this/TestCPPThis.py b/lldb/test/API/lang/cpp/this/TestCPPThis.py
index 3d87eba8fe05a..5e648c6229182 100644
--- a/lldb/test/API/lang/cpp/this/TestCPPThis.py
+++ b/lldb/test/API/lang/cpp/this/TestCPPThis.py
@@ -42,7 +42,8 @@ def test_with_run_command(self):
"expression -- m_a = 2",
error=True,
substrs=[
- "cannot assign to non-static data member within const member function"
+ "cannot assign to non-static data member within const member function",
+ "Possibly trying to mutate object in a const context. Try running the expression with",
],
)
>From 4fca9dc104ff2e0da3a65251b77c1b02121b52e2 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 30 Jan 2026 11:20:31 +0000
Subject: [PATCH 8/8] fixup! rename option
---
.../Plugins/ExpressionParser/Clang/ClangUserExpression.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index edb1b336858a0..c71b3d813ea9b 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -972,17 +972,17 @@ void ClangUserExpression::FixupParseErrorDiagnostics(
// If the user already tried ignoring function qualifiers but
// the expression still failed, we don't want to suggest the hint again.
- if (m_options.GetIgnoreContextQualifiers()) {
+ if (m_options.GetCppIgnoreContextQualifiers()) {
// Hard to prove that we don't get here so don't emit a diagnostic n
// non-asserts builds. But we do want a signal in asserts builds.
assert(false &&
- "IgnoreContextQualifiers didn't resolve compiler diagnostic.");
+ "CppIgnoreContextQualifiers didn't resolve compiler diagnostic.");
return;
}
diagnostic_manager.AddDiagnostic(
"Possibly trying to mutate object in a const context. Try "
- "running the expression with: expression --ignore-context-qualifiers "
+ "running the expression with: expression --cpp-ignore-context-qualifiers "
"-- <your expression>",
lldb::eSeverityInfo, eDiagnosticOriginLLDB);
}
More information about the lldb-commits
mailing list