[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