Skip to content

Conversation

@cmtice
Copy link
Contributor

@cmtice cmtice commented Jul 31, 2025

This updates the DIL code for handling array subscripting to more closely match and handle all the cases from the original 'frame var' implementation. Also updates the DIL array subscripting test. This particularly fixes some issues with handling synthetic children, objc pointers, and accessing specific bits within scalar data types.

This updates the DIL code for handling array subscripting to more
closely match and handle all the cases from the original
'frame var' implementation. Also updates the DIL array subscripting
test. This particularly fixes some issues with handling
synthetic children, objc pointers, and accessing specific bits
within scalar data types.
@cmtice cmtice requested a review from JDevlieghere as a code owner July 31, 2025 22:11
@cmtice cmtice requested review from kuilpd and labath and removed request for JDevlieghere July 31, 2025 22:11
@llvmbot llvmbot added the lldb label Jul 31, 2025
@cmtice cmtice requested a review from JDevlieghere July 31, 2025 22:12
@llvmbot
Copy link
Member

llvmbot commented Jul 31, 2025

@llvm/pr-subscribers-lldb

Author: None (cmtice)

Changes

This updates the DIL code for handling array subscripting to more closely match and handle all the cases from the original 'frame var' implementation. Also updates the DIL array subscripting test. This particularly fixes some issues with handling synthetic children, objc pointers, and accessing specific bits within scalar data types.


Full diff: https://github.com/llvm/llvm-project/pull/151605.diff

2 Files Affected:

  • (modified) lldb/source/ValueObject/DILEval.cpp (+126-29)
  • (modified) lldb/test/API/commands/frame/var-dil/basics/ArraySubscript/TestFrameVarDILArraySubscript.py (+9-7)
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 6f28434c646cd..527fd46cab1cc 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -323,6 +323,7 @@ Interpreter::Visit(const MemberOfNode *node) {
       m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
 }
 
+
 llvm::Expected<lldb::ValueObjectSP>
 Interpreter::Visit(const ArraySubscriptNode *node) {
   auto lhs_or_err = Evaluate(node->GetBase());
@@ -330,40 +331,136 @@ Interpreter::Visit(const ArraySubscriptNode *node) {
     return lhs_or_err;
   lldb::ValueObjectSP base = *lhs_or_err;
 
-  // Check to see if 'base' has a synthetic value; if so, try using that.
+  StreamString var_expr_path_strm;
   uint64_t child_idx = node->GetIndex();
-  if (lldb::ValueObjectSP synthetic = base->GetSyntheticValue()) {
-    llvm::Expected<uint32_t> num_children =
-        synthetic->GetNumChildren(child_idx + 1);
-    if (!num_children)
-      return llvm::make_error<DILDiagnosticError>(
-          m_expr, toString(num_children.takeError()), node->GetLocation());
-    if (child_idx >= *num_children) {
-      std::string message = llvm::formatv(
-          "array index {0} is not valid for \"({1}) {2}\"", child_idx,
-          base->GetTypeName().AsCString("<invalid type>"),
-          base->GetName().AsCString());
-      return llvm::make_error<DILDiagnosticError>(m_expr, message,
+  lldb::ValueObjectSP child_valobj_sp;
+  bool is_incomplete_array = false;
+  CompilerType base_type = base->GetCompilerType().GetNonReferenceType();
+  base->GetExpressionPath(var_expr_path_strm);
+  if (base_type.IsPointerType()) {
+    bool is_objc_pointer = true;
+    if (base->GetCompilerType().GetMinimumLanguage() != lldb::eLanguageTypeObjC)
+      is_objc_pointer = false;
+    else if (!base_type.IsPointerType())
+      is_objc_pointer = false;
+
+    if (is_objc_pointer && !m_use_synthetic) {
+      std::string errMsg =
+          llvm::formatv("\"(({0}) {1}\" is an Objective-C pointer, and cannot "
+                        "be subscripted",
+                        base->GetTypeName().AsCString("<invalid type>"),
+                        base->GetName());
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                  node->GetLocation());
+    } else if (is_objc_pointer) {
+      lldb::ValueObjectSP synthetic = base->GetSyntheticValue();
+      if (!synthetic || synthetic == base) {
+        std::string errMsg =
+            llvm::formatv("\"({0}) {1}\" is not an array type",
+                          base->GetTypeName().AsCString("<invalid type>"),
+                          base->GetName());
+        return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                    node->GetLocation());
+      } else if (static_cast<uint32_t>(child_idx) >=
+                 synthetic->GetNumChildrenIgnoringErrors()) {
+        std::string errMsg =
+            llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
+                          child_idx,
+                          base->GetTypeName().AsCString("<invalid type>"),
+                          var_expr_path_strm.GetData());
+        return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                    node->GetLocation());
+      } else {
+        child_valobj_sp = synthetic->GetChildAtIndex(child_idx);
+        if (!child_valobj_sp) {
+          std::string errMsg =
+              llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
+                            child_idx,
+                            base->GetTypeName().AsCString("<invalid type>"),
+                            var_expr_path_strm.GetData());
+          return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                      node->GetLocation());
+        }
+      }
+    } else { // it's not an objc pointer
+      child_valobj_sp = base->GetSyntheticArrayMember(child_idx, true);
+      if (!child_valobj_sp) {
+        std::string errMsg =
+            llvm::formatv("failed to use pointer as array for index {0} for "
+                          "\"({1}) {2}\"", child_idx,
+                          base->GetTypeName().AsCString("<invalid type>"),
+                          var_expr_path_strm.GetData());
+        if (base_type.IsPointerToVoid())
+          errMsg = "subscript of pointer to incomplete type 'void'";
+        return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                    node->GetLocation());
+      }
+    }
+  } else if (base_type.IsArrayType(
+      nullptr, nullptr, &is_incomplete_array)) {
+    child_valobj_sp = base->GetChildAtIndex(child_idx);
+    if (!child_valobj_sp && (is_incomplete_array || m_use_synthetic))
+      child_valobj_sp = base->GetSyntheticArrayMember(child_idx, true);
+    if (!child_valobj_sp) {
+      std::string errMsg =
+          llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
+                        child_idx,
+                        base->GetTypeName().AsCString("<invalid type>"),
+                        var_expr_path_strm.GetData());
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
                                                   node->GetLocation());
     }
