NSSet.cpp revision 355940
1//===-- NSSet.cpp -----------------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "NSSet.h"
10
11#include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
12#include "lldb/Core/ValueObject.h"
13#include "lldb/Core/ValueObjectConstResult.h"
14#include "lldb/DataFormatters/FormattersHelpers.h"
15#include "lldb/Symbol/ClangASTContext.h"
16#include "lldb/Target/Language.h"
17#include "lldb/Target/Target.h"
18#include "lldb/Utility/DataBufferHeap.h"
19#include "lldb/Utility/Endian.h"
20#include "lldb/Utility/Status.h"
21#include "lldb/Utility/Stream.h"
22
23using namespace lldb;
24using namespace lldb_private;
25using namespace lldb_private::formatters;
26
27std::map<ConstString, CXXFunctionSummaryFormat::Callback> &
28NSSet_Additionals::GetAdditionalSummaries() {
29  static std::map<ConstString, CXXFunctionSummaryFormat::Callback> g_map;
30  return g_map;
31}
32
33std::map<ConstString, CXXSyntheticChildren::CreateFrontEndCallback> &
34NSSet_Additionals::GetAdditionalSynthetics() {
35  static std::map<ConstString, CXXSyntheticChildren::CreateFrontEndCallback>
36      g_map;
37  return g_map;
38}
39
40namespace lldb_private {
41namespace formatters {
42class NSSetISyntheticFrontEnd : public SyntheticChildrenFrontEnd {
43public:
44  NSSetISyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
45
46  ~NSSetISyntheticFrontEnd() override;
47
48  size_t CalculateNumChildren() override;
49
50  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
51
52  bool Update() override;
53
54  bool MightHaveChildren() override;
55
56  size_t GetIndexOfChildWithName(ConstString name) override;
57
58private:
59  struct DataDescriptor_32 {
60    uint32_t _used : 26;
61    uint32_t _szidx : 6;
62  };
63
64  struct DataDescriptor_64 {
65    uint64_t _used : 58;
66    uint32_t _szidx : 6;
67  };
68
69  struct SetItemDescriptor {
70    lldb::addr_t item_ptr;
71    lldb::ValueObjectSP valobj_sp;
72  };
73
74  ExecutionContextRef m_exe_ctx_ref;
75  uint8_t m_ptr_size;
76  DataDescriptor_32 *m_data_32;
77  DataDescriptor_64 *m_data_64;
78  lldb::addr_t m_data_ptr;
79  std::vector<SetItemDescriptor> m_children;
80};
81
82template <typename D32, typename D64>
83class GenericNSSetMSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
84public:
85  GenericNSSetMSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
86
87  ~GenericNSSetMSyntheticFrontEnd() override;
88
89  size_t CalculateNumChildren() override;
90
91  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
92
93  bool Update() override;
94
95  bool MightHaveChildren() override;
96
97  size_t GetIndexOfChildWithName(ConstString name) override;
98
99private:
100
101  struct SetItemDescriptor {
102    lldb::addr_t item_ptr;
103    lldb::ValueObjectSP valobj_sp;
104  };
105
106  ExecutionContextRef m_exe_ctx_ref;
107  uint8_t m_ptr_size;
108  D32 *m_data_32;
109  D64 *m_data_64;
110  std::vector<SetItemDescriptor> m_children;
111};
112
113namespace Foundation1300 {
114  struct DataDescriptor_32 {
115    uint32_t _used : 26;
116    uint32_t _size;
117    uint32_t _mutations;
118    uint32_t _objs_addr;
119  };
120
121  struct DataDescriptor_64 {
122    uint64_t _used : 58;
123    uint64_t _size;
124    uint64_t _mutations;
125    uint64_t _objs_addr;
126  };
127
128  using NSSetMSyntheticFrontEnd =
129      GenericNSSetMSyntheticFrontEnd<DataDescriptor_32, DataDescriptor_64>;
130}
131
132namespace Foundation1428 {
133  struct DataDescriptor_32 {
134    uint32_t _used : 26;
135    uint32_t _size;
136    uint32_t _objs_addr;
137    uint32_t _mutations;
138  };
139
140  struct DataDescriptor_64 {
141    uint64_t _used : 58;
142    uint64_t _size;
143    uint64_t _objs_addr;
144    uint64_t _mutations;
145  };
146
147  using NSSetMSyntheticFrontEnd =
148      GenericNSSetMSyntheticFrontEnd<DataDescriptor_32, DataDescriptor_64>;
149}
150
151namespace Foundation1437 {
152  struct DataDescriptor_32 {
153    uint32_t _cow;
154    // __table storage
155    uint32_t _objs_addr;
156    uint32_t _muts;
157    uint32_t _used : 26;
158    uint32_t _szidx : 6;
159  };
160
161  struct DataDescriptor_64 {
162    uint64_t _cow;
163    // __Table storage
164    uint64_t _objs_addr;
165    uint32_t _muts;
166    uint32_t _used : 26;
167    uint32_t _szidx : 6;
168  };
169
170  using NSSetMSyntheticFrontEnd =
171      GenericNSSetMSyntheticFrontEnd<DataDescriptor_32, DataDescriptor_64>;
172
173  template <typename DD>
174  uint64_t
175  __NSSetMSize_Impl(lldb_private::Process &process, lldb::addr_t valobj_addr,
176                    Status &error) {
177    const lldb::addr_t start_of_descriptor =
178        valobj_addr + process.GetAddressByteSize();
179    DD descriptor = DD();
180    process.ReadMemory(start_of_descriptor, &descriptor, sizeof(descriptor),
181                       error);
182    if (error.Fail()) {
183      return 0;
184    }
185    return descriptor._used;
186  }
187
188  uint64_t
189  __NSSetMSize(lldb_private::Process &process, lldb::addr_t valobj_addr,
190               Status &error) {
191    if (process.GetAddressByteSize() == 4) {
192      return __NSSetMSize_Impl<DataDescriptor_32>(process, valobj_addr, error);
193    } else {
194      return __NSSetMSize_Impl<DataDescriptor_64>(process, valobj_addr, error);
195    }
196  }
197}
198
199class NSSetCodeRunningSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
200public:
201  NSSetCodeRunningSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
202
203  ~NSSetCodeRunningSyntheticFrontEnd() override;
204
205  size_t CalculateNumChildren() override;
206
207  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
208
209  bool Update() override;
210
211  bool MightHaveChildren() override;
212
213  size_t GetIndexOfChildWithName(ConstString name) override;
214};
215} // namespace formatters
216} // namespace lldb_private
217
218template <bool cf_style>
219bool lldb_private::formatters::NSSetSummaryProvider(
220    ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
221  static ConstString g_TypeHint("NSSet");
222
223  ProcessSP process_sp = valobj.GetProcessSP();
224  if (!process_sp)
225    return false;
226
227  ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(*process_sp);
228
229  if (!runtime)
230    return false;
231
232  ObjCLanguageRuntime::ClassDescriptorSP descriptor(
233      runtime->GetClassDescriptor(valobj));
234
235  if (!descriptor || !descriptor->IsValid())
236    return false;
237
238  uint32_t ptr_size = process_sp->GetAddressByteSize();
239  bool is_64bit = (ptr_size == 8);
240
241  lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(0);
242
243  if (!valobj_addr)
244    return false;
245
246  uint64_t value = 0;
247
248  ConstString class_name_cs = descriptor->GetClassName();
249  const char *class_name = class_name_cs.GetCString();
250
251  if (!class_name || !*class_name)
252    return false;
253
254  if (!strcmp(class_name, "__NSSetI") ||
255      !strcmp(class_name, "__NSOrderedSetI")) {
256    Status error;
257    value = process_sp->ReadUnsignedIntegerFromMemory(valobj_addr + ptr_size,
258                                                      ptr_size, 0, error);
259    if (error.Fail())
260      return false;
261    value &= (is_64bit ? ~0xFC00000000000000UL : ~0xFC000000U);
262  } else if (!strcmp(class_name, "__NSSetM")) {
263    AppleObjCRuntime *apple_runtime =
264        llvm::dyn_cast_or_null<AppleObjCRuntime>(runtime);
265    Status error;
266    if (apple_runtime && apple_runtime->GetFoundationVersion() >= 1437) {
267      value = Foundation1437::__NSSetMSize(*process_sp, valobj_addr, error);
268    } else {
269      value = process_sp->ReadUnsignedIntegerFromMemory(valobj_addr + ptr_size,
270                                                        ptr_size, 0, error);
271      value &= (is_64bit ? ~0xFC00000000000000UL : ~0xFC000000U);
272    }
273    if (error.Fail())
274      return false;
275  } else {
276    auto &map(NSSet_Additionals::GetAdditionalSummaries());
277    auto iter = map.find(class_name_cs), end = map.end();
278    if (iter != end)
279      return iter->second(valobj, stream, options);
280    else
281      return false;
282  }
283
284  std::string prefix, suffix;
285  if (Language *language = Language::FindPlugin(options.GetLanguage())) {
286    if (!language->GetFormatterPrefixSuffix(valobj, g_TypeHint, prefix,
287                                            suffix)) {
288      prefix.clear();
289      suffix.clear();
290    }
291  }
292
293  stream.Printf("%s%" PRIu64 " %s%s%s", prefix.c_str(), value, "element",
294                value == 1 ? "" : "s", suffix.c_str());
295  return true;
296}
297
298SyntheticChildrenFrontEnd *
299lldb_private::formatters::NSSetSyntheticFrontEndCreator(
300    CXXSyntheticChildren *synth, lldb::ValueObjectSP valobj_sp) {
301  lldb::ProcessSP process_sp(valobj_sp->GetProcessSP());
302  if (!process_sp)
303    return nullptr;
304  ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(*process_sp);
305  if (!runtime)
306    return nullptr;
307
308  CompilerType valobj_type(valobj_sp->GetCompilerType());
309  Flags flags(valobj_type.GetTypeInfo());
310
311  if (flags.IsClear(eTypeIsPointer)) {
312    Status error;
313    valobj_sp = valobj_sp->AddressOf(error);
314    if (error.Fail() || !valobj_sp)
315      return nullptr;
316  }
317
318  ObjCLanguageRuntime::ClassDescriptorSP descriptor(
319      runtime->GetClassDescriptor(*valobj_sp));
320
321  if (!descriptor || !descriptor->IsValid())
322    return nullptr;
323
324  ConstString class_name_cs = descriptor->GetClassName();
325  const char *class_name = class_name_cs.GetCString();
326
327  if (!class_name || !*class_name)
328    return nullptr;
329
330  if (!strcmp(class_name, "__NSSetI") ||
331      !strcmp(class_name, "__NSOrderedSetI")) {
332    return (new NSSetISyntheticFrontEnd(valobj_sp));
333  } else if (!strcmp(class_name, "__NSSetM")) {
334    AppleObjCRuntime *apple_runtime =
335        llvm::dyn_cast_or_null<AppleObjCRuntime>(runtime);
336    if (apple_runtime) {
337      if (apple_runtime->GetFoundationVersion() >= 1437)
338        return (new Foundation1437::NSSetMSyntheticFrontEnd(valobj_sp));
339      else if (apple_runtime->GetFoundationVersion() >= 1428)
340        return (new Foundation1428::NSSetMSyntheticFrontEnd(valobj_sp));
341      else
342        return (new Foundation1300::NSSetMSyntheticFrontEnd(valobj_sp));
343    } else {
344      return (new Foundation1300::NSSetMSyntheticFrontEnd(valobj_sp));
345    }
346  } else {
347    auto &map(NSSet_Additionals::GetAdditionalSynthetics());
348    auto iter = map.find(class_name_cs), end = map.end();
349    if (iter != end)
350      return iter->second(synth, valobj_sp);
351    return nullptr;
352  }
353}
354
355lldb_private::formatters::NSSetISyntheticFrontEnd::NSSetISyntheticFrontEnd(
356    lldb::ValueObjectSP valobj_sp)
357    : SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(), m_ptr_size(8),
358      m_data_32(nullptr), m_data_64(nullptr) {
359  if (valobj_sp)
360    Update();
361}
362
363lldb_private::formatters::NSSetISyntheticFrontEnd::~NSSetISyntheticFrontEnd() {
364  delete m_data_32;
365  m_data_32 = nullptr;
366  delete m_data_64;
367  m_data_64 = nullptr;
368}
369
370size_t
371lldb_private::formatters::NSSetISyntheticFrontEnd::GetIndexOfChildWithName(
372    ConstString name) {
373  const char *item_name = name.GetCString();
374  uint32_t idx = ExtractIndexFromString(item_name);
375  if (idx < UINT32_MAX && idx >= CalculateNumChildren())
376    return UINT32_MAX;
377  return idx;
378}
379
380size_t
381lldb_private::formatters::NSSetISyntheticFrontEnd::CalculateNumChildren() {
382  if (!m_data_32 && !m_data_64)
383    return 0;
384  return (m_data_32 ? m_data_32->_used : m_data_64->_used);
385}
386
387bool lldb_private::formatters::NSSetISyntheticFrontEnd::Update() {
388  m_children.clear();
389  delete m_data_32;
390  m_data_32 = nullptr;
391  delete m_data_64;
392  m_data_64 = nullptr;
393  m_ptr_size = 0;
394  ValueObjectSP valobj_sp = m_backend.GetSP();
395  if (!valobj_sp)
396    return false;
397  if (!valobj_sp)
398    return false;
399  m_exe_ctx_ref = valobj_sp->GetExecutionContextRef();
400  Status error;
401  if (valobj_sp->IsPointerType()) {
402    valobj_sp = valobj_sp->Dereference(error);
403    if (error.Fail() || !valobj_sp)
404      return false;
405  }
406  error.Clear();
407  lldb::ProcessSP process_sp(valobj_sp->GetProcessSP());
408  if (!process_sp)
409    return false;
410  m_ptr_size = process_sp->GetAddressByteSize();
411  uint64_t data_location = valobj_sp->GetAddressOf() + m_ptr_size;
412  if (m_ptr_size == 4) {
413    m_data_32 = new DataDescriptor_32();
414    process_sp->ReadMemory(data_location, m_data_32, sizeof(DataDescriptor_32),
415                           error);
416  } else {
417    m_data_64 = new DataDescriptor_64();
418    process_sp->ReadMemory(data_location, m_data_64, sizeof(DataDescriptor_64),
419                           error);
420  }
421  if (error.Fail())
422    return false;
423  m_data_ptr = data_location + m_ptr_size;
424  return false;
425}
426
427bool lldb_private::formatters::NSSetISyntheticFrontEnd::MightHaveChildren() {
428  return true;
429}
430
431lldb::ValueObjectSP
432lldb_private::formatters::NSSetISyntheticFrontEnd::GetChildAtIndex(size_t idx) {
433  uint32_t num_children = CalculateNumChildren();
434
435  if (idx >= num_children)
436    return lldb::ValueObjectSP();
437
438  ProcessSP process_sp = m_exe_ctx_ref.GetProcessSP();
439  if (!process_sp)
440    return lldb::ValueObjectSP();
441
442  if (m_children.empty()) {
443    // do the scan phase
444    lldb::addr_t obj_at_idx = 0;
445
446    uint32_t tries = 0;
447    uint32_t test_idx = 0;
448
449    while (tries < num_children) {
450      obj_at_idx = m_data_ptr + (test_idx * m_ptr_size);
451      if (!process_sp)
452        return lldb::ValueObjectSP();
453      Status error;
454      obj_at_idx = process_sp->ReadPointerFromMemory(obj_at_idx, error);
455      if (error.Fail())
456        return lldb::ValueObjectSP();
457
458      test_idx++;
459
460      if (!obj_at_idx)
461        continue;
462      tries++;
463
464      SetItemDescriptor descriptor = {obj_at_idx, lldb::ValueObjectSP()};
465
466      m_children.push_back(descriptor);
467    }
468  }
469
470  if (idx >= m_children.size()) // should never happen
471    return lldb::ValueObjectSP();
472
473  SetItemDescriptor &set_item = m_children[idx];
474  if (!set_item.valobj_sp) {
475    auto ptr_size = process_sp->GetAddressByteSize();
476    DataBufferHeap buffer(ptr_size, 0);
477    switch (ptr_size) {
478    case 0: // architecture has no clue?? - fail
479      return lldb::ValueObjectSP();
480    case 4:
481      *((uint32_t *)buffer.GetBytes()) = (uint32_t)set_item.item_ptr;
482      break;
483    case 8:
484      *((uint64_t *)buffer.GetBytes()) = (uint64_t)set_item.item_ptr;
485      break;
486    default:
487      assert(false && "pointer size is not 4 nor 8 - get out of here ASAP");
488    }
489    StreamString idx_name;
490    idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
491
492    DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(),
493                       process_sp->GetByteOrder(),
494                       process_sp->GetAddressByteSize());
495
496    set_item.valobj_sp = CreateValueObjectFromData(
497        idx_name.GetString(), data, m_exe_ctx_ref,
498        m_backend.GetCompilerType().GetBasicTypeFromAST(
499            lldb::eBasicTypeObjCID));
500  }
501  return set_item.valobj_sp;
502}
503
504template <typename D32, typename D64>
505lldb_private::formatters::
506  GenericNSSetMSyntheticFrontEnd<D32, D64>::GenericNSSetMSyntheticFrontEnd(
507    lldb::ValueObjectSP valobj_sp)
508    : SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(), m_ptr_size(8),
509      m_data_32(nullptr), m_data_64(nullptr) {
510  if (valobj_sp)
511    Update();
512}
513
514template <typename D32, typename D64>
515lldb_private::formatters::
516  GenericNSSetMSyntheticFrontEnd<D32, D64>::~GenericNSSetMSyntheticFrontEnd() {
517  delete m_data_32;
518  m_data_32 = nullptr;
519  delete m_data_64;
520  m_data_64 = nullptr;
521}
522
523template <typename D32, typename D64>
524size_t
525lldb_private::formatters::
526  GenericNSSetMSyntheticFrontEnd<D32, D64>::GetIndexOfChildWithName(
527    ConstString name) {
528  const char *item_name = name.GetCString();
529  uint32_t idx = ExtractIndexFromString(item_name);
530  if (idx < UINT32_MAX && idx >= CalculateNumChildren())
531    return UINT32_MAX;
532  return idx;
533}
534
535template <typename D32, typename D64>
536size_t
537lldb_private::formatters::
538  GenericNSSetMSyntheticFrontEnd<D32, D64>::CalculateNumChildren() {
539  if (!m_data_32 && !m_data_64)
540    return 0;
541  return (m_data_32 ? m_data_32->_used : m_data_64->_used);
542}
543
544template <typename D32, typename D64>
545bool
546lldb_private::formatters::
547  GenericNSSetMSyntheticFrontEnd<D32, D64>::Update() {
548  m_children.clear();
549  ValueObjectSP valobj_sp = m_backend.GetSP();
550  m_ptr_size = 0;
551  delete m_data_32;
552  m_data_32 = nullptr;
553  delete m_data_64;
554  m_data_64 = nullptr;
555  if (!valobj_sp)
556    return false;
557  if (!valobj_sp)
558    return false;
559  m_exe_ctx_ref = valobj_sp->GetExecutionContextRef();
560  Status error;
561  if (valobj_sp->IsPointerType()) {
562    valobj_sp = valobj_sp->Dereference(error);
563    if (error.Fail() || !valobj_sp)
564      return false;
565  }
566  error.Clear();
567  lldb::ProcessSP process_sp(valobj_sp->GetProcessSP());
568  if (!process_sp)
569    return false;
570  m_ptr_size = process_sp->GetAddressByteSize();
571  uint64_t data_location = valobj_sp->GetAddressOf() + m_ptr_size;
572  if (m_ptr_size == 4) {
573    m_data_32 = new D32();
574    process_sp->ReadMemory(data_location, m_data_32, sizeof(D32),
575                           error);
576  } else {
577    m_data_64 = new D64();
578    process_sp->ReadMemory(data_location, m_data_64, sizeof(D64),
579                           error);
580  }
581  if (error.Fail())
582    return false;
583  return false;
584}
585
586template <typename D32, typename D64>
587bool
588lldb_private::formatters::
589  GenericNSSetMSyntheticFrontEnd<D32, D64>::MightHaveChildren() {
590  return true;
591}
592
593template <typename D32, typename D64>
594lldb::ValueObjectSP
595lldb_private::formatters::
596  GenericNSSetMSyntheticFrontEnd<D32, D64>::GetChildAtIndex(size_t idx) {
597  lldb::addr_t m_objs_addr =
598      (m_data_32 ? m_data_32->_objs_addr : m_data_64->_objs_addr);
599
600  uint32_t num_children = CalculateNumChildren();
601
602  if (idx >= num_children)
603    return lldb::ValueObjectSP();
604
605  ProcessSP process_sp = m_exe_ctx_ref.GetProcessSP();
606  if (!process_sp)
607    return lldb::ValueObjectSP();
608
609  if (m_children.empty()) {
610    // do the scan phase
611    lldb::addr_t obj_at_idx = 0;
612
613    uint32_t tries = 0;
614    uint32_t test_idx = 0;
615
616    while (tries < num_children) {
617      obj_at_idx = m_objs_addr + (test_idx * m_ptr_size);
618      if (!process_sp)
619        return lldb::ValueObjectSP();
620      Status error;
621      obj_at_idx = process_sp->ReadPointerFromMemory(obj_at_idx, error);
622      if (error.Fail())
623        return lldb::ValueObjectSP();
624
625      test_idx++;
626
627      if (!obj_at_idx)
628        continue;
629      tries++;
630
631      SetItemDescriptor descriptor = {obj_at_idx, lldb::ValueObjectSP()};
632
633      m_children.push_back(descriptor);
634    }
635  }
636
637  if (idx >= m_children.size()) // should never happen
638    return lldb::ValueObjectSP();
639
640  SetItemDescriptor &set_item = m_children[idx];
641  if (!set_item.valobj_sp) {
642    auto ptr_size = process_sp->GetAddressByteSize();
643    DataBufferHeap buffer(ptr_size, 0);
644    switch (ptr_size) {
645    case 0: // architecture has no clue?? - fail
646      return lldb::ValueObjectSP();
647    case 4:
648      *((uint32_t *)buffer.GetBytes()) = (uint32_t)set_item.item_ptr;
649      break;
650    case 8:
651      *((uint64_t *)buffer.GetBytes()) = (uint64_t)set_item.item_ptr;
652      break;
653    default:
654      assert(false && "pointer size is not 4 nor 8 - get out of here ASAP");
655    }
656    StreamString idx_name;
657    idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
658
659    DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(),
660                       process_sp->GetByteOrder(),
661                       process_sp->GetAddressByteSize());
662
663    set_item.valobj_sp = CreateValueObjectFromData(
664        idx_name.GetString(), data, m_exe_ctx_ref,
665        m_backend.GetCompilerType().GetBasicTypeFromAST(
666            lldb::eBasicTypeObjCID));
667  }
668  return set_item.valobj_sp;
669}
670
671template bool lldb_private::formatters::NSSetSummaryProvider<true>(
672    ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options);
673
674template bool lldb_private::formatters::NSSetSummaryProvider<false>(
675    ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options);
676