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

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

Used std::min(), std::max() explicitly to avoid compiler confusion. Used std::size() explicitly instead of the equivalent macro

  • 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 "extvalue.h"
23#include <assert.h>
24#include <common/nonstd_math.h>
25
26#ifdef MULTITHREADED
27#include <pthread.h>
28static pthread_mutex_t sstring_ref_lock = PTHREAD_MUTEX_INITIALIZER;
29#define REF_LOCK pthread_mutex_lock(&sstring_ref_lock);
30#define REF_UNLOCK pthread_mutex_unlock(&sstring_ref_lock)
31#else
32#define REF_LOCK
33#define REF_UNLOCK
34#endif
35
36static int guessMemSize(int request)
37{
38        return request + min(request / 2, 10000) + 8;
39}
40
41SBuf::SBuf()
42{
43        txt = (char*)"";
44        size = used = 0;
45        refcount = 1;
46}
47
48SBuf::SBuf(int initsize)
49{
50        size = guessMemSize(initsize);
51        if (size > 0) { txt = (char*)malloc(size + 1); txt[0] = 0; }
52        else    txt = (char*)"";
53        used = 0;
54        refcount = 1;
55}
56
57SBuf::~SBuf()
58{
59        freeBuf();
60}
61
62void SBuf::initEmpty()
63{
64        txt = (char*)"";
65        used = size = 0;
66        refcount = 1;
67}
68
69void SBuf::freeBuf()
70{
71        if (!size) return;
72        free(txt); used = 0;
73}
74
75void SBuf::copyFrom(const char *ch, int chlen)
76{
77        if (chlen == -1) chlen = strlen(ch);
78        if (chlen > 0)
79        {
80                if (chlen < size)
81                {
82                        memmove(txt, ch, chlen);
83                }
84                else
85                {
86                        size = guessMemSize(chlen);
87                        char *newtxt = (char*)malloc(size + 1);
88                        memcpy(newtxt, ch, chlen);
89                        free(txt);
90                        txt = newtxt;
91                }
92        }
93        txt[chlen] = 0;
94        used = chlen;
95}
96
97void SBuf::append(const char *ch, int chlen)
98{ // doesn't check anything!
99        memmove(txt + used, ch, chlen);
100        used += chlen;
101        txt[used] = 0;
102}
103
104void SBuf::ensureSize(int needed)
105{
106        if (size >= needed) return;
107        needed = guessMemSize(needed);
108        txt = (char*)realloc(txt, needed + 1);
109        size = needed;
110}
111
112/////////////////////////////////////////////
113
114SString::SString()
115{
116        initEmpty();
117}
118
119SString::~SString()
120{
121        REF_LOCK;
122        detach();
123        REF_UNLOCK;
124}
125
126SString::SString(const char *t, int t_len)
127{
128        initEmpty();
129        if (!t) return;
130        copyFrom(t, t_len);
131}
132
133SString::SString(SString&& from)
134{
135        buf = from.buf;
136        from.buf = &SBuf::empty();
137}
138
139SString::SString(const SString &from)
140{
141        if (from.buf == &SBuf::empty())
142                buf = &SBuf::empty();
143        else
144        {
145                REF_LOCK;
146                buf = from.buf;
147                if (buf->size)
148                        buf->refcount++;
149                REF_UNLOCK;
150        }
151}
152
153SString::SString(char in)
154{
155        initEmpty();
156        copyFrom(&in, 1);
157}
158
159void SString::initEmpty()
160{
161        buf = &SBuf::empty();
162}
163
164void SString::detachEmpty(int ensuresize)
165{
166        if (buf == &SBuf::empty()) { buf = new SBuf(ensuresize); return; }
167        if (buf->refcount < 2) buf->ensureSize(ensuresize);
168        else
169        {
170                buf->refcount--;
171                buf = new SBuf(ensuresize);
172        }
173}
174
175void SString::detach()
176{
177        if (buf == &SBuf::empty()) return;
178        if (!--buf->refcount) delete buf;
179}
180
181void SString::detachCopy(int ensuresize)
182{
183        if (buf == &SBuf::empty()) { buf = new SBuf(ensuresize); return; }
184        if (buf->refcount < 2)
185        {
186                buf->ensureSize(ensuresize);
187                return;
188        }
189        buf->refcount--;
190        SBuf *newbuf = new SBuf(ensuresize);
191        newbuf->copyFrom(buf->txt, min(ensuresize, buf->used));
192        buf = newbuf;
193}
194
195char *SString::directWrite(int ensuresize)
196{
197        if (ensuresize < 0) ensuresize = length();
198        REF_LOCK;
199        detachCopy(ensuresize);
200        REF_UNLOCK;
201        appending = buf->used;
202        return buf->txt;
203}
204
205/*
206char *SString::directWrite()
207{
208return directWrite(buf->used);
209}
210*/
211char *SString::directAppend(int maxappend)
212{
213        REF_LOCK;
214        detachCopy(buf->used + maxappend);
215        REF_UNLOCK;
216        appending = buf->used;
217        return buf->txt + appending;
218}
219
220void SString::endWrite(int newlength)
221{
222        if (newlength < 0) newlength = strlen(buf->txt);
223        else
224        {
225                if ((newlength >= (buf->size + 1)) || (buf->size == 0))
226                {
227                        logMessage("SString", "endWrite", LOG_CRITICAL, "newlength >= allocated or nothing allocated");
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                        p[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(p), format);
420                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
421                        va_start(ap, format);
422                        n = vsnprintf(p, size, format, ap); //hoping 16M is enough
423                        va_end(ap);
424                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", p);
425                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
426                        {
427                                strcpy(p, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
428                                strcat(p, 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'", p);
430                        }
431                        n = strlen(p); //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.