-    if (lldb::ValueObjectSP child_valobj_sp =
-            synthetic->GetChildAtIndex(child_idx))
-      return child_valobj_sp;
+  } else if (base_type.IsScalarType()) {
+    child_valobj_sp =
+        base->GetSyntheticBitFieldChild(child_idx, child_idx, true);
+    if (!child_valobj_sp) {
+      std::string errMsg =
+          llvm::formatv("bitfield range {0}-{1} is not valid for \"({2}) {3}\"",
+                        child_idx, child_idx,
+                        base->GetTypeName().AsCString("<invalid type>"),
+                        var_expr_path_strm.GetData());
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                  node->GetLocation(), 1);
+    }
+  } else {
+    lldb::ValueObjectSP synthetic = base->GetSyntheticValue();
+    if (!m_use_synthetic || !synthetic || synthetic == base) {
+      std::string errMsg =
+          llvm::formatv("\"{0}\" is not an array type",
+                        base->GetTypeName().AsCString("<invalid type>"));
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                  node->GetLocation(), 1);
+    } else if (static_cast<uint32_t>(child_idx) >=
+               synthetic->GetNumChildrenIgnoringErrors()) {
+      std::string errMsg =
+          llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
+                        child_idx,
+                        base->GetTypeName().AsCString("<invalid type>"),
+                        var_expr_path_strm.GetData());
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                  node->GetLocation(), 1);
+    } else {
+      child_valobj_sp = synthetic->GetChildAtIndex(child_idx);
+      if (!child_valobj_sp) {
+        std::string errMsg =
+            llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
+                          child_idx,
+                          base->GetTypeName().AsCString("<invalid type>"),
+                          var_expr_path_strm.GetData());
+        return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                    node->GetLocation(), 1);
+      }
+    }
   }
 
