source: cpp/common/util-string.cpp @ 913

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

Added a few utility/helper functions

  • Property svn:eol-style set to native
File size: 4.7 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2019  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include "util-string.h"
6#include <stdarg.h>
7#include "nonstd_stdio.h"
8#include "nonstd.h"
9#include <assert.h>
10#include <cstdlib> //malloc()
11#ifdef USE_VIRTFILE
12#include <common/virtfile/virtfile.h>
13#endif
14#ifdef __ANDROID__
15#include <android/log.h> //only needed to print error messages related to a workaround for Android bug
16#endif
17
18string ssprintf_va(const char* format, va_list ap)
19{
20        string s; //clang crashed when this declaration was in s=buf
21        int size = 256;
22        char* buf;
23        va_list ap_copy; // "va_list ap" can only by used once by printf-type functions as they advance the current argument pointer (crashed on linux x86_64)
24        // (does not apply to SString::sprintf, it does not have the va_list variant)
25
26        //almost like SString::sprintf, but there is no common code to share because SString can use its directWrite to avoid double allocating/copying
27#ifdef USE_VSCPRINTF
28        va_copy(ap_copy, ap);
29        size = _vscprintf(format, ap_copy) + 1; //+1 for terminating null character
30        va_end(ap_copy);
31#endif
32
33        while (1)
34        {
35                buf = (char*)malloc(size);
36                assert(buf != NULL);
37                va_copy(ap_copy, ap);
38                int n = vsnprintf(buf, size, format, ap_copy);
39                va_end(ap_copy);
40
41#ifdef __ANDROID__
42                //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.
43                //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).
44                if (n < 0 && size >= (1 << 24)) //wants more than 16M
45                {
46                        buf[size - 1] = 0; //just to ensure there is at least some ending \0 in memory... who knows what buggy vsnprintf() did.
47                        __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);
48                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
49                        va_copy(ap_copy, ap);
50                        n = vsprintf(buf, format, ap_copy); //hoping 16M is enough
51                        va_end(ap_copy);
52                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", buf);
53                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
54                        {
55                                strcpy(buf, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
56                                strcat(buf, format); //append and return the original formatting string
57                                __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "vsprintf() also failed, using the incorrect resulting string: '%s'", buf);
58                        }
59                        n = strlen(buf); //pretend vsnprintf() or vsprintf() was OK to exit the endless loop
60                }
61#endif
62
63                if (n > -1 && n < size)
64                {
65                        s = buf;
66                        free(buf);
67                        return s;
68                }
69#ifdef VSNPRINTF_RETURNS_REQUIRED_SIZE
70                if (n > -1)    /* glibc 2.1 */
71                        size = n + 1; /* precisely what is needed */
72                else           /* glibc 2.0 */
73#endif
74                        size *= 2;  /* twice the old size */
75                free(buf);
76        }
77}
78
79bool str_starts_with(const char *str, const char *prefix)
80{
81        return strncmp(str,prefix,strlen(prefix))==0;
82}
83
84char* strmove(char *a, char *b) //strcpy that works well for overlapping strings ("Source and destination overlap")
85{
86        if (a == NULL || b == NULL)
87                return NULL;
88        memmove(a, b, strlen(b) + 1);
89        return a;
90}
91
92string ssprintf(const char* format, ...)
93{
94        va_list ap;
95        va_start(ap, format);
96        string ret = ssprintf_va(format, ap); //is it too wasteful? copying the string again... unless the compiler can handle it better
97        va_end(ap);
98        return ret;
99}
100
101string stripExt(const string& filename)
102{
103        size_t dot = filename.rfind('.');
104        if (dot == string::npos) return filename;
105        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
106        if ((sep == string::npos) || (sep < dot))
107                return filename.substr(0, dot);
108        return filename;
109}
110
111string stripFileDir(const string& filename)
112{
113        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
114        if (sep == string::npos) return filename;
115        return filename.substr(sep + 1);
116}
117
118string getFileExt(const string& filename)
119{
120        size_t dot = filename.rfind('.');
121        if (dot == string::npos) return string("");
122        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
123        if ((sep == string::npos) || (sep < dot))
124                return filename.substr(dot);
125        return string("");
126}
127
128string getFileDir(const string& filename)
129{
130        size_t slash = filename.rfind(PATH_SEPARATOR_CHAR);
131        if (slash == string::npos) return string("");
132        return filename.substr(0, slash);
133}
Note: See TracBrowser for help on using the repository browser.