[Lldb-commits] [lldb] [lldb] Add dictionary data type to formatter bytecode (PR #225955)

Dave Lee via lldb-commits lldb-commits at lists.llvm.org
Fri Sep 25 18:07:38 PDT 2026


https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/225955

>From 0e0cb3ae9ec36df04a0abbca1d6fea22a36292df Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Wed, 23 Sep 2026 15:11:57 -0700
Subject: [PATCH 1/2] [lldb] Add dictionary type to formatter bytecode

---
 lldb/docs/resources/formatterbytecode.md      | 20 ++++
 lldb/examples/python/formatter_bytecode.py    | 23 +++++
 .../lldb/DataFormatters/FormatterBytecode.def |  5 +
 .../lldb/DataFormatters/FormatterBytecode.h   | 17 +++-
 .../DataFormatters/FormatterBytecode.cpp      | 42 ++++++++
 .../DataFormatter/FormatterBytecodeTest.cpp   | 99 +++++++++++++++++++
 6 files changed, 205 insertions(+), 1 deletion(-)

diff --git a/lldb/docs/resources/formatterbytecode.md b/lldb/docs/resources/formatterbytecode.md
index 24ee48dcfebfc6..3330d1ded6a1c3 100644
--- a/lldb/docs/resources/formatterbytecode.md
+++ b/lldb/docs/resources/formatterbytecode.md
@@ -33,6 +33,7 @@ All objects on the data stack must have one of the following data types. These d
 - *Object* (Basically an `SBValue`)
 - *Type* (Basically an `SBType`)
 - *Selector* (One of the predefine functions)
+- *Dictionary* (A mutable mapping from `String` keys to values of any data type)
 
 *Object* and *Type* are opaque, they can only be used as a parameters of `call`.
 
@@ -142,6 +143,25 @@ For security reasons the list of functions callable with `call` is predefined. T
 ========  ==========  ============================================
 ```
 
+### Dictionary objects
+
+ `Dictionary` objects are key-value containers, with `String` value keys, and values of any data type. `Dictionary` is a reference type, mutating it through one reference is visible through any other reference to the same dictionary (e.g. one obtained earlier with `dup`). Empty `Dictionary` objects are created  with `dict`. `Dictionary` objects are populated with `dict_set`. Look up values with `dict_get`. When `dict_get` is called with a key that is not present in the dictionary, an error is emitted. Use `dict_has` first to check for a key's existence. Dictionary operations consumes the `Dictionary` argument, so `dup` it first if the `Dictionary` is needed afterward. For example, to set multiple keys in a row:
+
+```
+dict dup "a" 1 dict_set dup "b" 2 dict_set
+```
+
+```{eval-rst}
+========  =============  ============================================================
+ Opcode    Mnemonic      Stack effect
+--------  -------------  ------------------------------------------------------------
+ 0x70      ``dict``       ``( -> Dictionary)`` create an empty dictionary
+ 0x71      ``dict_set``   ``(Dictionary String x -> )`` set a key to a value
+ 0x72      ``dict_get``   ``(Dictionary String -> x)`` look up the value for a key
+ 0x73      ``dict_has``   ``(Dictionary String -> Integer)`` check whether a key is present
+========  =============  ============================================================
+```
+
 Method is one of a predefined set of *Selectors*.
 
 ```{eval-rst}
diff --git a/lldb/examples/python/formatter_bytecode.py b/lldb/examples/python/formatter_bytecode.py
index b09a896db5edf9..0a57e570d18135 100644
--- a/lldb/examples/python/formatter_bytecode.py
+++ b/lldb/examples/python/formatter_bytecode.py
@@ -89,6 +89,11 @@ def define_opcode(n, mnemonic, name):
 
 define_opcode(0x60, "call", "call")
 
+define_opcode(0x70, "dict", "dict")
+define_opcode(0x71, "dict_set", "dict_set")
+define_opcode(0x72, "dict_get", "dict_get")
+define_opcode(0x73, "dict_has", "dict_has")
+
 # Function signatures
 sig_summary = 0
 sig_init = 1
@@ -745,6 +750,24 @@ def next_byte():
             else:
                 print("not implemented: " + selector[sel])
                 assert False
+
+        # Dictionary operations.
+        elif b == op_dict:
+            data.append(dict())
+        elif b == op_dict_set:
+            value = data.pop()
+            key = data.pop()
+            d = data.pop()
+            d[key] = value
+            data.append(d)
+        elif b == op_dict_get:
+            key = data.pop()
+            d = data.pop()
+            data.append(d[key])
+        elif b == op_dict_has:
+            key = data.pop()
+            d = data.pop()
+            data.append(int(key in d))
     return data[-1]
 
 
diff --git a/lldb/include/lldb/DataFormatters/FormatterBytecode.def b/lldb/include/lldb/DataFormatters/FormatterBytecode.def
index a0d2f93b07ab3e..13f4ef936d5e2e 100644
--- a/lldb/include/lldb/DataFormatters/FormatterBytecode.def
+++ b/lldb/include/lldb/DataFormatters/FormatterBytecode.def
@@ -61,6 +61,11 @@ DEFINE_OPCODE(0x55, ">=", ge)
 
 DEFINE_OPCODE(0x60, "call", call)
 
+DEFINE_OPCODE(0x70, "dict", dict)
+DEFINE_OPCODE(0x71, "dict_set", dict_set)
+DEFINE_OPCODE(0x72, "dict_get", dict_get)
+DEFINE_OPCODE(0x73, "dict_has", dict_has)
+
 // Selectors.
 DEFINE_SELECTOR(0x00, summary)
 DEFINE_SELECTOR(0x01, type_summary)
diff --git a/lldb/include/lldb/DataFormatters/FormatterBytecode.h b/lldb/include/lldb/DataFormatters/FormatterBytecode.h
index 24bda32a6f0bb7..b9adbcf73277c0 100644
--- a/lldb/include/lldb/DataFormatters/FormatterBytecode.h
+++ b/lldb/include/lldb/DataFormatters/FormatterBytecode.h
@@ -12,6 +12,7 @@
 #include "lldb/DataFormatters/TypeSummary.h"
 #include "lldb/Symbol/CompilerType.h"
 #include "llvm/ADT/APSInt.h"
+#include "llvm/ADT/StringMap.h"
 
 namespace lldb_private {
 
@@ -26,6 +27,9 @@ enum DataType : uint8_t {
   Type,
   Selector,
   Integer,
+  // Named `Dict` to avoid colliding with the `Dictionary` class, since this
+  // enumerator is scoped to the enclosing namespace.
+  Dict,
 };
 
 enum OpCodes : uint8_t {
@@ -48,11 +52,22 @@ enum Signatures : uint8_t {
 
 using ControlStackElement = llvm::StringRef;
 using ControlStack = std::vector<ControlStackElement>;
+
+// A Dictionary mapping String keys to DataStackElements. It is always wrapped
+// in a shared_ptr for two reasons:
+//   1. To allow DataStackElement to hold a dictionary, breaking the type
+//   reference cyle.
+//   2. To provide reference type behavior. This means `op_dict_set` can mutate
+//   the same instance that other stack slots refer to (ex via `dup`).
+class Dictionary;
+
 // uint64_t and int64_t are kept for compatibility with the deprecated
 // uint/int opcodes. New code should instead use APSInt (op_lit_integer).
 using DataStackElement =
     std::variant<std::string, uint64_t, int64_t, lldb::ValueObjectSP,
-                 CompilerType, Selectors, llvm::APSInt>;
+                 CompilerType, Selectors, llvm::APSInt,
+                 std::shared_ptr<Dictionary>>;
+class Dictionary : public llvm::StringMap<DataStackElement> {};
 struct DataStack : public std::vector<DataStackElement> {
   DataStack() = default;
   DataStack(lldb::ValueObjectSP initial_value)
diff --git a/lldb/source/DataFormatters/FormatterBytecode.cpp b/lldb/source/DataFormatters/FormatterBytecode.cpp
index 1600a1e08bfe07..0aeb6f3e24904c 100644
--- a/lldb/source/DataFormatters/FormatterBytecode.cpp
+++ b/lldb/source/DataFormatters/FormatterBytecode.cpp
@@ -79,6 +79,10 @@ std::string toString(const FormatterBytecode::DataStack &data) {
       os << '(' << type->GetTypeName(true) << ')';
     } else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&d)) {
       os << toString(*sel);
+    } else if (auto *dict =
+                   std::get_if<std::shared_ptr<FormatterBytecode::Dictionary>>(
+                       &d)) {
+      os << "dict(" << (*dict ? (*dict)->size() : 0) << ')';
     }
     os << ' ';
   }
@@ -132,6 +136,8 @@ static llvm::Error FormatImpl(DataStack &data) {
       format(FormatFunctor(type->GetDisplayTypeName()));
     else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg))
       format(FormatFunctor(toString(*sel)));
+    else if (auto dict = std::get_if<std::shared_ptr<Dictionary>>(&arg))
+      format(FormatFunctor("dict"));
   }
   data.Push(s);
   return llvm::Error::success();