-  auto base_type = base->GetCompilerType().GetNonReferenceType();
-  if (!base_type.IsPointerType() && !base_type.IsArrayType())
-    return llvm::make_error<DILDiagnosticError>(
-        m_expr, "subscripted value is not an array or pointer",
-        node->GetLocation());
-  if (base_type.IsPointerToVoid())
-    return llvm::make_error<DILDiagnosticError>(
-        m_expr, "subscript of pointer to incomplete type 'void'",
-        node->GetLocation());
-
-  if (base_type.IsArrayType()) {
-    if (lldb::ValueObjectSP child_valobj_sp = base->GetChildAtIndex(child_idx))
-      return child_valobj_sp;
+  if (child_valobj_sp) {
+    if (m_use_dynamic != lldb::eNoDynamicValues) {
+      lldb::ValueObjectSP dynamic_value_sp(
+          child_valobj_sp->GetDynamicValue(m_use_dynamic));
+      if (dynamic_value_sp)
+        child_valobj_sp = dynamic_value_sp;
+    }
+    return child_valobj_sp;
   }
 
   int64_t signed_child_idx = node->GetIndex();
diff --git a/lldb/test/API/commands/frame/var-dil/basics/ArraySubscript/TestFrameVarDILArraySubscript.py b/lldb/test/API/commands/frame/var-dil/basics/ArraySubscript/TestFrameVarDILArraySubscript.py
index 0f56057189395..769bed2acb02c 100644
--- a/lldb/test/API/commands/frame/var-dil/basics/ArraySubscript/TestFrameVarDILArraySubscript.py
+++ b/lldb/test/API/commands/frame/var-dil/basics/ArraySubscript/TestFrameVarDILArraySubscript.py
@@ -69,17 +69,19 @@ def test_subscript(self):
             substrs=["expected 'r_square', got: <'.'"],
         )
 
-        # Base should be a "pointer to T" and index should be of an integral type.
-        self.expect(
-            "frame var 'idx_1[0]'",
-            error=True,
-            substrs=["subscripted value is not an array or pointer"],
-        )
+
+        # Test accessing bits in scalar types.
+        self.expect_var_path("idx_1[0]", value="1")
+        self.expect_var_path("idx_1[1]", value="0")
+
+        # Bit acess not valid for a reference.
         self.expect(
             "frame var 'idx_1_ref[0]'",
             error=True,
-            substrs=["subscripted value is not an array or pointer"],
+            substrs=["bitfield range 0-0 is not valid"],
         )
