xref: /openbsd-src/gnu/llvm/lldb/source/Plugins/Language/CPlusPlus/LibCxxList.cpp (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1dda28197Spatrick //===-- LibCxxList.cpp ----------------------------------------------------===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick 
9061da546Spatrick #include "LibCxx.h"
10061da546Spatrick 
11dda28197Spatrick #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
12061da546Spatrick #include "lldb/Core/ValueObject.h"
13061da546Spatrick #include "lldb/Core/ValueObjectConstResult.h"
14061da546Spatrick #include "lldb/DataFormatters/FormattersHelpers.h"
15061da546Spatrick #include "lldb/Target/Target.h"
16061da546Spatrick #include "lldb/Utility/DataBufferHeap.h"
17061da546Spatrick #include "lldb/Utility/Endian.h"
18061da546Spatrick #include "lldb/Utility/Status.h"
19061da546Spatrick #include "lldb/Utility/Stream.h"
20061da546Spatrick 
21061da546Spatrick using namespace lldb;
22061da546Spatrick using namespace lldb_private;
23061da546Spatrick using namespace lldb_private::formatters;
24061da546Spatrick 
25061da546Spatrick namespace {
26061da546Spatrick 
27061da546Spatrick class ListEntry {
28061da546Spatrick public:
29061da546Spatrick   ListEntry() = default;
ListEntry(ValueObjectSP entry_sp)30be691f3bSpatrick   ListEntry(ValueObjectSP entry_sp) : m_entry_sp(std::move(entry_sp)) {}
ListEntry(ValueObject * entry)31061da546Spatrick   ListEntry(ValueObject *entry)
32061da546Spatrick       : m_entry_sp(entry ? entry->GetSP() : ValueObjectSP()) {}
33061da546Spatrick 
next()34061da546Spatrick   ListEntry next() {
35061da546Spatrick     static ConstString g_next("__next_");
36061da546Spatrick 
37061da546Spatrick     if (!m_entry_sp)
38061da546Spatrick       return ListEntry();
39061da546Spatrick     return ListEntry(m_entry_sp->GetChildMemberWithName(g_next, true));
40061da546Spatrick   }
41061da546Spatrick 
prev()42061da546Spatrick   ListEntry prev() {
43061da546Spatrick     static ConstString g_prev("__prev_");
44061da546Spatrick 
45061da546Spatrick     if (!m_entry_sp)
46061da546Spatrick       return ListEntry();
47061da546Spatrick     return ListEntry(m_entry_sp->GetChildMemberWithName(g_prev, true));
48061da546Spatrick   }
49061da546Spatrick 
value() const50061da546Spatrick   uint64_t value() const {
51061da546Spatrick     if (!m_entry_sp)
52061da546Spatrick       return 0;
53061da546Spatrick     return m_entry_sp->GetValueAsUnsigned(0);
54061da546Spatrick   }
55061da546Spatrick 
null()56061da546Spatrick   bool null() { return (value() == 0); }
57061da546Spatrick 
operator bool()58061da546Spatrick   explicit operator bool() { return GetEntry() && !null(); }
59061da546Spatrick 
GetEntry()60061da546Spatrick   ValueObjectSP GetEntry() { return m_entry_sp; }
61061da546Spatrick 
SetEntry(ValueObjectSP entry)62061da546Spatrick   void SetEntry(ValueObjectSP entry) { m_entry_sp = entry; }
63061da546Spatrick 
operator ==(const ListEntry & rhs) const64061da546Spatrick   bool operator==(const ListEntry &rhs) const { return value() == rhs.value(); }
65061da546Spatrick 
operator !=(const ListEntry & rhs) const66061da546Spatrick   bool operator!=(const ListEntry &rhs) const { return !(*this == rhs); }
67061da546Spatrick 
68061da546Spatrick private:
69061da546Spatrick   ValueObjectSP m_entry_sp;
70061da546Spatrick };
71061da546Spatrick 
72061da546Spatrick class ListIterator {
73061da546Spatrick public:
74061da546Spatrick   ListIterator() = default;
ListIterator(ListEntry entry)75be691f3bSpatrick   ListIterator(ListEntry entry) : m_entry(std::move(entry)) {}
ListIterator(ValueObjectSP entry)76be691f3bSpatrick   ListIterator(ValueObjectSP entry) : m_entry(std::move(entry)) {}
ListIterator(ValueObject * entry)77061da546Spatrick   ListIterator(ValueObject *entry) : m_entry(entry) {}
78061da546Spatrick 
value()79061da546Spatrick   ValueObjectSP value() { return m_entry.GetEntry(); }
80061da546Spatrick 
advance(size_t count)81061da546Spatrick   ValueObjectSP advance(size_t count) {
82061da546Spatrick     if (count == 0)
83061da546Spatrick       return m_entry.GetEntry();
84061da546Spatrick     if (count == 1) {
85061da546Spatrick       next();
86061da546Spatrick       return m_entry.GetEntry();
87061da546Spatrick     }
88061da546Spatrick     while (count > 0) {
89061da546Spatrick       next();
90061da546Spatrick       count--;
91061da546Spatrick       if (m_entry.null())
92061da546Spatrick         return lldb::ValueObjectSP();
93061da546Spatrick     }
94061da546Spatrick     return m_entry.GetEntry();
95061da546Spatrick   }
96061da546Spatrick 
operator ==(const ListIterator & rhs) const97061da546Spatrick   bool operator==(const ListIterator &rhs) const {
98061da546Spatrick     return (rhs.m_entry == m_entry);
99061da546Spatrick   }
100061da546Spatrick 
101061da546Spatrick protected:
next()102061da546Spatrick   void next() { m_entry = m_entry.next(); }
103061da546Spatrick 
prev()104061da546Spatrick   void prev() { m_entry = m_entry.prev(); }
105061da546Spatrick 
106061da546Spatrick private:
107061da546Spatrick   ListEntry m_entry;
108061da546Spatrick };
109061da546Spatrick 
110061da546Spatrick class AbstractListFrontEnd : public SyntheticChildrenFrontEnd {
111061da546Spatrick public:
GetIndexOfChildWithName(ConstString name)112061da546Spatrick   size_t GetIndexOfChildWithName(ConstString name) override {
113061da546Spatrick     return ExtractIndexFromString(name.GetCString());
114061da546Spatrick   }
MightHaveChildren()115061da546Spatrick   bool MightHaveChildren() override { return true; }
116061da546Spatrick   bool Update() override;
117061da546Spatrick 
118061da546Spatrick protected:
AbstractListFrontEnd(ValueObject & valobj)119061da546Spatrick   AbstractListFrontEnd(ValueObject &valobj)
120061da546Spatrick       : SyntheticChildrenFrontEnd(valobj) {}
121061da546Spatrick 
122*f6aab3d8Srobert   size_t m_count = 0;
123*f6aab3d8Srobert   ValueObject *m_head = nullptr;
124061da546Spatrick 
125061da546Spatrick   static constexpr bool g_use_loop_detect = true;
126*f6aab3d8Srobert   size_t m_loop_detected = 0; // The number of elements that have had loop
127*f6aab3d8Srobert                               // detection run over them.
128061da546Spatrick   ListEntry m_slow_runner; // Used for loop detection
129061da546Spatrick   ListEntry m_fast_runner; // Used for loop detection
130061da546Spatrick 
131*f6aab3d8Srobert   size_t m_list_capping_size = 0;
132061da546Spatrick   CompilerType m_element_type;
133061da546Spatrick   std::map<size_t, ListIterator> m_iterators;
134061da546Spatrick 
135061da546Spatrick   bool HasLoop(size_t count);
136061da546Spatrick   ValueObjectSP GetItem(size_t idx);
137061da546Spatrick };
138061da546Spatrick 
139061da546Spatrick class ForwardListFrontEnd : public AbstractListFrontEnd {
140061da546Spatrick public:
141061da546Spatrick   ForwardListFrontEnd(ValueObject &valobj);
142061da546Spatrick 
143061da546Spatrick   size_t CalculateNumChildren() override;
144061da546Spatrick   ValueObjectSP GetChildAtIndex(size_t idx) override;
145061da546Spatrick   bool Update() override;
146061da546Spatrick };
147061da546Spatrick 
148061da546Spatrick class ListFrontEnd : public AbstractListFrontEnd {
149061da546Spatrick public:
150061da546Spatrick   ListFrontEnd(lldb::ValueObjectSP valobj_sp);
151061da546Spatrick 
152061da546Spatrick   ~ListFrontEnd() override = default;
153061da546Spatrick 
154061da546Spatrick   size_t CalculateNumChildren() override;
155061da546Spatrick 
156061da546Spatrick   lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
157061da546Spatrick 
158061da546Spatrick   bool Update() override;
159061da546Spatrick 
160061da546Spatrick private:
161*f6aab3d8Srobert   lldb::addr_t m_node_address = 0;
162*f6aab3d8Srobert   ValueObject *m_tail = nullptr;
163061da546Spatrick };
164061da546Spatrick 
165061da546Spatrick } // end anonymous namespace
166061da546Spatrick 
Update()167061da546Spatrick bool AbstractListFrontEnd::Update() {
168061da546Spatrick   m_loop_detected = 0;
169061da546Spatrick   m_count = UINT32_MAX;
170061da546Spatrick   m_head = nullptr;
171061da546Spatrick   m_list_capping_size = 0;
172061da546Spatrick   m_slow_runner.SetEntry(nullptr);
173061da546Spatrick   m_fast_runner.SetEntry(nullptr);
174061da546Spatrick   m_iterators.clear();
175061da546Spatrick 
176061da546Spatrick   if (m_backend.GetTargetSP())
177061da546Spatrick     m_list_capping_size =
178061da546Spatrick         m_backend.GetTargetSP()->GetMaximumNumberOfChildrenToDisplay();
179061da546Spatrick   if (m_list_capping_size == 0)
180061da546Spatrick     m_list_capping_size = 255;
181061da546Spatrick 
182061da546Spatrick   CompilerType list_type = m_backend.GetCompilerType();
183061da546Spatrick   if (list_type.IsReferenceType())
184061da546Spatrick     list_type = list_type.GetNonReferenceType();
185061da546Spatrick 
186061da546Spatrick   if (list_type.GetNumTemplateArguments() == 0)
187061da546Spatrick     return false;
188061da546Spatrick   m_element_type = list_type.GetTypeTemplateArgument(0);
189061da546Spatrick 
190061da546Spatrick   return false;
191061da546Spatrick }
192061da546Spatrick 
HasLoop(size_t count)193061da546Spatrick bool AbstractListFrontEnd::HasLoop(size_t count) {
194061da546Spatrick   if (!g_use_loop_detect)
195061da546Spatrick     return false;
196061da546Spatrick   // don't bother checking for a loop if we won't actually need to jump nodes
197061da546Spatrick   if (m_count < 2)
198061da546Spatrick     return false;
199061da546Spatrick 
200061da546Spatrick   if (m_loop_detected == 0) {
201061da546Spatrick     // This is the first time we are being run (after the last update). Set up
202061da546Spatrick     // the loop invariant for the first element.
203061da546Spatrick     m_slow_runner = ListEntry(m_head).next();
204061da546Spatrick     m_fast_runner = m_slow_runner.next();
205061da546Spatrick     m_loop_detected = 1;
206061da546Spatrick   }
207061da546Spatrick 
208061da546Spatrick   // Loop invariant:
209061da546Spatrick   // Loop detection has been run over the first m_loop_detected elements. If
210061da546Spatrick   // m_slow_runner == m_fast_runner then the loop has been detected after
211061da546Spatrick   // m_loop_detected elements.
212061da546Spatrick   const size_t steps_to_run = std::min(count, m_count);
213061da546Spatrick   while (m_loop_detected < steps_to_run && m_slow_runner && m_fast_runner &&
214061da546Spatrick          m_slow_runner != m_fast_runner) {
215061da546Spatrick 
216061da546Spatrick     m_slow_runner = m_slow_runner.next();
217061da546Spatrick     m_fast_runner = m_fast_runner.next().next();
218061da546Spatrick     m_loop_detected++;
219061da546Spatrick   }
220061da546Spatrick   if (count <= m_loop_detected)
221061da546Spatrick     return false; // No loop in the first m_loop_detected elements.
222061da546Spatrick   if (!m_slow_runner || !m_fast_runner)
223061da546Spatrick     return false; // Reached the end of the list. Definitely no loops.
224061da546Spatrick   return m_slow_runner == m_fast_runner;
225061da546Spatrick }
226061da546Spatrick 
GetItem(size_t idx)227061da546Spatrick ValueObjectSP AbstractListFrontEnd::GetItem(size_t idx) {
228061da546Spatrick   size_t advance = idx;
229061da546Spatrick   ListIterator current(m_head);
230061da546Spatrick   if (idx > 0) {
231061da546Spatrick     auto cached_iterator = m_iterators.find(idx - 1);
232061da546Spatrick     if (cached_iterator != m_iterators.end()) {
233061da546Spatrick       current = cached_iterator->second;
234061da546Spatrick       advance = 1;
235061da546Spatrick     }
236061da546Spatrick   }
237061da546Spatrick   ValueObjectSP value_sp = current.advance(advance);
238061da546Spatrick   m_iterators[idx] = current;
239061da546Spatrick   return value_sp;
240061da546Spatrick }
241061da546Spatrick 
ForwardListFrontEnd(ValueObject & valobj)242061da546Spatrick ForwardListFrontEnd::ForwardListFrontEnd(ValueObject &valobj)
243061da546Spatrick     : AbstractListFrontEnd(valobj) {
244061da546Spatrick   Update();
245061da546Spatrick }
246061da546Spatrick 
CalculateNumChildren()247061da546Spatrick size_t ForwardListFrontEnd::CalculateNumChildren() {
248061da546Spatrick   if (m_count != UINT32_MAX)
249061da546Spatrick     return m_count;
250061da546Spatrick 
251061da546Spatrick   ListEntry current(m_head);
252061da546Spatrick   m_count = 0;
253061da546Spatrick   while (current && m_count < m_list_capping_size) {
254061da546Spatrick     ++m_count;
255061da546Spatrick     current = current.next();
256061da546Spatrick   }
257061da546Spatrick   return m_count;
258061da546Spatrick }
259061da546Spatrick 
GetChildAtIndex(size_t idx)260061da546Spatrick ValueObjectSP ForwardListFrontEnd::GetChildAtIndex(size_t idx) {
261061da546Spatrick   if (idx >= CalculateNumChildren())
262061da546Spatrick     return nullptr;
263061da546Spatrick 
264061da546Spatrick   if (!m_head)
265061da546Spatrick     return nullptr;
266061da546Spatrick 
267061da546Spatrick   if (HasLoop(idx + 1))
268061da546Spatrick     return nullptr;
269061da546Spatrick 
270061da546Spatrick   ValueObjectSP current_sp = GetItem(idx);
271061da546Spatrick   if (!current_sp)
272061da546Spatrick     return nullptr;
273061da546Spatrick 
274061da546Spatrick   current_sp = current_sp->GetChildAtIndex(1, true); // get the __value_ child
275061da546Spatrick   if (!current_sp)
276061da546Spatrick     return nullptr;
277061da546Spatrick 
278061da546Spatrick   // we need to copy current_sp into a new object otherwise we will end up with
279061da546Spatrick   // all items named __value_
280061da546Spatrick   DataExtractor data;
281061da546Spatrick   Status error;
282061da546Spatrick   current_sp->GetData(data, error);
283061da546Spatrick   if (error.Fail())
284061da546Spatrick     return nullptr;
285061da546Spatrick 
286061da546Spatrick   return CreateValueObjectFromData(llvm::formatv("[{0}]", idx).str(), data,
287061da546Spatrick                                    m_backend.GetExecutionContextRef(),
288061da546Spatrick                                    m_element_type);
289061da546Spatrick }
290061da546Spatrick 
Update()291061da546Spatrick bool ForwardListFrontEnd::Update() {
292061da546Spatrick   AbstractListFrontEnd::Update();
293061da546Spatrick 
294061da546Spatrick   Status err;
295061da546Spatrick   ValueObjectSP backend_addr(m_backend.AddressOf(err));
296061da546Spatrick   if (err.Fail() || !backend_addr)
297061da546Spatrick     return false;
298061da546Spatrick 
299061da546Spatrick   ValueObjectSP impl_sp(
300061da546Spatrick       m_backend.GetChildMemberWithName(ConstString("__before_begin_"), true));
301061da546Spatrick   if (!impl_sp)
302061da546Spatrick     return false;
303dda28197Spatrick   impl_sp = GetValueOfLibCXXCompressedPair(*impl_sp);
304061da546Spatrick   if (!impl_sp)
305061da546Spatrick     return false;
306061da546Spatrick   m_head = impl_sp->GetChildMemberWithName(ConstString("__next_"), true).get();
307061da546Spatrick   return false;
308061da546Spatrick }
309061da546Spatrick 
ListFrontEnd(lldb::ValueObjectSP valobj_sp)310061da546Spatrick ListFrontEnd::ListFrontEnd(lldb::ValueObjectSP valobj_sp)
311*f6aab3d8Srobert     : AbstractListFrontEnd(*valobj_sp) {
312061da546Spatrick   if (valobj_sp)
313061da546Spatrick     Update();
314061da546Spatrick }
315061da546Spatrick 
CalculateNumChildren()316061da546Spatrick size_t ListFrontEnd::CalculateNumChildren() {
317061da546Spatrick   if (m_count != UINT32_MAX)
318061da546Spatrick     return m_count;
319061da546Spatrick   if (!m_head || !m_tail || m_node_address == 0)
320061da546Spatrick     return 0;
321061da546Spatrick   ValueObjectSP size_alloc(
322061da546Spatrick       m_backend.GetChildMemberWithName(ConstString("__size_alloc_"), true));
323061da546Spatrick   if (size_alloc) {
324dda28197Spatrick     ValueObjectSP value = GetValueOfLibCXXCompressedPair(*size_alloc);
325061da546Spatrick     if (value) {
326061da546Spatrick       m_count = value->GetValueAsUnsigned(UINT32_MAX);
327061da546Spatrick     }
328061da546Spatrick   }
329061da546Spatrick   if (m_count != UINT32_MAX) {
330061da546Spatrick     return m_count;
331061da546Spatrick   } else {
332061da546Spatrick     uint64_t next_val = m_head->GetValueAsUnsigned(0);
333061da546Spatrick     uint64_t prev_val = m_tail->GetValueAsUnsigned(0);
334061da546Spatrick     if (next_val == 0 || prev_val == 0)
335061da546Spatrick       return 0;
336061da546Spatrick     if (next_val == m_node_address)
337061da546Spatrick       return 0;
338061da546Spatrick     if (next_val == prev_val)
339061da546Spatrick       return 1;
340061da546Spatrick     uint64_t size = 2;
341061da546Spatrick     ListEntry current(m_head);
342061da546Spatrick     while (current.next() && current.next().value() != m_node_address) {
343061da546Spatrick       size++;
344061da546Spatrick       current = current.next();
345061da546Spatrick       if (size > m_list_capping_size)
346061da546Spatrick         break;
347061da546Spatrick     }
348061da546Spatrick     return m_count = (size - 1);
349061da546Spatrick   }
350061da546Spatrick }
351061da546Spatrick 
GetChildAtIndex(size_t idx)352061da546Spatrick lldb::ValueObjectSP ListFrontEnd::GetChildAtIndex(size_t idx) {
353061da546Spatrick   static ConstString g_value("__value_");
354061da546Spatrick   static ConstString g_next("__next_");
355061da546Spatrick 
356061da546Spatrick   if (idx >= CalculateNumChildren())
357061da546Spatrick     return lldb::ValueObjectSP();
358061da546Spatrick 
359061da546Spatrick   if (!m_head || !m_tail || m_node_address == 0)
360061da546Spatrick     return lldb::ValueObjectSP();
361061da546Spatrick 
362061da546Spatrick   if (HasLoop(idx + 1))
363061da546Spatrick     return lldb::ValueObjectSP();
364061da546Spatrick 
365061da546Spatrick   ValueObjectSP current_sp = GetItem(idx);
366061da546Spatrick   if (!current_sp)
367061da546Spatrick     return lldb::ValueObjectSP();
368061da546Spatrick 
369061da546Spatrick   current_sp = current_sp->GetChildAtIndex(1, true); // get the __value_ child
370061da546Spatrick   if (!current_sp)
371061da546Spatrick     return lldb::ValueObjectSP();
372061da546Spatrick 
373061da546Spatrick   if (current_sp->GetName() == g_next) {
374061da546Spatrick     ProcessSP process_sp(current_sp->GetProcessSP());
375061da546Spatrick     if (!process_sp)
376061da546Spatrick       return lldb::ValueObjectSP();
377061da546Spatrick 
378061da546Spatrick     // if we grabbed the __next_ pointer, then the child is one pointer deep-er
379061da546Spatrick     lldb::addr_t addr = current_sp->GetParent()->GetPointerValue();
380061da546Spatrick     addr = addr + 2 * process_sp->GetAddressByteSize();
381061da546Spatrick     ExecutionContext exe_ctx(process_sp);
382061da546Spatrick     current_sp =
383061da546Spatrick         CreateValueObjectFromAddress("__value_", addr, exe_ctx, m_element_type);
384061da546Spatrick     if (!current_sp)
385061da546Spatrick       return lldb::ValueObjectSP();
386061da546Spatrick   }
387061da546Spatrick 
388061da546Spatrick   // we need to copy current_sp into a new object otherwise we will end up with
389061da546Spatrick   // all items named __value_
390061da546Spatrick   DataExtractor data;
391061da546Spatrick   Status error;
392061da546Spatrick   current_sp->GetData(data, error);
393061da546Spatrick   if (error.Fail())
394061da546Spatrick     return lldb::ValueObjectSP();
395061da546Spatrick 
396061da546Spatrick   StreamString name;
397061da546Spatrick   name.Printf("[%" PRIu64 "]", (uint64_t)idx);
398061da546Spatrick   return CreateValueObjectFromData(name.GetString(), data,
399061da546Spatrick                                    m_backend.GetExecutionContextRef(),
400061da546Spatrick                                    m_element_type);
401061da546Spatrick }
402061da546Spatrick 
Update()403061da546Spatrick bool ListFrontEnd::Update() {
404061da546Spatrick   AbstractListFrontEnd::Update();
405061da546Spatrick   m_tail = nullptr;
406061da546Spatrick   m_node_address = 0;
407061da546Spatrick 
408061da546Spatrick   Status err;
409061da546Spatrick   ValueObjectSP backend_addr(m_backend.AddressOf(err));
410061da546Spatrick   if (err.Fail() || !backend_addr)
411061da546Spatrick     return false;
412061da546Spatrick   m_node_address = backend_addr->GetValueAsUnsigned(0);
413061da546Spatrick   if (!m_node_address || m_node_address == LLDB_INVALID_ADDRESS)
414061da546Spatrick     return false;
415061da546Spatrick   ValueObjectSP impl_sp(
416061da546Spatrick       m_backend.GetChildMemberWithName(ConstString("__end_"), true));
417061da546Spatrick   if (!impl_sp)
418061da546Spatrick     return false;
419061da546Spatrick   m_head = impl_sp->GetChildMemberWithName(ConstString("__next_"), true).get();
420061da546Spatrick   m_tail = impl_sp->GetChildMemberWithName(ConstString("__prev_"), true).get();
421061da546Spatrick   return false;
422061da546Spatrick }
423061da546Spatrick 
LibcxxStdListSyntheticFrontEndCreator(CXXSyntheticChildren *,lldb::ValueObjectSP valobj_sp)424061da546Spatrick SyntheticChildrenFrontEnd *formatters::LibcxxStdListSyntheticFrontEndCreator(
425061da546Spatrick     CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
426061da546Spatrick   return (valobj_sp ? new ListFrontEnd(valobj_sp) : nullptr);
427061da546Spatrick }
428061da546Spatrick 
429061da546Spatrick SyntheticChildrenFrontEnd *
LibcxxStdForwardListSyntheticFrontEndCreator(CXXSyntheticChildren *,lldb::ValueObjectSP valobj_sp)430061da546Spatrick formatters::LibcxxStdForwardListSyntheticFrontEndCreator(
431061da546Spatrick     CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
432061da546Spatrick   return valobj_sp ? new ForwardListFrontEnd(*valobj_sp) : nullptr;
433061da546Spatrick }
434