VirtualBox

source: vbox/trunk/src/VBox/Main/glue/string.cpp@ 80835

Last change on this file since 80835 was 80835, checked in by vboxsync, 6 years ago

Main/glue: Added bstr testcase and fixed printf bugs. Introduced jolt, joltNoThrow, reserve and reserveNoThrow for the low level allocation operations needed by printf.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.3 KB
Line 
1/* $Id: string.cpp 80835 2019-09-16 20:30:40Z vboxsync $ */
2/** @file
3 * MS COM / XPCOM Abstraction Layer - UTF-8 and UTF-16 string classes.
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include "VBox/com/string.h"
19
20#include <iprt/err.h>
21#include <iprt/path.h>
22#include <iprt/log.h>
23#include <iprt/string.h>
24#include <iprt/uni.h>
25
26namespace com
27{
28
29// BSTR representing a null wide char with 32 bits of length prefix (0);
30// this will work on Windows as well as other platforms where BSTR does
31// not use length prefixes
32const OLECHAR g_achEmptyBstr[3] = { 0, 0, 0 };
33const BSTR g_bstrEmpty = (BSTR)&g_achEmptyBstr[2];
34
35/* static */
36const Bstr Bstr::Empty; /* default ctor is OK */
37
38
39Bstr &Bstr::printf(const char *pszFormat, ...)
40{
41 va_list va;
42 va_start(va, pszFormat);
43 HRESULT hrc = printfVNoThrow(pszFormat, va);
44 va_end(va);
45 if (hrc == S_OK)
46 { /* likely */ }
47 else
48 throw std::bad_alloc();
49 return *this;
50}
51
52HRESULT Bstr::printfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT
53{
54 va_list va;
55 va_start(va, pszFormat);
56 HRESULT hrc = printfVNoThrow(pszFormat, va);
57 va_end(va);
58 return hrc;
59}
60
61
62Bstr &Bstr::printfV(const char *pszFormat, va_list va)
63{
64 HRESULT hrc = printfVNoThrow(pszFormat, va);
65 if (hrc == S_OK)
66 { /* likely */ }
67 else
68 throw std::bad_alloc();
69 return *this;
70}
71
72struct BSTRNOTHROW
73{
74 Bstr *pThis;
75 size_t cwcAlloc;
76 size_t offDst;
77 HRESULT hrc;
78};
79
80/**
81 * Callback used with RTStrFormatV by Bstr::printfVNoThrow.
82 *
83 * @returns The number of bytes added (not used).
84 *
85 * @param pvArg Pointer to a BSTRNOTHROW structure.
86 * @param pachChars The characters to append.
87 * @param cbChars The number of characters. 0 on the final callback.
88 */
89/*static*/ DECLCALLBACK(size_t)
90Bstr::printfOutputCallbackNoThrow(void *pvArg, const char *pachChars, size_t cbChars) RT_NOEXCEPT
91{
92 BSTRNOTHROW *pArgs = (BSTRNOTHROW *)pvArg;
93 if (cbChars)
94 {
95 size_t cwcAppend;
96 int rc = ::RTStrCalcUtf16LenEx(pachChars, cbChars, &cwcAppend);
97 AssertRCReturnStmt(rc, pArgs->hrc = E_UNEXPECTED, 0);
98
99 /*
100 * Ensure we've got sufficient memory.
101 */
102 Bstr *pThis = pArgs->pThis;
103 size_t const cwcBoth = pArgs->offDst + cwcAppend;
104 if (cwcBoth >= pArgs->cwcAlloc)
105 {
106 if (pArgs->hrc == S_OK)
107 {
108 /* Double the buffer size, if it's less that _1M. Align sizes like
109 for append. */
110 size_t cwcAlloc = RT_ALIGN_Z(pArgs->cwcAlloc, 128);
111 cwcAlloc += RT_MIN(cwcAlloc, _1M);
112 if (cwcAlloc <= cwcBoth)
113 cwcAlloc = RT_ALIGN_Z(cwcBoth + 1, 512);
114 pArgs->hrc = pThis->reserveNoThrow(cwcAlloc, true /*fForce*/);
115 AssertMsgReturn(pArgs->hrc == S_OK, ("cwcAlloc=%#zx\n", cwcAlloc), 0);
116 pArgs->cwcAlloc = cwcAlloc;
117 }
118 else
119 return 0;
120 }
121
122 /*
123 * Do the conversion.
124 */
125 PRTUTF16 pwszDst = pThis->m_bstr + pArgs->offDst;
126 Assert(pArgs->cwcAlloc > pArgs->offDst);
127 rc = ::RTStrToUtf16Ex(pachChars, cbChars, &pwszDst, pArgs->cwcAlloc - pArgs->offDst, &cwcAppend);
128 AssertRCReturnStmt(rc, pArgs->hrc = E_UNEXPECTED, 0);
129 pArgs->offDst += cwcAppend;
130 }
131 return cbChars;
132}
133
134HRESULT Bstr::printfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT
135{
136 cleanup();
137 BSTRNOTHROW Args = { this, 0, 0, S_OK };
138 RTStrFormatV(printfOutputCallbackNoThrow, &Args, NULL, NULL, pszFormat, va);
139 if (Args.hrc == S_OK)
140 {
141 Args.hrc = joltNoThrow(Args.offDst);
142 if (Args.hrc == S_OK)
143 return S_OK;
144 }
145 cleanup();
146 return Args.hrc;
147}
148
149void Bstr::copyFromN(const char *a_pszSrc, size_t a_cchMax)
150{
151 /*
152 * Initialize m_bstr first in case of throws further down in the code, then
153 * check for empty input (m_bstr == NULL means empty, there are no NULL
154 * strings).
155 */
156 m_bstr = NULL;
157 if (!a_cchMax || !a_pszSrc || !*a_pszSrc)
158 return;
159
160 /*
161 * Calculate the length and allocate a BSTR string buffer of the right
162 * size, i.e. optimize heap usage.
163 */
164 size_t cwc;
165 int vrc = ::RTStrCalcUtf16LenEx(a_pszSrc, a_cchMax, &cwc);
166 if (RT_SUCCESS(vrc))
167 {
168 m_bstr = ::SysAllocStringByteLen(NULL, (unsigned)(cwc * sizeof(OLECHAR)));
169 if (RT_LIKELY(m_bstr))
170 {
171 PRTUTF16 pwsz = (PRTUTF16)m_bstr;
172 vrc = ::RTStrToUtf16Ex(a_pszSrc, a_cchMax, &pwsz, cwc + 1, NULL);
173 if (RT_SUCCESS(vrc))
174 return;
175
176 /* This should not happen! */
177 AssertRC(vrc);
178 cleanup();
179 }
180 }
181 else /* ASSUME: input is valid Utf-8. Fake out of memory error. */
182 AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTStrNLen(a_pszSrc, a_cchMax), a_pszSrc));
183 throw std::bad_alloc();
184}
185
186int Bstr::compareUtf8(const char *a_pszRight, CaseSensitivity a_enmCase /*= CaseSensitive*/) const
187{
188 PCRTUTF16 pwszLeft = m_bstr;
189
190 /*
191 * Special case for null/empty strings. Unlike RTUtf16Cmp we
192 * treat null and empty equally.
193 */
194 if (!pwszLeft)
195 return !a_pszRight || *a_pszRight == '\0' ? 0 : -1;
196 if (!a_pszRight)
197 return *pwszLeft == '\0' ? 0 : 1;
198
199 /*
200 * Compare with a UTF-8 string by enumerating them char by char.
201 */
202 for (;;)
203 {
204 RTUNICP ucLeft;
205 int rc = RTUtf16GetCpEx(&pwszLeft, &ucLeft);
206 AssertRCReturn(rc, 1);
207
208 RTUNICP ucRight;
209 rc = RTStrGetCpEx(&a_pszRight, &ucRight);
210 AssertRCReturn(rc, -1);
211 if (ucLeft == ucRight)
212 {
213 if (ucLeft)
214 continue;
215 return 0;
216 }
217
218 if (a_enmCase == CaseInsensitive)
219 {
220 if (RTUniCpToUpper(ucLeft) == RTUniCpToUpper(ucRight))
221 continue;
222 if (RTUniCpToLower(ucLeft) == RTUniCpToLower(ucRight))
223 continue;
224 }
225
226 return ucLeft < ucRight ? -1 : 1;
227 }
228}
229
230
231#ifndef VBOX_WITH_XPCOM
232
233HRESULT Bstr::joltNoThrow(ssize_t cwcNew /* = -1*/)
234{
235 if (m_bstr)
236 {
237 size_t const cwcAlloc = ::SysStringLen(m_bstr);
238 size_t const cwcActual = cwcNew < 0 ? ::RTUtf16Len(m_bstr) : (size_t)cwcNew;
239 Assert(cwcNew < 0 || cwcActual == ::RTUtf16Len(m_bstr));
240 if (cwcActual != cwcAlloc)
241 {
242 Assert(cwcActual <= cwcAlloc);
243 Assert((unsigned int)cwcActual == cwcActual);
244
245 /* Official way: Reallocate the string. We could of course just update the size-prefix if we dared... */
246 if (!::SysReAllocStringLen(&m_bstr, NULL, (unsigned int)cwcActual))
247 {
248 AssertFailed();
249 return E_OUTOFMEMORY;
250 }
251 }
252 }
253 else
254 Assert(cwcNew <= 0);
255 return S_OK;
256}
257
258
259void Bstr::jolt(ssize_t cwcNew /* = -1*/)
260{
261 HRESULT hrc = joltNoThrow(cwcNew);
262 if (hrc != S_OK)
263 throw std::bad_alloc();
264}
265
266#endif /* !VBOX_WITH_XPCOM */
267
268
269HRESULT Bstr::reserveNoThrow(size_t cwcMin, bool fForce /*= false*/) RT_NOEXCEPT
270{
271 /* If not forcing the string to the cwcMin length, check cwcMin against the
272 current string length: */
273 if (!fForce)
274 {
275 size_t cwcCur = m_bstr ? ::SysStringLen(m_bstr) : 0;
276 if (cwcCur >= cwcMin)
277 return S_OK;
278 }
279
280 /* The documentation for SysReAllocStringLen hints about it being allergic
281 to NULL in some way or another, so we call SysAllocStringLen directly
282 when appropriate: */
283 if (m_bstr)
284 AssertReturn(::SysReAllocStringLen(&m_bstr, NULL, (unsigned int)cwcMin) != FALSE, E_OUTOFMEMORY);
285 else if (cwcMin > 0)
286 {
287 m_bstr = ::SysAllocStringLen(NULL, (unsigned int)cwcMin);
288 AssertReturn(m_bstr, E_OUTOFMEMORY);
289 }
290
291 return S_OK;
292}
293
294
295void Bstr::reserve(size_t cwcMin, bool fForce /*= false*/)
296{
297 HRESULT hrc = reserveNoThrow(cwcMin, fForce);
298 if (hrc != S_OK)
299 throw std::bad_alloc();
300}
301
302
303/* static */
304const Utf8Str Utf8Str::Empty; /* default ctor is OK */
305
306#if defined(VBOX_WITH_XPCOM)
307void Utf8Str::cloneTo(char **pstr) const
308{
309 size_t cb = length() + 1;
310 *pstr = (char *)nsMemory::Alloc(cb);
311 if (RT_LIKELY(*pstr))
312 memcpy(*pstr, c_str(), cb);
313 else
314 throw std::bad_alloc();
315}
316
317HRESULT Utf8Str::cloneToEx(char **pstr) const
318{
319 size_t cb = length() + 1;
320 *pstr = (char *)nsMemory::Alloc(cb);
321 if (RT_LIKELY(*pstr))
322 {
323 memcpy(*pstr, c_str(), cb);
324 return S_OK;
325 }
326 return E_OUTOFMEMORY;
327}
328#endif
329
330Utf8Str& Utf8Str::stripTrailingSlash()
331{
332 if (length())
333 {
334 ::RTPathStripTrailingSlash(m_psz);
335 jolt();
336 }
337 return *this;
338}
339
340Utf8Str& Utf8Str::stripFilename()
341{
342 if (length())
343 {
344 RTPathStripFilename(m_psz);
345 jolt();
346 }
347 return *this;
348}
349
350Utf8Str& Utf8Str::stripPath()
351{
352 if (length())
353 {
354 char *pszName = ::RTPathFilename(m_psz);
355 if (pszName)
356 {
357 size_t cchName = length() - (pszName - m_psz);
358 memmove(m_psz, pszName, cchName + 1);
359 jolt();
360 }
361 else
362 cleanup();
363 }
364 return *this;
365}
366
367Utf8Str& Utf8Str::stripSuffix()
368{
369 if (length())
370 {
371 RTPathStripSuffix(m_psz);
372 jolt();
373 }
374 return *this;
375}
376
377size_t Utf8Str::parseKeyValue(Utf8Str &a_rKey, Utf8Str &a_rValue, size_t a_offStart /* = 0*/,
378 const Utf8Str &a_rPairSeparator /*= ","*/, const Utf8Str &a_rKeyValueSeparator /*= "="*/) const
379{
380 /* Find the end of the next pair, skipping empty pairs.
381 Note! The skipping allows us to pass the return value of a parseKeyValue()
382 call as offStart to the next call. */
383 size_t offEnd;
384 while ( a_offStart == (offEnd = find(&a_rPairSeparator, a_offStart))
385 && offEnd != npos)
386 a_offStart++;
387
388 /* Look for a key/value separator before the end of the pair.
389 ASSUMES npos value returned by find when the substring is not found is
390 really high. */
391 size_t offKeyValueSep = find(&a_rKeyValueSeparator, a_offStart);
392 if (offKeyValueSep < offEnd)
393 {
394 a_rKey = substr(a_offStart, offKeyValueSep - a_offStart);
395 if (offEnd == npos)
396 offEnd = m_cch; /* No confusing npos when returning strings. */
397 a_rValue = substr(offKeyValueSep + 1, offEnd - offKeyValueSep - 1);
398 }
399 else
400 {
401 a_rKey.setNull();
402 a_rValue.setNull();
403 }
404
405 return offEnd;
406}
407
408/**
409 * Internal function used in Utf8Str copy constructors and assignment when
410 * copying from a UTF-16 string.
411 *
412 * As with the RTCString::copyFrom() variants, this unconditionally sets the
413 * members to a copy of the given other strings and makes no assumptions about
414 * previous contents. This can therefore be used both in copy constructors,
415 * when member variables have no defined value, and in assignments after having
416 * called cleanup().
417 *
418 * This variant converts from a UTF-16 string, most probably from
419 * a Bstr assignment.
420 *
421 * @param a_pbstr The source string. The caller guarantees that this
422 * is valid UTF-16.
423 * @param a_cwcMax The number of characters to be copied. If set to RTSTR_MAX,
424 * the entire string will be copied.
425 *
426 * @sa RTCString::copyFromN
427 */
428void Utf8Str::copyFrom(CBSTR a_pbstr, size_t a_cwcMax)
429{
430 if (a_pbstr && *a_pbstr)
431 {
432 int vrc = RTUtf16ToUtf8Ex((PCRTUTF16)a_pbstr,
433 a_cwcMax, // size_t cwcString: translate entire string
434 &m_psz, // char **ppsz: output buffer
435 0, // size_t cch: if 0, func allocates buffer in *ppsz
436 &m_cch); // size_t *pcch: receives the size of the output string, excluding the terminator.
437 if (RT_SUCCESS(vrc))
438 m_cbAllocated = m_cch + 1;
439 else
440 {
441 if ( vrc != VERR_NO_STR_MEMORY
442 && vrc != VERR_NO_MEMORY)
443 {
444 /* ASSUME: input is valid Utf-16. Fake out of memory error. */
445 AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTUtf16Len(a_pbstr) * sizeof(RTUTF16), a_pbstr));
446 }
447
448 m_cch = 0;
449 m_cbAllocated = 0;
450 m_psz = NULL;
451
452 throw std::bad_alloc();
453 }
454 }
455 else
456 {
457 m_cch = 0;
458 m_cbAllocated = 0;
459 m_psz = NULL;
460 }
461}
462
463/**
464 * A variant of Utf8Str::copyFrom that does not throw any exceptions but returns
465 * E_OUTOFMEMORY instead.
466 *
467 * @param a_pbstr The source string.
468 * @returns S_OK or E_OUTOFMEMORY.
469 */
470HRESULT Utf8Str::copyFromEx(CBSTR a_pbstr)
471{
472 if (a_pbstr && *a_pbstr)
473 {
474 int vrc = RTUtf16ToUtf8Ex((PCRTUTF16)a_pbstr,
475 RTSTR_MAX, // size_t cwcString: translate entire string
476 &m_psz, // char **ppsz: output buffer
477 0, // size_t cch: if 0, func allocates buffer in *ppsz
478 &m_cch); // size_t *pcch: receives the size of the output string, excluding the terminator.
479 if (RT_SUCCESS(vrc))
480 m_cbAllocated = m_cch + 1;
481 else
482 {
483 if ( vrc != VERR_NO_STR_MEMORY
484 && vrc != VERR_NO_MEMORY)
485 {
486 /* ASSUME: input is valid Utf-16. Fake out of memory error. */
487 AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTUtf16Len(a_pbstr) * sizeof(RTUTF16), a_pbstr));
488 }
489
490 m_cch = 0;
491 m_cbAllocated = 0;
492 m_psz = NULL;
493
494 return E_OUTOFMEMORY;
495 }
496 }
497 else
498 {
499 m_cch = 0;
500 m_cbAllocated = 0;
501 m_psz = NULL;
502 }
503 return S_OK;
504}
505
506
507/**
508 * A variant of Utf8Str::copyFromN that does not throw any exceptions but
509 * returns E_OUTOFMEMORY instead.
510 *
511 * @param a_pcszSrc The source string.
512 * @param a_offSrc Start offset to copy from.
513 * @param a_cchSrc How much to copy
514 * @returns S_OK or E_OUTOFMEMORY.
515 *
516 * @remarks This calls cleanup() first, so the caller doesn't have to. (Saves
517 * code space.)
518 */
519HRESULT Utf8Str::copyFromExNComRC(const char *a_pcszSrc, size_t a_offSrc, size_t a_cchSrc)
520{
521 Assert(!a_cchSrc || !m_psz || (uintptr_t)&a_pcszSrc[a_offSrc] - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
522 cleanup();
523 if (a_cchSrc)
524 {
525 m_psz = RTStrAlloc(a_cchSrc + 1);
526 if (RT_LIKELY(m_psz))
527 {
528 m_cch = a_cchSrc;
529 m_cbAllocated = a_cchSrc + 1;
530 memcpy(m_psz, a_pcszSrc + a_offSrc, a_cchSrc);
531 m_psz[a_cchSrc] = '\0';
532 }
533 else
534 {
535 m_cch = 0;
536 m_cbAllocated = 0;
537 return E_OUTOFMEMORY;
538 }
539 }
540 else
541 {
542 m_cch = 0;
543 m_cbAllocated = 0;
544 m_psz = NULL;
545 }
546 return S_OK;
547}
548
549} /* namespace com */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette