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

Last change on this file since 920 was 897, checked in by Maciej Komosinski, 5 years ago

A workaround for Android bug in vsnprintf() and vsprintf() needed in more classes (introduced earlier in r892), ​https://github.com/android-ndk/ndk/issues/879

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