@@ -174,6 +180,11 @@ static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
     if (!std::holds_alternative<llvm::APSInt>(elem))
       return llvm::createStringError("expected Integer");
     break;
+  case Dict:
+    if (!std::holds_alternative<std::shared_ptr<FormatterBytecode::Dictionary>>(
+            elem))
+      return llvm::createStringError("expected Dictionary");
+    break;
   }
   return llvm::Error::success();
 }
@@ -726,6 +737,37 @@ llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig) {
       }
       continue;
     }
+
+    // Dictionary operations.
+    case op_dict:
+      data.Push(std::make_shared<Dictionary>());
+      continue;
+    case op_dict_set: {
+      TYPE_CHECK(Dict, String, Any);
+      auto value = data.PopAny();
+      auto key = data.Pop<std::string>();
+      auto dict_sp = data.Pop<std::shared_ptr<Dictionary>>();
+      (*dict_sp)[key] = std::move(value);
+      continue;
+    }
+    case op_dict_get: {
+      TYPE_CHECK(Dict, String);
+      auto key = data.Pop<std::string>();
+      auto dict_sp = data.Pop<std::shared_ptr<Dictionary>>();
+      auto it = dict_sp->find(key);
+      if (it == dict_sp->end())
+        return error("key not found in dictionary");
+      data.Push(it->second);
+      continue;
+    }
+    case op_dict_has: {
+      TYPE_CHECK(Dict, String);
+      auto key = data.Pop<std::string>();
+      auto dict_sp = data.Pop<std::shared_ptr<Dictionary>>();
+      bool found = dict_sp->find(key) != dict_sp->end();
+      data.Push(llvm::APSInt(llvm::APInt(1, found), /*isUnsigned=*/true));
+      continue;
+    }
     }
     return error("opcode not implemented");
   }
