xref: /openbsd-src/gnu/llvm/lldb/source/Plugins/Language/ObjC/NSString.cpp (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1dda28197Spatrick //===-- NSString.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 "NSString.h"
10061da546Spatrick 
11061da546Spatrick #include "lldb/Core/ValueObject.h"
12061da546Spatrick #include "lldb/Core/ValueObjectConstResult.h"
13061da546Spatrick #include "lldb/DataFormatters/FormattersHelpers.h"
14061da546Spatrick #include "lldb/DataFormatters/StringPrinter.h"
15061da546Spatrick #include "lldb/Target/Language.h"
16061da546Spatrick #include "lldb/Target/Target.h"
17*f6aab3d8Srobert #include "lldb/Utility/ConstString.h"
18061da546Spatrick #include "lldb/Utility/DataBufferHeap.h"
19061da546Spatrick #include "lldb/Utility/Endian.h"
20061da546Spatrick #include "lldb/Utility/Status.h"
21061da546Spatrick #include "lldb/Utility/Stream.h"
22061da546Spatrick 
23061da546Spatrick using namespace lldb;
24061da546Spatrick using namespace lldb_private;
25061da546Spatrick using namespace lldb_private::formatters;
26061da546Spatrick 
27061da546Spatrick std::map<ConstString, CXXFunctionSummaryFormat::Callback> &
GetAdditionalSummaries()28061da546Spatrick NSString_Additionals::GetAdditionalSummaries() {
29061da546Spatrick   static std::map<ConstString, CXXFunctionSummaryFormat::Callback> g_map;
30061da546Spatrick   return g_map;
31061da546Spatrick }
32061da546Spatrick 
NSStringSummaryProvider(ValueObject & valobj,Stream & stream,const TypeSummaryOptions & summary_options)33061da546Spatrick bool lldb_private::formatters::NSStringSummaryProvider(
34061da546Spatrick     ValueObject &valobj, Stream &stream,
35061da546Spatrick     const TypeSummaryOptions &summary_options) {
36061da546Spatrick   static ConstString g_TypeHint("NSString");
37061da546Spatrick 
38061da546Spatrick   ProcessSP process_sp = valobj.GetProcessSP();
39061da546Spatrick   if (!process_sp)
40061da546Spatrick     return false;
41061da546Spatrick 
42061da546Spatrick   ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(*process_sp);
43061da546Spatrick 
44061da546Spatrick   if (!runtime)
45061da546Spatrick     return false;
46061da546Spatrick 
47061da546Spatrick   ObjCLanguageRuntime::ClassDescriptorSP descriptor(
48061da546Spatrick       runtime->GetClassDescriptor(valobj));
49061da546Spatrick 
50061da546Spatrick   if (!descriptor.get() || !descriptor->IsValid())
51061da546Spatrick     return false;
52061da546Spatrick 
53061da546Spatrick   uint32_t ptr_size = process_sp->GetAddressByteSize();
54061da546Spatrick 
55061da546Spatrick   lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(0);
56061da546Spatrick 
57061da546Spatrick   if (!valobj_addr)
58061da546Spatrick     return false;
59061da546Spatrick 
60061da546Spatrick   ConstString class_name_cs = descriptor->GetClassName();
61061da546Spatrick   llvm::StringRef class_name = class_name_cs.GetStringRef();
62061da546Spatrick 
63061da546Spatrick   if (class_name.empty())
64061da546Spatrick     return false;
65061da546Spatrick 
66061da546Spatrick   bool is_tagged_ptr = class_name == "NSTaggedPointerString" &&
67061da546Spatrick                        descriptor->GetTaggedPointerInfo();
68061da546Spatrick   // for a tagged pointer, the descriptor has everything we need
69061da546Spatrick   if (is_tagged_ptr)
70061da546Spatrick     return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
71061da546Spatrick                                           summary_options);
72061da546Spatrick 
73061da546Spatrick   auto &additionals_map(NSString_Additionals::GetAdditionalSummaries());
74061da546Spatrick   auto iter = additionals_map.find(class_name_cs), end = additionals_map.end();
75061da546Spatrick   if (iter != end)
76061da546Spatrick     return iter->second(valobj, stream, summary_options);
77061da546Spatrick 
78061da546Spatrick   // if not a tagged pointer that we know about, try the normal route
79061da546Spatrick   uint64_t info_bits_location = valobj_addr + ptr_size;
80061da546Spatrick   if (process_sp->GetByteOrder() != lldb::eByteOrderLittle)
81061da546Spatrick     info_bits_location += 3;
82061da546Spatrick 
83061da546Spatrick   Status error;
84061da546Spatrick 
85061da546Spatrick   uint8_t info_bits = process_sp->ReadUnsignedIntegerFromMemory(
86061da546Spatrick       info_bits_location, 1, 0, error);
87061da546Spatrick   if (error.Fail())
88061da546Spatrick     return false;
89061da546Spatrick 
90061da546Spatrick   bool is_mutable = (info_bits & 1) == 1;
91061da546Spatrick   bool is_inline = (info_bits & 0x60) == 0;
92061da546Spatrick   bool has_explicit_length = (info_bits & (1 | 4)) != 4;
93061da546Spatrick   bool is_unicode = (info_bits & 0x10) == 0x10;
94061da546Spatrick   bool is_path_store = class_name == "NSPathStore2";
95061da546Spatrick   bool has_null = (info_bits & 8) == 8;
96061da546Spatrick 
97061da546Spatrick   size_t explicit_length = 0;
98061da546Spatrick   if (!has_null && has_explicit_length && !is_path_store) {
99061da546Spatrick     lldb::addr_t explicit_length_offset = 2 * ptr_size;
100061da546Spatrick     if (is_mutable && !is_inline)
101061da546Spatrick       explicit_length_offset =
102061da546Spatrick           explicit_length_offset + ptr_size; //  notInlineMutable.length;
103061da546Spatrick     else if (is_inline)
104061da546Spatrick       explicit_length = explicit_length + 0; // inline1.length;
105061da546Spatrick     else if (!is_inline && !is_mutable)
106061da546Spatrick       explicit_length_offset =
107061da546Spatrick           explicit_length_offset + ptr_size; // notInlineImmutable1.length;
108061da546Spatrick     else
109061da546Spatrick       explicit_length_offset = 0;
110061da546Spatrick 
111061da546Spatrick     if (explicit_length_offset) {
112061da546Spatrick       explicit_length_offset = valobj_addr + explicit_length_offset;
113061da546Spatrick       explicit_length = process_sp->ReadUnsignedIntegerFromMemory(
114061da546Spatrick           explicit_length_offset, 4, 0, error);
115061da546Spatrick     }
116061da546Spatrick   }
117061da546Spatrick 
118061da546Spatrick   const llvm::StringSet<> supported_string_classes = {
119061da546Spatrick       "NSString",     "CFMutableStringRef",
120061da546Spatrick       "CFStringRef",  "__NSCFConstantString",
121061da546Spatrick       "__NSCFString", "NSCFConstantString",
122061da546Spatrick       "NSCFString",   "NSPathStore2"};
123061da546Spatrick   if (supported_string_classes.count(class_name) == 0) {
124061da546Spatrick     // not one of us - but tell me class name
125061da546Spatrick     stream.Printf("class name = %s", class_name_cs.GetCString());
126061da546Spatrick     return true;
127061da546Spatrick   }
128061da546Spatrick 
129061da546Spatrick   std::string prefix, suffix;
130061da546Spatrick   if (Language *language =
131061da546Spatrick           Language::FindPlugin(summary_options.GetLanguage())) {
132061da546Spatrick     if (!language->GetFormatterPrefixSuffix(valobj, g_TypeHint, prefix,
133061da546Spatrick                                             suffix)) {
134061da546Spatrick       prefix.clear();
135061da546Spatrick       suffix.clear();
136061da546Spatrick     }
137061da546Spatrick   }
138061da546Spatrick 
139061da546Spatrick   StringPrinter::ReadStringAndDumpToStreamOptions options(valobj);
140061da546Spatrick   options.SetPrefixToken(prefix);
141061da546Spatrick   options.SetSuffixToken(suffix);
142061da546Spatrick 
143061da546Spatrick   if (is_mutable) {
144061da546Spatrick     uint64_t location = 2 * ptr_size + valobj_addr;
145061da546Spatrick     location = process_sp->ReadPointerFromMemory(location, error);
146061da546Spatrick     if (error.Fail())
147061da546Spatrick       return false;
148061da546Spatrick     if (has_explicit_length && is_unicode) {
149061da546Spatrick       options.SetLocation(location);
150*f6aab3d8Srobert       options.SetTargetSP(valobj.GetTargetSP());
151061da546Spatrick       options.SetStream(&stream);
152061da546Spatrick       options.SetQuote('"');
153061da546Spatrick       options.SetSourceSize(explicit_length);
154dda28197Spatrick       options.SetHasSourceSize(has_explicit_length);
155061da546Spatrick       options.SetNeedsZeroTermination(false);
156061da546Spatrick       options.SetIgnoreMaxLength(summary_options.GetCapping() ==
157061da546Spatrick                                  TypeSummaryCapping::eTypeSummaryUncapped);
158061da546Spatrick       options.SetBinaryZeroIsTerminator(false);
159061da546Spatrick       return StringPrinter::ReadStringAndDumpToStream<
160061da546Spatrick           StringPrinter::StringElementType::UTF16>(options);
161061da546Spatrick     } else {
162061da546Spatrick       options.SetLocation(location + 1);
163*f6aab3d8Srobert       options.SetTargetSP(valobj.GetTargetSP());
164061da546Spatrick       options.SetStream(&stream);
165061da546Spatrick       options.SetSourceSize(explicit_length);
166dda28197Spatrick       options.SetHasSourceSize(has_explicit_length);
167061da546Spatrick       options.SetNeedsZeroTermination(false);
168061da546Spatrick       options.SetIgnoreMaxLength(summary_options.GetCapping() ==
169061da546Spatrick                                  TypeSummaryCapping::eTypeSummaryUncapped);
170061da546Spatrick       options.SetBinaryZeroIsTerminator(false);
171061da546Spatrick       return StringPrinter::ReadStringAndDumpToStream<
172061da546Spatrick           StringPrinter::StringElementType::ASCII>(options);
173061da546Spatrick     }
174061da546Spatrick   } else if (is_inline && has_explicit_length && !is_unicode &&
175061da546Spatrick              !is_path_store && !is_mutable) {
176061da546Spatrick     uint64_t location = 3 * ptr_size + valobj_addr;
177061da546Spatrick 
178061da546Spatrick     options.SetLocation(location);
179*f6aab3d8Srobert     options.SetTargetSP(valobj.GetTargetSP());
180061da546Spatrick     options.SetStream(&stream);
181061da546Spatrick     options.SetQuote('"');
182061da546Spatrick     options.SetSourceSize(explicit_length);
183dda28197Spatrick     options.SetHasSourceSize(has_explicit_length);
184061da546Spatrick     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
185061da546Spatrick                                TypeSummaryCapping::eTypeSummaryUncapped);
186061da546Spatrick     return StringPrinter::ReadStringAndDumpToStream<
187061da546Spatrick         StringPrinter::StringElementType::ASCII>(options);
188061da546Spatrick   } else if (is_unicode) {
189061da546Spatrick     uint64_t location = valobj_addr + 2 * ptr_size;
190061da546Spatrick     if (is_inline) {
191061da546Spatrick       if (!has_explicit_length) {
192061da546Spatrick         return false;
193061da546Spatrick       } else
194061da546Spatrick         location += ptr_size;
195061da546Spatrick     } else {
196061da546Spatrick       location = process_sp->ReadPointerFromMemory(location, error);
197061da546Spatrick       if (error.Fail())
198061da546Spatrick         return false;
199061da546Spatrick     }
200061da546Spatrick     options.SetLocation(location);
201*f6aab3d8Srobert     options.SetTargetSP(valobj.GetTargetSP());
202061da546Spatrick     options.SetStream(&stream);
203061da546Spatrick     options.SetQuote('"');
204061da546Spatrick     options.SetSourceSize(explicit_length);
205dda28197Spatrick     options.SetHasSourceSize(has_explicit_length);
206061da546Spatrick     options.SetNeedsZeroTermination(!has_explicit_length);
207061da546Spatrick     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
208061da546Spatrick                                TypeSummaryCapping::eTypeSummaryUncapped);
209061da546Spatrick     options.SetBinaryZeroIsTerminator(!has_explicit_length);
210061da546Spatrick     return StringPrinter::ReadStringAndDumpToStream<
211061da546Spatrick         StringPrinter::StringElementType::UTF16>(options);
212061da546Spatrick   } else if (is_path_store) {
213*f6aab3d8Srobert     // _lengthAndRefCount is the first ivar of NSPathStore2 (after the isa).
214*f6aab3d8Srobert     uint64_t length_ivar_offset = 1 * ptr_size;
215*f6aab3d8Srobert     CompilerType length_type = valobj.GetCompilerType().GetBasicTypeFromAST(
216*f6aab3d8Srobert         lldb::eBasicTypeUnsignedInt);
217*f6aab3d8Srobert     ValueObjectSP length_valobj_sp =
218*f6aab3d8Srobert         valobj.GetSyntheticChildAtOffset(length_ivar_offset, length_type, true,
219*f6aab3d8Srobert                                          ConstString("_lengthAndRefCount"));
220*f6aab3d8Srobert     if (!length_valobj_sp)
221*f6aab3d8Srobert       return false;
222*f6aab3d8Srobert     // Get the length out of _lengthAndRefCount.
223*f6aab3d8Srobert     explicit_length = length_valobj_sp->GetValueAsUnsigned(0) >> 20;
224061da546Spatrick     lldb::addr_t location = valobj.GetValueAsUnsigned(0) + ptr_size + 4;
225061da546Spatrick 
226061da546Spatrick     options.SetLocation(location);
227*f6aab3d8Srobert     options.SetTargetSP(valobj.GetTargetSP());
228061da546Spatrick     options.SetStream(&stream);
229061da546Spatrick     options.SetQuote('"');
230061da546Spatrick     options.SetSourceSize(explicit_length);
231dda28197Spatrick     options.SetHasSourceSize(has_explicit_length);
232061da546Spatrick     options.SetNeedsZeroTermination(!has_explicit_length);
233061da546Spatrick     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
234061da546Spatrick                                TypeSummaryCapping::eTypeSummaryUncapped);
235061da546Spatrick     options.SetBinaryZeroIsTerminator(!has_explicit_length);
236061da546Spatrick     return StringPrinter::ReadStringAndDumpToStream<
237061da546Spatrick         StringPrinter::StringElementType::UTF16>(options);
238061da546Spatrick   } else if (is_inline) {
239061da546Spatrick     uint64_t location = valobj_addr + 2 * ptr_size;
240061da546Spatrick     if (!has_explicit_length) {
241061da546Spatrick       // in this kind of string, the byte before the string content is a length
242061da546Spatrick       // byte so let's try and use it to handle the embedded NUL case
243061da546Spatrick       Status error;
244061da546Spatrick       explicit_length =
245061da546Spatrick           process_sp->ReadUnsignedIntegerFromMemory(location, 1, 0, error);
246061da546Spatrick       has_explicit_length = !(error.Fail() || explicit_length == 0);
247061da546Spatrick       location++;
248061da546Spatrick     }
249061da546Spatrick     options.SetLocation(location);
250*f6aab3d8Srobert     options.SetTargetSP(valobj.GetTargetSP());
251061da546Spatrick     options.SetStream(&stream);
252061da546Spatrick     options.SetSourceSize(explicit_length);
253dda28197Spatrick     options.SetHasSourceSize(has_explicit_length);
254061da546Spatrick     options.SetNeedsZeroTermination(!has_explicit_length);
255061da546Spatrick     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
256061da546Spatrick                                TypeSummaryCapping::eTypeSummaryUncapped);
257061da546Spatrick     options.SetBinaryZeroIsTerminator(!has_explicit_length);
258061da546Spatrick     if (has_explicit_length)
259061da546Spatrick       return StringPrinter::ReadStringAndDumpToStream<
260061da546Spatrick           StringPrinter::StringElementType::UTF8>(options);
261061da546Spatrick     else
262061da546Spatrick       return StringPrinter::ReadStringAndDumpToStream<
263061da546Spatrick           StringPrinter::StringElementType::ASCII>(options);
264061da546Spatrick   } else {
265061da546Spatrick     uint64_t location = valobj_addr + 2 * ptr_size;
266061da546Spatrick     location = process_sp->ReadPointerFromMemory(location, error);
267061da546Spatrick     if (error.Fail())
268061da546Spatrick       return false;
269061da546Spatrick     if (has_explicit_length && !has_null)
270061da546Spatrick       explicit_length++; // account for the fact that there is no NULL and we
271061da546Spatrick                          // need to have one added
272061da546Spatrick     options.SetLocation(location);
273*f6aab3d8Srobert     options.SetTargetSP(valobj.GetTargetSP());
274061da546Spatrick     options.SetStream(&stream);
275061da546Spatrick     options.SetSourceSize(explicit_length);
276dda28197Spatrick     options.SetHasSourceSize(has_explicit_length);
277061da546Spatrick     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
278061da546Spatrick                                TypeSummaryCapping::eTypeSummaryUncapped);
279061da546Spatrick     return StringPrinter::ReadStringAndDumpToStream<
280061da546Spatrick         StringPrinter::StringElementType::ASCII>(options);
281061da546Spatrick   }
282061da546Spatrick }
283061da546Spatrick 
NSAttributedStringSummaryProvider(ValueObject & valobj,Stream & stream,const TypeSummaryOptions & options)284061da546Spatrick bool lldb_private::formatters::NSAttributedStringSummaryProvider(
285061da546Spatrick     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
286061da546Spatrick   TargetSP target_sp(valobj.GetTargetSP());
287061da546Spatrick   if (!target_sp)
288061da546Spatrick     return false;
289061da546Spatrick   uint32_t addr_size = target_sp->GetArchitecture().GetAddressByteSize();
290061da546Spatrick   uint64_t pointer_value = valobj.GetValueAsUnsigned(0);
291061da546Spatrick   if (!pointer_value)
292061da546Spatrick     return false;
293061da546Spatrick   pointer_value += addr_size;
294061da546Spatrick   CompilerType type(valobj.GetCompilerType());
295061da546Spatrick   ExecutionContext exe_ctx(target_sp, false);
296061da546Spatrick   ValueObjectSP child_ptr_sp(valobj.CreateValueObjectFromAddress(
297061da546Spatrick       "string_ptr", pointer_value, exe_ctx, type));
298061da546Spatrick   if (!child_ptr_sp)
299061da546Spatrick     return false;
300061da546Spatrick   DataExtractor data;
301061da546Spatrick   Status error;
302061da546Spatrick   child_ptr_sp->GetData(data, error);
303061da546Spatrick   if (error.Fail())
304061da546Spatrick     return false;
305061da546Spatrick   ValueObjectSP child_sp(child_ptr_sp->CreateValueObjectFromData(
306061da546Spatrick       "string_data", data, exe_ctx, type));
307061da546Spatrick   child_sp->GetValueAsUnsigned(0);
308061da546Spatrick   if (child_sp)
309061da546Spatrick     return NSStringSummaryProvider(*child_sp, stream, options);
310061da546Spatrick   return false;
311061da546Spatrick }
312061da546Spatrick 
NSMutableAttributedStringSummaryProvider(ValueObject & valobj,Stream & stream,const TypeSummaryOptions & options)313061da546Spatrick bool lldb_private::formatters::NSMutableAttributedStringSummaryProvider(
314061da546Spatrick     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
315061da546Spatrick   return NSAttributedStringSummaryProvider(valobj, stream, options);
316061da546Spatrick }
317061da546Spatrick 
NSTaggedString_SummaryProvider(ValueObject & valobj,ObjCLanguageRuntime::ClassDescriptorSP descriptor,Stream & stream,const TypeSummaryOptions & summary_options)318061da546Spatrick bool lldb_private::formatters::NSTaggedString_SummaryProvider(
319061da546Spatrick     ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
320061da546Spatrick     Stream &stream, const TypeSummaryOptions &summary_options) {
321061da546Spatrick   static ConstString g_TypeHint("NSString");
322061da546Spatrick 
323061da546Spatrick   if (!descriptor)
324061da546Spatrick     return false;
325061da546Spatrick   uint64_t len_bits = 0, data_bits = 0;
326061da546Spatrick   if (!descriptor->GetTaggedPointerInfo(&len_bits, &data_bits, nullptr))
327061da546Spatrick     return false;
328061da546Spatrick 
329061da546Spatrick   static const int g_MaxNonBitmaskedLen = 7; // TAGGED_STRING_UNPACKED_MAXLEN
330061da546Spatrick   static const int g_SixbitMaxLen = 9;
331061da546Spatrick   static const int g_fiveBitMaxLen = 11;
332061da546Spatrick 
333061da546Spatrick   static const char *sixBitToCharLookup = "eilotrm.apdnsIc ufkMShjTRxgC4013"
334061da546Spatrick                                           "bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX";
335061da546Spatrick 
336061da546Spatrick   if (len_bits > g_fiveBitMaxLen)
337061da546Spatrick     return false;
338061da546Spatrick 
339061da546Spatrick   std::string prefix, suffix;
340061da546Spatrick   if (Language *language =
341061da546Spatrick           Language::FindPlugin(summary_options.GetLanguage())) {
342061da546Spatrick     if (!language->GetFormatterPrefixSuffix(valobj, g_TypeHint, prefix,
343061da546Spatrick                                             suffix)) {
344061da546Spatrick       prefix.clear();
345061da546Spatrick       suffix.clear();
346061da546Spatrick     }
347061da546Spatrick   }
348061da546Spatrick 
349061da546Spatrick   // this is a fairly ugly trick - pretend that the numeric value is actually a
350061da546Spatrick   // char* this works under a few assumptions: little endian architecture
351061da546Spatrick   // sizeof(uint64_t) > g_MaxNonBitmaskedLen
352061da546Spatrick   if (len_bits <= g_MaxNonBitmaskedLen) {
353061da546Spatrick     stream.Printf("%s", prefix.c_str());
354061da546Spatrick     stream.Printf("\"%s\"", (const char *)&data_bits);
355061da546Spatrick     stream.Printf("%s", suffix.c_str());
356061da546Spatrick     return true;
357061da546Spatrick   }
358061da546Spatrick 
359061da546Spatrick   // if the data is bitmasked, we need to actually process the bytes
360061da546Spatrick   uint8_t bitmask = 0;
361061da546Spatrick   uint8_t shift_offset = 0;
362061da546Spatrick 
363061da546Spatrick   if (len_bits <= g_SixbitMaxLen) {
364061da546Spatrick     bitmask = 0x03f;
365061da546Spatrick     shift_offset = 6;
366061da546Spatrick   } else {
367061da546Spatrick     bitmask = 0x01f;
368061da546Spatrick     shift_offset = 5;
369061da546Spatrick   }
370061da546Spatrick 
371061da546Spatrick   std::vector<uint8_t> bytes;
372061da546Spatrick   bytes.resize(len_bits);
373061da546Spatrick   for (; len_bits > 0; data_bits >>= shift_offset, --len_bits) {
374061da546Spatrick     uint8_t packed = data_bits & bitmask;
375061da546Spatrick     bytes.insert(bytes.begin(), sixBitToCharLookup[packed]);
376061da546Spatrick   }
377061da546Spatrick 
378061da546Spatrick   stream.Printf("%s", prefix.c_str());
379061da546Spatrick   stream.Printf("\"%s\"", &bytes[0]);
380061da546Spatrick   stream.Printf("%s", suffix.c_str());
381061da546Spatrick   return true;
382061da546Spatrick }
383