source: cpp/frams/util/sstring.cpp @ 1101

Last change on this file since 1101 was 1040, checked in by Maciej Komosinski, 4 years ago

Follow-up to r897: A workaround for Android bug in vsnprintf() and vsprintf() in SString, https://github.com/android-ndk/ndk/issues/879

  • Property svn:eol-style set to native
File size: 11.1 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include "sstring.h"
6#include <common/nonstd.h> //to be sure the vsnprintf-related stuff gets included
7
8#ifdef SSTRING_SIMPLE
9
10// simple sstring implementation using direct character arrays
11// - duplicate = copy all characters
12// - no mutex needed
13
14#include "sstring-simple.cpp"
15
16#else
17///////////////////////////////////////////////////////////////////////////
18// old sstring implementation using SBuf references
19// - duplicate = copy buffer pointer
20// - mutex required to be thread safe
21
22#include <common/nonstd_stl.h>
23#include "extvalue.h"
24#include <assert.h>
25#include <common/nonstd_math.h>
26
27#ifdef MULTITHREADED
28#include <pthread.h>
29static pthread_mutex_t sstring_ref_lock = PTHREAD_MUTEX_INITIALIZER;
30#define REF_LOCK pthread_mutex_lock(&sstring_ref_lock);
31#define REF_UNLOCK pthread_mutex_unlock(&sstring_ref_lock)
32#else
33#define REF_LOCK
34#define REF_UNLOCK
35#endif
36
37static int guessMemSize(int request)
38{
39        return request + min(request / 2, 10000) + 8;
40}
41
42SBuf::SBuf()
43{
44        txt = (char*)"";
45        size = used = 0;
46        refcount = 1;
47}
48
49SBuf::SBuf(int initsize)
50{
51        size = guessMemSize(initsize);
52        if (size > 0) { txt = (char*)malloc(size + 1); txt[0] = 0; }
53        else    txt = (char*)"";
54        used = 0;
55        refcount = 1;
56}
57
58SBuf::~SBuf()
59{
60        freeBuf();
61}
62
63void SBuf::initEmpty()
64{
65        txt = (char*)"";
66        used = size = 0;
67        refcount = 1;
68}
69
70void SBuf::freeBuf()
71{
72        if (!size) return;
73        free(txt); used = 0;
74}
75
76void SBuf::copyFrom(const char *ch, int chlen)
77{
78        if (chlen == -1) chlen = strlen(ch);
79        if (chlen > 0)
80        {
81                if (chlen < size)
82                {
83                        memmove(txt, ch, chlen);
84                }
85                else
86                {
87                        size = guessMemSize(chlen);
88                        char *newtxt = (char*)malloc(size + 1);
89                        memcpy(newtxt, ch, chlen);
90                        free(txt);
91                        txt = newtxt;
92                }
93        }
94        txt[chlen] = 0;
95        used = chlen;
96}
97
98void SBuf::append(const char *ch, int chlen)
99{ // doesn't check anything!
100        memmove(txt + used, ch, chlen);
101        used += chlen;
102        txt[used] = 0;
103}
104
105void SBuf::ensureSize(int needed)
106{
107        if (size >= needed) return;
108        needed = guessMemSize(needed);
109        txt = (char*)realloc(txt, needed + 1);
110        size = needed;
111}
112
113/////////////////////////////////////////////
114
115SString::SString()
116{
117        initEmpty();
118}
119
120SString::~SString()
121{
122        REF_LOCK;
123        detach();
124        REF_UNLOCK;
125}
126
127SString::SString(const char *t, int t_len)
128{
129        initEmpty();
130        if (!t) return;
131        copyFrom(t, t_len);
132}
133
134SString::SString(SString&& from)
135{
136        buf = from.buf;
137        from.buf = &SBuf::empty();
138}
139
140SString::SString(const SString &from)
141{
142        if (from.buf == &SBuf::empty())
143                buf = &SBuf::empty();
144        else
145        {
146                REF_LOCK;
147                buf = from.buf;
148                if (buf->size)
149                        buf->refcount++;
150                REF_UNLOCK;
151        }
152}
153
154SString::SString(char in)
155{
156        initEmpty();
157        copyFrom(&in, 1);
158}
159
160void SString::initEmpty()
161{
162        buf = &SBuf::empty();
163}
164
165void SString::detachEmpty(int ensuresize)
166{
167        if (buf == &SBuf::empty()) { buf = new SBuf(ensuresize); return; }
168        if (buf->refcount < 2) buf->ensureSize(ensuresize);
169        else
170        {
171                buf->refcount--;
172                buf = new SBuf(ensuresize);
173        }
174}
175
176void SString::detach()
177{
178        if (buf == &SBuf::empty()) return;
179        if (!--buf->refcount) delete buf;
180}
181
182void SString::detachCopy(int ensuresize)
183{
184        if (buf == &SBuf::empty()) { buf = new SBuf(ensuresize); return; }
185        if (buf->refcount < 2)
186        {
187                buf->ensureSize(ensuresize);
188                return;
189        }
190        buf->refcount--;
191        SBuf *newbuf = new SBuf(ensuresize);
192        newbuf->copyFrom(buf->txt, min(ensuresize, buf->used));
193        buf = newbuf;
194}
195
196char *SString::directWrite(int ensuresize)
197{
198        if (ensuresize < 0) ensuresize = length();
199        REF_LOCK;
200        detachCopy(ensuresize);
201        REF_UNLOCK;
202        appending = buf->used;
203        return buf->txt;
204}
205
206/*
207char *SString::directWrite()
208{
209return directWrite(buf->used);
210}
211*/
212char *SString::directAppend(int maxappend)
213{
214        REF_LOCK;
215        detachCopy(buf->used + maxappend);
216        REF_UNLOCK;
217        appending = buf->used;
218        return buf->txt + appending;
219}
220
221void SString::endWrite(int newlength)
222{
223        if (newlength < 0) newlength = strlen(buf->txt);
224        else
225        {
226                if ((newlength >= (buf->size + 1)) || (buf->size == 0))
227                {
228                        logMessage("SString", "endWrite", LOG_CRITICAL, "newlength >= allocated or nothing allocated");
229                        assert((newlength < (buf->size + 1)) && (buf->size > 0));
230                        if (buf->size == 0) return;
231                        newlength = buf->size;
232                }
233                buf->txt[newlength] = 0;
234        }
235        buf->used = newlength;
236}
237
238void SString::endAppend(int newappend)
239{
240        if (newappend < 0) endWrite(appending + strlen(buf->txt + appending));
241        else endWrite(newappend + appending);
242}
243
244////////////// append /////////////////
245
246void SString::operator+=(const char *s)
247{
248        if (!s) return;
249        int x = strlen(s);
250        if (!x) return;
251        append(s, x);
252}
253
254void SString::append(const char *txt, int count)
255{
256        if (!count) return;
257        REF_LOCK;
258        detachCopy(buf->used + count);
259        REF_UNLOCK;
260        buf->append(txt, count);
261}
262
263void SString::operator+=(const SString&s)
264{
265        append(s.c_str(), s.length());
266}
267
268SString SString::operator+(const SString& s) const
269{
270        SString ret(*this);
271        ret += s;
272        return ret;
273}
274
275/////////////////////////////
276
277void SString::copyFrom(const char *ch, int chlen)
278{
279        if (!ch) chlen = 0;
280        else if (chlen < 0) chlen = strlen(ch);
281        REF_LOCK;
282        detachEmpty(chlen);
283        REF_UNLOCK;
284        memmove(buf->txt, ch, chlen);
285        buf->txt[chlen] = 0; buf->used = chlen;
286}
287
288void SString::operator=(const char *ch)
289{
290        copyFrom(ch);
291}
292
293void SString::operator=(const SString&s)
294{
295        if (s.buf == buf) return;
296        REF_LOCK;
297        detach();
298        buf = s.buf;
299        if (buf->size) buf->refcount++;
300        REF_UNLOCK;
301}
302///////////////////////////////////////
303
304SString SString::substr(int begin, int numchars) const
305{
306        if (begin < 0) { numchars += begin; begin = 0; }
307        if (numchars >= (length() - begin)) numchars = length() - begin;
308        if (numchars <= 0) return SString();
309        if (numchars == length()) return *this;
310        return SString((*this)(begin), numchars);
311}
312
313///////////////////////////////////////
314
315bool SString::equals(const SString& s) const
316{
317        if (s.buf == buf) return true;
318        return strcmp(buf->txt, s.buf->txt) == 0;
319}
320
321///////////////////////////////////////
322
323int SString::indexOf(int character, int start) const
324{
325        const char *found = strchr(buf->txt + start, character);
326        return found ? found - buf->txt : -1;
327}
328
329int SString::indexOf(const char *substring, int start) const
330{
331        char *found = strstr(buf->txt + start, substring);
332        return found ? found - buf->txt : -1;
333}
334
335int SString::indexOf(const SString & substring, int start) const
336{
337        char *found = strstr(buf->txt + start, substring.c_str());
338        return found ? found - buf->txt : -1;
339}
340
341bool SString::getNextToken(int& pos, SString &token, char separator) const
342{
343        if (pos >= length()) { token = 0; return false; }
344        int p1 = pos, p2;
345        const char *t1 = buf->txt + pos;
346        const char *t2 = strchr(t1, separator);
347        if (t2) pos = (p2 = (t2 - buf->txt)) + 1; else p2 = pos = length();
348        strncpy(token.directWrite(p2 - p1), t1, p2 - p1);
349        token.endWrite(p2 - p1);
350        return true;
351}
352
353bool SString::startsWith(const char *pattern) const
354{
355        const char *t = this->c_str();
356        for (; *pattern; pattern++, t++)
357                if (*t != *pattern) return false;
358        return true;
359}
360
361SString SString::valueOf(int i)
362{
363        return SString::sprintf("%d", i);
364}
365SString SString::valueOf(long i)
366{
367        return SString::sprintf("%d", i);
368}
369SString SString::valueOf(double d)
370{
371        SString tmp;
372        char* here = tmp.directWrite(30);
373        tmp.endWrite(doubleToString(d, -1, here, 30));
374        if ((!strchr(tmp.c_str(), '.')) && (!strchr(tmp.c_str(), 'e'))) tmp += ".0";
375        return tmp;
376}
377SString SString::valueOf(const SString& s)
378{
379        return s;
380}
381
382#if 0 //testing _vscprintf
383#define USE_VSCPRINTF
384int _vscprintf(const char *format, va_list argptr)
385{
386        return vsnprintf("", 0, format, argptr);
387}
388#endif
389
390SString SString::sprintf(const char* format, ...)
391{
392        int n, size = 30;
393        va_list ap;
394
395        SString ret;
396
397#ifdef USE_VSCPRINTF
398        va_start(ap, format);
399        size = _vscprintf(format, ap);
400        va_end(ap);
401#endif
402
403        while (1)
404        {
405                char* p = ret.directWrite(size);
406                assert(p != NULL);
407                size = ret.capacity() + 1;
408                /* Try to print in the allocated space. */
409                va_start(ap, format);
410                n = vsnprintf(p, size, format, ap);
411                va_end(ap);
412                /* If that worked, return the string. */
413
414#ifdef __ANDROID__
415                //Workaround for Android bug. /system/lib64/libc.so? maybe only arm 64-bit? "If an encoding error occurs, a negative number is returned". On some devices keeps returning -1 forever.
416                //https://github.com/android-ndk/ndk/issues/879 but unfortunately during google play tests (Firebase Test Lab) this problem turned out to be not limited to Chinese devices and occurred in Mate 9, Galaxy S9, Pixel, Pixel 2, Moto Z (even with the en_GB locale; the locale is not important but the problem seem to be utf8 non-ascii chars in the format string).
417                if (n < 0 && size >= (1 << 24)) //wants more than 16M
418                {
419                        p[size - 1] = 0; //just to ensure there is at least some ending \0 in memory... who knows what buggy vsnprintf() did.
420                        __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "Giving up due to Android bug: vsnprintf() wants more than %d bytes, it used %zu bytes, for format='%s'", size, strlen(p), format);
421                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
422                        va_start(ap, format);
423                        n = vsnprintf(p, size, format, ap); //hoping 16M is enough
424                        va_end(ap);
425                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", p);
426                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
427                        {
428                                strcpy(p, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
429                                strcat(p, format); //append and return the original formatting string
430                                __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "vsprintf() also failed, using the incorrect resulting string: '%s'", p);
431                        }
432                        n = strlen(p); //pretend vsnprintf() or vsprintf() was OK to exit the endless loop
433                }
434#endif
435
436                if (n > -1 && n < size)
437                {
438                        ret.endWrite(n);
439                        return ret;
440                }
441                /* Else try again with more space. */
442#ifdef VSNPRINTF_RETURNS_REQUIRED_SIZE
443                if (n > -1)    /* glibc 2.1 */
444                        size = n; /* precisely what is needed */
445                else           /* glibc 2.0 */
446#endif
447                        size *= 2;  /* twice the old size */
448        }
449}
450
451SString &SString::empty()
452{
453        static SString empty;
454        return empty;
455}
456
457SBuf &SBuf::empty()
458{
459        static SBuf empty;
460        return empty;
461}
462
463#endif //#ifdef SSTRING_SIMPLE
464
465//////////////////////////////////////////////////
466// to be moved somewhere else?
467// public domain source: http://isthe.com/chongo/src/fnv
468typedef uint32_t Fnv32_t;
469
470#define FNV_32_PRIME ((Fnv32_t)0x01000193)
471#define FNV1_32_INIT ((Fnv32_t)0x811c9dc5)
472#define FNV1_32A_INIT FNV1_32_INIT
473
474Fnv32_t fnv_32a_buf(void *buf, size_t len, Fnv32_t hval)
475{
476        unsigned char *bp = (unsigned char *)buf;       /* start of buffer */
477        unsigned char *be = bp + len;           /* beyond end of buffer */
478
479        while (bp < be) {
480
481                /* xor the bottom with the current octet */
482                hval ^= (Fnv32_t)*bp++;
483
484                /* multiply by the 32 bit FNV magic prime mod 2^32 */
485#if defined(NO_FNV_GCC_OPTIMIZATION)
486                hval *= FNV_32_PRIME;
487#else
488                hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
489#endif
490
491        }
492
493        /* return our new hash value */
494        return hval;
495}
496//////////////////////////////////////////////////
497
498#ifdef SSTRING_SIMPLE
499uint32_t SString::hash() const
500{
501        return fnv_32a_buf(txt, used, FNV1_32A_INIT);
502}
503#else
504uint32_t SBuf::hash() const
505{
506        return fnv_32a_buf(txt, used, FNV1_32A_INIT);
507}
508#endif
Note: See TracBrowser for help on using the repository browser.