diff --git a/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp b/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
index 2288c99d48dacd..36dd1939a733f3 100644
--- a/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
+++ b/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
@@ -490,3 +490,102 @@ TEST_F(FormatterBytecodeTest, CallOps) {
     ASSERT_FALSE(Interpret({op_lit_selector, sel_fmt, op_call}, data));
   }
 }
+
+TEST_F(FormatterBytecodeTest, DictionaryOps) {
+  {
+    // Set key a, then read it back.
+    DataStack data;
+    ASSERT_TRUE(
+        Interpret({op_dict, op_dup, op_lit_string, 1, 'a', op_lit_integer, 42,
+                   op_dict_set, op_lit_string, 1, 'a', op_dict_get},
+                  data));
+    ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(42));
+  }
+
+  {
+    // Add keys a and b, then read them back.
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_dict,
+                           op_dup,
+                           op_lit_string,
+                           1,
+                           'a',
+                           op_lit_integer,
+                           1,
+                           op_dict_set,
+                           op_dup,
+                           op_lit_string,
+                           1,
+                           'b',
+                           op_lit_integer,
+                           2,
+                           op_dict_set,
+                           op_dup,
+                           op_lit_string,
+                           1,
+                           'b',
+                           op_dict_get,
+                           op_swap,
+                           op_lit_string,
+                           1,
+                           'a',
+                           op_dict_get},
+                          data));
+    ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(1));
+    ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(2));
+  }
+
+  {
+    // Set key a, reassign key a, then read it back.
+    DataStack data;
+    ASSERT_TRUE(
+        Interpret({op_dict, op_dup, op_lit_string, 1, 'a', op_lit_integer, 1,
+                   op_dict_set, op_dup, op_lit_string, 1, 'a', op_lit_integer,
+                   2, op_dict_set, op_lit_string, 1, 'a', op_dict_get},
+                  data));
+    ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(2));
+  }
+
+  // Error: get value of missing key.
+  EXPECT_THAT_ERROR(
+      InterpretFail({op_dict, op_lit_string, 1, 'a', op_dict_get}),
+      FailedWithMessage("key not found in dictionary(opcode=dict_get)"));
+  // Error: get value from a non-dictionary.
+  EXPECT_THAT_ERROR(
+      InterpretFail({op_lit_integer, 0, op_lit_string, 1, 'a', op_dict_get}),
+      FailedWithMessage("expected Dictionary"));
+  // Error: check for key in a non-dictionary.
+  EXPECT_THAT_ERROR(
+      InterpretFail({op_lit_integer, 0, op_lit_string, 1, 'a', op_dict_has}),
+      FailedWithMessage("expected Dictionary"));
+
+  {
+    // Check a key, then get its value.
+    DataStack data;
+    ASSERT_TRUE(
+        Interpret({op_dict, op_dup, op_lit_string, 1, 'a', op_lit_integer, 1,
+                   op_dict_set, op_dup, op_lit_string, 1, 'a', op_dict_has,
+                   op_swap, op_lit_string, 1, 'a', op_dict_get},
+                  data));
+    ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(1));
+    ASSERT_TRUE(data.Pop<llvm::APSInt>().getBoolValue());
+  }
+
+  {
+    // Check for a non-existing key.
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_dict, op_lit_string, 1, 'a', op_dict_has}, data));
+    ASSERT_FALSE(data.Pop<llvm::APSInt>().getBoolValue());
+  }
+
+  {
+    // Use dict_has in combination with `if`.
+    DataStack data;
+    ASSERT_TRUE(
+        Interpret({op_dict, op_dup, op_lit_string, 1, 'a', op_lit_integer, 1,
+                   op_dict_set, op_lit_string, 1, 'a', op_dict_has, op_begin, 2,
+                   op_lit_integer, 42, op_if},
+                  data));
+    ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(42));
+  }
+}