+
+        # Base should be a "pointer to T" and index should be of an integral type.
         self.expect(
             "frame var 'int_arr[int_ptr]'",
             error=True,

@cmtice cmtice requested review from asl and jimingham July 31, 2025 22:12
@github-actions
Copy link

github-actions bot commented Jul 31, 2025

✅ With the latest revision this PR passed the Python code formatter.

@github-actions
Copy link

github-actions bot commented Jul 31, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Comment on lines 355 to 384
} else if (is_objc_pointer) {
lldb::ValueObjectSP synthetic = base->GetSyntheticValue();
if (!synthetic || synthetic == base) {
std::string errMsg =
llvm::formatv("\"({0}) {1}\" is not an array type",
base->GetTypeName().AsCString("<invalid type>"),
base->GetName());
return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
node->GetLocation());
} else if (static_cast<uint32_t>(child_idx) >=
synthetic->GetNumChildrenIgnoringErrors()) {
std::string errMsg =
llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
child_idx,
base->GetTypeName().AsCString("<invalid type>"),
var_expr_path_strm.GetData());
return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
node->GetLocation());
} else {
child_valobj_sp = synthetic->GetChildAtIndex(child_idx);
if (!child_valobj_sp) {
std::string errMsg =
llvm::formatv("array index {0} is not valid for \"({1}) {2}\"",
child_idx,
base->GetTypeName().AsCString("<invalid type>"),
var_expr_path_strm.GetData());
return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
node->GetLocation());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in lines 356-383 is basically the same as in lines 426-453, could we reuse this somehow?

self.expect_var_path("idx_1[0]", value="1")
self.expect_var_path("idx_1[1]", value="0")

# Bit acess not valid for a reference.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Bit acess not valid for a reference.
# Bit access not valid for a reference.

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the bitfield handling is reflected in the test case, but I don't see anything about handling of synthetic children or objc pointers. For the objc case, I'm very tempted to not copy that bit of code over, since it: a) is untested (or you wouldn't have been able to start using the new implementation); b) doesn't fit into our concept of being language-agnostic.

It looks like that's mainly error handling code guarding a call to GetSyntheticArrayMember. That function eventually calls GetCompilerType().GetChildCompilerTypeAtIndex, so I think that special handling of ObjC should happen there.

That would also reduce the maze of the if statements, which is making the function hard to review.

return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
node->GetLocation());
} else if (static_cast<uint32_t>(child_idx) >=
synthetic->GetNumChildrenIgnoringErrors()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
synthetic->GetNumChildrenIgnoringErrors()) {
synthetic->GetNumChildrenIgnoringErrors(child_idx+1)) {

.. potentially avoids materializing many children unnecessarily

is_objc_pointer = false;

if (is_objc_pointer && !m_use_synthetic) {
std::string errMsg =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

err_msg

base->GetName());
return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
node->GetLocation());
} else if (is_objc_pointer) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmtice added 2 commits August 6, 2025 22:58
- Remove code testing for objc pointers
- No else-after-return
- Rename errMsg err_msg
- Add child_idx+1 argument to calls to GetNumChildrenIgnoringErrors
- Fix typo.
"array index {0} is not valid for \"({1}) {2}\"", child_idx,
base->GetTypeName().AsCString("<invalid type>"),
var_expr_path_strm.GetData());
return llvm::make_error<DILDiagnosticError>(m_expr, err_msg,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can std::move these err_msgs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 406 to 409
lldb::ValueObjectSP dynamic_value_sp(
child_valobj_sp->GetDynamicValue(m_use_dynamic));
if (dynamic_value_sp)
child_valobj_sp = dynamic_value_sp;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lldb::ValueObjectSP dynamic_value_sp(
child_valobj_sp->GetDynamicValue(m_use_dynamic));
if (dynamic_value_sp)
child_valobj_sp = dynamic_value_sp;
if (auto dynamic_sp = child_valobj_sp->GetDynamicValue(m_use_dynamic))
child_valobj_sp = std::move(dynamic_sp);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@cmtice
Copy link
Contributor Author

cmtice commented Aug 13, 2025

I've addressed all the review comments and added the test case. Please review again when you have a few minutes. Thanks!

@cmtice cmtice requested a review from werat August 14, 2025 15:04
return llvm::make_error<DILDiagnosticError>(m_expr, std::move(err_msg),
node->GetLocation());
}
} else if (base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe too fancy, but reduces the scope of the variable.

Suggested change
} else if (base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array)) {
} else if (bool is_incomplete_array; base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array)) {

@cmtice cmtice merged commit 6d3ad9d into llvm:main Aug 15, 2025
9 checks passed
rastogishubham added a commit that referenced this pull request Aug 15, 2025
This reverts commit 6d3ad9d.

This was reverted because it broke the LLDB greendragon bot.
@rastogishubham
Copy link
Contributor

Hi, it seems like this change broke greendragon

https://green.lab.llvm.org/job/llvm.org/view/LLDB/job/as-lldb-cmake/31450/

It was reverted with cd0bf27

cmtice added a commit to cmtice/llvm-project that referenced this pull request Aug 19, 2025
This attempts to fix the issues with the original PR (llvm#151605),
updating the DIL code for handling array subscripting to more closely
match and handle all the casees from the original 'frame var'
implementation. The first PR did not include special-case code for
objc pointers, which apparently caused a test failure on the
green-dragon buildbot. Hopefully this PR, which includes the objc
pointer special code, fixes that issue.
cmtice added a commit that referenced this pull request Aug 26, 2025
This attempts to fix the issues with the original PR (#151605), updating
the DIL code for handling array subscripting to more closely match and
handle all the casees from the original 'frame var' implementation. The
first PR did not include special-case code for objc pointers, which
apparently caused a test failure on the green-dragon buildbot. Hopefully
this PR, which includes the objc pointer special code, fixes that issue.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants