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

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

SString::endWrite()/endAppend() now safe

  • Property svn:eol-style set to native
File size: 11.0 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                        assert((newlength < (buf->size + 1)) && (buf->size > 0));
229                        if (buf->size == 0) return;
230                        newlength = buf->size;
231                }
232                buf->txt[newlength] = 0;
233        }
234        buf->used = newlength;
235}
236
237void SString::endAppend(int newappend)
238{
239        if (newappend < 0) endWrite(appending + strlen(buf->txt + appending));
240        else endWrite(newappend + appending);
241}
242
243////////////// append /////////////////
244
245void SString::operator+=(const char *s)
246{
247        if (!s) return;
248        int x = strlen(s);
249        if (!x) return;
250        append(s, x);
251}
252
253void SString::append(const char *txt, int count)
254{
255        if (!count) return;
256        REF_LOCK;
257        detachCopy(buf->used + count);
258        REF_UNLOCK;
259        buf->append(txt, count);
260}
261
262void SString::operator+=(const SString&s)
263{
264        append(s.c_str(), s.length());
265}
266
267SString SString::operator+(const SString& s) const
268{
269        SString ret(*this);
270        ret += s;
271        return ret;
272}
273
274/////////////////////////////
275
276void SString::copyFrom(const char *ch, int chlen)
277{
278        if (!ch) chlen = 0;
279        else if (chlen < 0) chlen = strlen(ch);
280        REF_LOCK;
281        detachEmpty(chlen);
282        REF_UNLOCK;
283        memmove(buf->txt, ch, chlen);
284        buf->txt[chlen] = 0; buf->used = chlen;
285}
286
287void SString::operator=(const char *ch)
288{
289        copyFrom(ch);
290}
291
292void SString::operator=(const SString&s)
293{
294        if (s.buf == buf) return;
295        REF_LOCK;
296        detach();
297        buf = s.buf;
298        if (buf->size) buf->refcount++;
299        REF_UNLOCK;
300}
301///////////////////////////////////////
302
303SString SString::substr(int begin, int numchars) const
304{
305        if (begin < 0) { numchars += begin; begin = 0; }
306        if (numchars >= (length() - begin)) numchars = length() - begin;
307        if (numchars <= 0) return SString();
308        if (numchars == length()) return *this;
309        return SString((*this)(begin), numchars);
310}
311
312///////////////////////////////////////
313
314bool SString::equals(const SString& s) const
315{
316        if (s.buf == buf) return true;
317        return strcmp(buf->txt, s.buf->txt) == 0;
318}
319
320///////////////////////////////////////
321
322int SString::indexOf(int character, int start) const
323{
324        const char *found = strchr(buf->txt + start, character);
325        return found ? found - buf->txt : -1;
326}
327
328int SString::indexOf(const char *substring, int start) const
329{
330        char *found = strstr(buf->txt + start, substring);
331        return found ? found - buf->txt : -1;
332}
333
334int SString::indexOf(const SString & substring, int start) const
335{
336        char *found = strstr(buf->txt + start, substring.c_str());
337        return found ? found - buf->txt : -1;
338}
339
340bool SString::getNextToken(int& pos, SString &token, char separator) const
341{
342        if (pos >= length()) { token = 0; return false; }
343        int p1 = pos, p2;
344        const char *t1 = buf->txt + pos;
345        const char *t2 = strchr(t1, separator);
346        if (t2) pos = (p2 = (t2 - buf->txt)) + 1; else p2 = pos = length();
347        strncpy(token.directWrite(p2 - p1), t1, p2 - p1);
348        token.endWrite(p2 - p1);
349        return true;
350}
351
352bool SString::startsWith(const char *pattern) const
353{
354        const char *t = this->c_str();
355        for (; *pattern; pattern++, t++)
356                if (*t != *pattern) return false;
357        return true;
358}
359
360SString SString::valueOf(int i)
361{
362        return SString::sprintf("%d", i);
363}
364SString SString::valueOf(long i)
365{
366        return SString::sprintf("%d", i);
367}
368SString SString::valueOf(double d)
369{
370        SString tmp;
371        char* here = tmp.directWrite(30);
372        tmp.endWrite(doubleToString(d, -1, here, 30));
373        if ((!strchr(tmp.c_str(), '.')) && (!strchr(tmp.c_str(), 'e'))) tmp += ".0";
374        return tmp;
375}
376SString SString::valueOf(const SString& s)
377{
378        return s;
379}
380
381#if 0 //testing _vscprintf
382#define USE_VSCPRINTF
383int _vscprintf(const char *format, va_list argptr)
384{
385        return vsnprintf("", 0, format, argptr);
386}
387#endif
388
389SString SString::sprintf(const char* format, ...)
390{
391        int n, size = 30;
392        va_list ap;
393
394        SString ret;
395
396#ifdef USE_VSCPRINTF
397        va_start(ap, format);
398        size = _vscprintf(format, ap);
399        va_end(ap);
400#endif
401
402        while (1)
403        {
404                char* p = ret.directWrite(size);
405                assert(p != NULL);
406                size = ret.capacity() + 1;
407                /* Try to print in the allocated space. */
408                va_start(ap, format);
409                n = vsnprintf(p, size, format, ap);
410                va_end(ap);
411                /* If that worked, return the string. */
412
413#ifdef __ANDROID__
414                //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.
415                //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).
416                if (n < 0 && size >= (1 << 24)) //wants more than 16M
417                {
418                        buf[size - 1] = 0; //just to ensure there is at least some ending \0 in memory... who knows what buggy vsnprintf() did.
419                        __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);
420                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
421                        va_copy(ap_copy, ap);
422                        n = vsprintf(buf, format, ap_copy); //hoping 16M is enough
423                        va_end(ap_copy);
424                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", buf);
425                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
426                        {
427                                strcpy(buf, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
428                                strcat(buf, format); //append and return the original formatting string
429                                __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "vsprintf() also failed, using the incorrect resulting string: '%s'", buf);
430                        }
431                        n = strlen(buf); //pretend vsnprintf() or vsprintf() was OK to exit the endless loop
432                }
433#endif
434
435                if (n > -1 && n < size)
436                {
437                        ret.endWrite(n);
438                        return ret;
439                }
440                /* Else try again with more space. */
441#ifdef VSNPRINTF_RETURNS_REQUIRED_SIZE
442                if (n > -1)    /* glibc 2.1 */
443                        size = n; /* precisely what is needed */
444                else           /* glibc 2.0 */
445#endif
446                        size *= 2;  /* twice the old size */
447        }
448}
449
450SString &SString::empty()
451{
452        static SString empty;
453        return empty;
454}
455
456SBuf &SBuf::empty()
457{
458        static SBuf empty;
459        return empty;
460}
461
462#endif //#ifdef SSTRING_SIMPLE
463
464//////////////////////////////////////////////////
465// to be moved somewhere else?
466// public domain source: http://isthe.com/chongo/src/fnv
467typedef uint32_t Fnv32_t;
468
469#define FNV_32_PRIME ((Fnv32_t)0x01000193)
470#define FNV1_32_INIT ((Fnv32_t)0x811c9dc5)
471#define FNV1_32A_INIT FNV1_32_INIT
472
473Fnv32_t fnv_32a_buf(void *buf, size_t len, Fnv32_t hval)
474{
475        unsigned char *bp = (unsigned char *)buf;       /* start of buffer */
476        unsigned char *be = bp + len;           /* beyond end of buffer */
477
478        while (bp < be) {
479
480                /* xor the bottom with the current octet */
481                hval ^= (Fnv32_t)*bp++;
482
483                /* multiply by the 32 bit FNV magic prime mod 2^32 */
484#if defined(NO_FNV_GCC_OPTIMIZATION)
485                hval *= FNV_32_PRIME;
486#else
487                hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
488#endif
489
490        }
491
492        /* return our new hash value */
493        return hval;
494}
495//////////////////////////////////////////////////
496
497#ifdef SSTRING_SIMPLE
498uint32_t SString::hash() const
499{
500        return fnv_32a_buf(txt, used, FNV1_32A_INIT);
501}
502#else
503uint32_t SBuf::hash() const
504{
505        return fnv_32a_buf(txt, used, FNV1_32A_INIT);
506}
507#endif
Note: See TracBrowser for help on using the repository browser.