>From 6a168cdfbc032939dbe231d9b787f7b52096a359 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Fri, 25 Sep 2026 16:34:38 -0700
Subject: [PATCH 2/2] Updates

---
 lldb/docs/resources/formatterbytecode.md      | 40 +++++++------
 .../lldb/DataFormatters/FormatterBytecode.h   |  7 ++-
 .../DataFormatters/FormatterBytecode.cpp      | 37 ++++++++++--
 .../DataFormatter/FormatterBytecodeTest.cpp   | 59 +++++++++++++++++++
 4 files changed, 117 insertions(+), 26 deletions(-)

diff --git a/lldb/docs/resources/formatterbytecode.md b/lldb/docs/resources/formatterbytecode.md
index 3330d1ded6a1c3..b92fd224c3a2bc 100644
--- a/lldb/docs/resources/formatterbytecode.md
+++ b/lldb/docs/resources/formatterbytecode.md
@@ -143,25 +143,6 @@ For security reasons the list of functions callable with `call` is predefined. T
 ========  ==========  ============================================
 ```
 
-### Dictionary objects
-
- `Dictionary` objects are key-value containers, with `String` value keys, and values of any data type. `Dictionary` is a reference type, mutating it through one reference is visible through any other reference to the same dictionary (e.g. one obtained earlier with `dup`). Empty `Dictionary` objects are created  with `dict`. `Dictionary` objects are populated with `dict_set`. Look up values with `dict_get`. When `dict_get` is called with a key that is not present in the dictionary, an error is emitted. Use `dict_has` first to check for a key's existence. Dictionary operations consumes the `Dictionary` argument, so `dup` it first if the `Dictionary` is needed afterward. For example, to set multiple keys in a row:
-
-```
-dict dup "a" 1 dict_set dup "b" 2 dict_set
-```
-
-```{eval-rst}
-========  =============  ============================================================
- Opcode    Mnemonic      Stack effect
---------  -------------  ------------------------------------------------------------
- 0x70      ``dict``       ``( -> Dictionary)`` create an empty dictionary
- 0x71      ``dict_set``   ``(Dictionary String x -> )`` set a key to a value
- 0x72      ``dict_get``   ``(Dictionary String -> x)`` look up the value for a key
- 0x73      ``dict_has``   ``(Dictionary String -> Integer)`` check whether a key is present
-========  =============  ============================================================
-```
-
 Method is one of a predefined set of *Selectors*.
 
 ```{eval-rst}
@@ -198,6 +179,27 @@ Sel.  Mnemonic                         Stack Effect
 ====  ===============================  ======================================================  ======================================
 ```
 
+### Dictionary objects
+
+ `Dictionary` objects are key-value containers, with `String` value keys, and values of any data type. `Dictionary` is a reference type, mutating it through one reference is visible through any other reference to the same dictionary (e.g. one obtained earlier with `dup`). Empty `Dictionary` objects are created  with `dict`. `Dictionary` objects are populated with `dict_set`. Look up values with `dict_get`. When `dict_get` is called with a key that is not present in the dictionary, an error is emitted. Use `dict_has` first to check for a key's existence. Dictionary operations consumes the `Dictionary` argument, so `dup` it first if the `Dictionary` is needed afterward. For example, to set multiple keys in a row:
+
+```
+dict dup "a" 1 dict_set dup "b" 2 dict_set
+```
+
+A `Dictionary` may be stored as a value in another `Dictionary`, but `dict_set` emits an error if doing so would make a dictionary contain itself, directly or through nested dictionaries.
+
+```{eval-rst}
+========  =============  ============================================================
+ Opcode    Mnemonic      Stack effect
+--------  -------------  ------------------------------------------------------------
+ 0x70      ``dict``       ``( -> Dictionary)`` create an empty dictionary
+ 0x71      ``dict_set``   ``(Dictionary String x -> )`` set a key to a value
+ 0x72      ``dict_get``   ``(Dictionary String -> x)`` look up the value for a key
+ 0x73      ``dict_has``   ``(Dictionary String -> Integer)`` check whether a key is present
+========  =============  ============================================================
+```
+
 ### Byte Code
 
 Most instructions are just a single byte opcode. The only exceptions are the literals:
diff --git a/lldb/include/lldb/DataFormatters/FormatterBytecode.h b/lldb/include/lldb/DataFormatters/FormatterBytecode.h
index b9adbcf73277c0..27ed315dac205c 100644
--- a/lldb/include/lldb/DataFormatters/FormatterBytecode.h
+++ b/lldb/include/lldb/DataFormatters/FormatterBytecode.h
@@ -13,6 +13,7 @@
 #include "lldb/Symbol/CompilerType.h"
 #include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/StringMap.h"
+#include <memory>
 
 namespace lldb_private {
 
@@ -60,14 +61,16 @@ using ControlStack = std::vector<ControlStackElement>;
 //   2. To provide reference type behavior. This means `op_dict_set` can mutate
 //   the same instance that other stack slots refer to (ex via `dup`).
 class Dictionary;
+using DictionarySP = std::shared_ptr<Dictionary>;
 
 // uint64_t and int64_t are kept for compatibility with the deprecated
 // uint/int opcodes. New code should instead use APSInt (op_lit_integer).
 using DataStackElement =
     std::variant<std::string, uint64_t, int64_t, lldb::ValueObjectSP,
-                 CompilerType, Selectors, llvm::APSInt,
-                 std::shared_ptr<Dictionary>>;
+                 CompilerType, Selectors, llvm::APSInt, DictionarySP>;
+
 class Dictionary : public llvm::StringMap<DataStackElement> {};
+
 struct DataStack : public std::vector<DataStackElement> {
   DataStack() = default;
   DataStack(lldb::ValueObjectSP initial_value)
diff --git a/lldb/source/DataFormatters/FormatterBytecode.cpp b/lldb/source/DataFormatters/FormatterBytecode.cpp
index 0aeb6f3e24904c..5891c2251703d0 100644
--- a/lldb/source/DataFormatters/FormatterBytecode.cpp
+++ b/lldb/source/DataFormatters/FormatterBytecode.cpp
@@ -11,6 +11,9 @@
 #include "lldb/ValueObject/ValueObject.h"
 #include "lldb/ValueObject/ValueObjectConstResult.h"
 #include "lldb/lldb-forward.h"
+#include "llvm/ADT/APSInt.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/DataExtractor.h"
 #include "llvm/Support/Error.h"
@@ -136,7 +139,7 @@ static llvm::Error FormatImpl(DataStack &data) {
       format(FormatFunctor(type->GetDisplayTypeName()));
     else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg))
       format(FormatFunctor(toString(*sel)));
-    else if (auto dict = std::get_if<std::shared_ptr<Dictionary>>(&arg))
+    else if (auto *dict = std::get_if<DictionarySP>(&arg))
       format(FormatFunctor("dict"));
   }
   data.Push(s);
@@ -216,6 +219,27 @@ static DataStackElement WrapAPSIntResult(T result, unsigned bit_width,
     return DataStackElement(std::move(result));
 }
 
+/// Returns true if `target` is transitively reachable via `from`. Likewise,
+/// returns true if they are the same dictionary. This is used to prevent memory
+/// leaks caused by retain cycles. Dictionaries can be shared by multiple
+/// parents, so each one is visited only once to prevent exponential running
+/// time.
+static bool Reaches(const Dictionary *from, const Dictionary *target) {
+  llvm::SmallPtrSet<const Dictionary *, 8> visited;
+  llvm::SmallVector<const Dictionary *, 8> worklist = {from};
+  while (!worklist.empty()) {
+    const Dictionary *dict = worklist.pop_back_val();
+    if (dict == target)
+      return true;
+    if (!visited.insert(dict).second)
+      continue;
+    for (const auto &entry : *dict)
+      if (auto *nested = std::get_if<DictionarySP>(&entry.second))
+        worklist.push_back(nested->get());
+  }
+  return false;
+}
+
 llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig) {
   if (control.empty())
     return llvm::Error::success();
@@ -746,14 +770,17 @@ llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig) {
       TYPE_CHECK(Dict, String, Any);
       auto value = data.PopAny();
       auto key = data.Pop<std::string>();
-      auto dict_sp = data.Pop<std::shared_ptr<Dictionary>>();
+      auto dict_sp = data.Pop<DictionarySP>();
+      if (auto *nested_sp = std::get_if<DictionarySP>(&value))
+        if (Reaches(nested_sp->get(), dict_sp.get()))
+          return error("dict_set would create a reference cycle");
       (*dict_sp)[key] = std::move(value);
       continue;
     }
     case op_dict_get: {
       TYPE_CHECK(Dict, String);
       auto key = data.Pop<std::string>();
-      auto dict_sp = data.Pop<std::shared_ptr<Dictionary>>();
+      auto dict_sp = data.Pop<DictionarySP>();
       auto it = dict_sp->find(key);
       if (it == dict_sp->end())
         return error("key not found in dictionary");
@@ -763,9 +790,9 @@ llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig) {
     case op_dict_has: {
       TYPE_CHECK(Dict, String);
       auto key = data.Pop<std::string>();
-      auto dict_sp = data.Pop<std::shared_ptr<Dictionary>>();
+      auto dict_sp = data.Pop<DictionarySP>();
       bool found = dict_sp->find(key) != dict_sp->end();
-      data.Push(llvm::APSInt(llvm::APInt(1, found), /*isUnsigned=*/true));
+      data.Push(llvm::APSInt::get(found));
       continue;
     }
     }
diff --git a/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp b/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
index 36dd1939a733f3..a3c197e05cf0c9 100644
--- a/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
+++ b/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
@@ -588,4 +588,63 @@ TEST_F(FormatterBytecodeTest, DictionaryOps) {
                   data));
     ASSERT_EQ(data.Pop<llvm::APSInt>(), llvm::APSInt::get(42));
   }
+
+  {
+    // dict_has yields a signed Integer, usable with comparison operators.
+    DataStack data;
+    ASSERT_TRUE(Interpret(
+        {op_dict, op_lit_string, 1, 'a', op_dict_has, op_lit_integer, 0, op_eq},
+        data));
+    ASSERT_TRUE(data.Pop<llvm::APSInt>().getBoolValue());
+  }
+
+  {
+    // Dictionaries can be nested.
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_dict, op_dup, op_lit_string, 1, 'k', op_dict,
+                           op_dict_set, op_lit_string, 1, 'k', op_dict_get},
+                          data));
+    ASSERT_TRUE(data.Pop<std::shared_ptr<Dictionary>>());
+  }
+
+  // Error: store a dictionary in itself.
+  EXPECT_THAT_ERROR(
+      InterpretFail(
+          {op_dict, op_dup, op_lit_string, 1, 'k', op_over, op_dict_set}),
+      FailedWithMessage(
+          "dict_set would create a reference cycle(opcode=dict_set)"));
+  // Error: store b in a, when a is already in b.
+  EXPECT_THAT_ERROR(
+      InterpretFail(
+          {op_dict,     op_dict,     op_dup,  op_lit_string, 1,
+           'k',         op_lit_uint, 0,       op_pick,       op_dict_set,
+           op_lit_uint, 0,           op_pick, op_lit_string, 1,
+           'k',         op_lit_uint, 1,       op_pick,       op_dict_set}),
+      FailedWithMessage(
+          "dict_set would create a reference cycle(opcode=dict_set)"));
+
+  {
+    // Dictionaries shared by multiple parents. Starting from [d0 d0], each
+    // level turns [.. p] into [.. n] where n["a"] and n["b"] are both p. The
+    // number of paths to d0 doubles per level, so cycle detection must not
+    // walk every path.
+    std::vector<uint8_t> code = {op_dict, op_dup};
+    for (int i = 0; i < 64; ++i)
+      code.insert(code.end(),
+                  {op_dict, op_swap, op_over, op_over, op_lit_string, 1, 'a',
+                   op_swap, op_dict_set, op_over, op_over, op_lit_string, 1,
+                   'b', op_swap, op_dict_set, op_drop});
+    {
+      DataStack data;
+      ASSERT_TRUE(Interpret(code, data));
+      ASSERT_EQ(data.size(), 2u);
+    }
+    // Error: store the top dictionary in d0, which it reaches via sharing.
+    code.insert(code.end(), {op_over, op_swap, op_lit_string, 1, 'k', op_swap,
+                             op_dict_set});
+    EXPECT_THAT_ERROR(
+        InterpretFail(code),
+        FailedWithMessage(
+            "dict_set would create a reference cycle(opcode=dict_set)"));
+  }
 }



More information about the lldb-commits mailing list