diff --git a/.gitignore b/.gitignore index f771526..2179c1b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,17 @@ /build /docker/build /docker/conan + +# sampctl files +/samp-npc +/samp03svr +/pawn.lock +/announce +/server.cfg +/dependencies/ +/server_log.txt +/sampctl_build_file.inc +*.amx + +# Zed editor settings +.zed/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 51d0f69..06d98fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,8 +66,8 @@ elseif(UNIX) specifiers.cpp sscanf.cpp utils.cpp + error.cpp ) endif() set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sscanf) - diff --git a/README.md b/README.md index 38dc22c..8cb17c2 100644 --- a/README.md +++ b/README.md @@ -2573,7 +2573,7 @@ This code tries to be slightly clever, but fails. The correct way to check for There is a third version of this error which looks like: ``` -error 004: function "sscanf" is not implemented sscanf The main entry point. See the readme for vast amounts of information on how to call this function and all the details on what it does. This is a macro that calls SSCANF__ and passes the current file and line number as well for improved error messages. +error 004: function "sscanf" is not implemented sscanf The main entry point. See the readme for vast amounts of information on how to call this function and all the details on what it does. This is a macro that calls SSCANF__ and passes the current file and line number as well for improved error messages. ``` For more information on why, see [this compiler issue](https://github.com/pawn-lang/compiler/issues/705). @@ -3224,4 +3224,3 @@ the Initial Developer. All Rights Reserved. * Added alternates via `|`. * `SSCANF_GetErrorSpecifier` to get the error position in failure cases. * `SSCANF_Debug` to dump a load of useful debugging information. - diff --git a/data.cpp b/data.cpp index 31b0c2d..dde17df 100644 --- a/data.cpp +++ b/data.cpp @@ -40,12 +40,15 @@ * Emmet_, for his efforts in maintaining it for almost a year. */ +#include #include #include "sscanf.h" #include "args.h" #include "utils.h" #include "data.h" +#define UINT32_MAX_DIV_10_CEIL 429496730 + extern unsigned int g_iTrueMax; @@ -64,25 +67,25 @@ float gNameSimilarity = -1.0f; // Options: -// +// // 1 = OLD_DEFAULT_NAME -// +// // Parse values in "U(5)" as if they were a name that must be connected, // instead of just any number. -// +// // 2 = MATCH_NAME_PARTIAL -// +// // When searching for players, match any part of their name not just the // start. -// +// // 4 = CELLMIN_ON_MATCHES -// +// // If multiple player name matches are found, return 0x80000000. -// +// // 8 = SSCANF_QUIET -// +// // Disable all prints. -// +// // 16 = OLD_DEFAULT_KUSTOM // // `K(def)` needs a valid input as the default value. @@ -97,7 +100,7 @@ float // E_SSCANF_OPTIONS gOptions = SSCANF_OPTIONS_NONE; - + cell * args_s::Next() { if (HasMore()) @@ -640,40 +643,85 @@ unsigned int return id; } -int - GetDecValue(char ** const input) +/* + * Attempt to convert value to signed integer (inverting it, if neg == true), + * store result to *output. + * + * Return false if such operation will lead to overflow, true otherwise. + */ +bool + ConvertUnsignedValue(bool neg, unsigned int value, int * output) +{ + if (neg) + { + if (value > ((unsigned int)INT32_MAX) + 1) + { + *output = 0; + return false; + } + *output = (int)(-value); + return true; + } + else + { + if (value > ((unsigned int)INT32_MAX)) + { + *output = 0; + return false; + } + *output = (int)value; + return true; + } +} + +bool + GetDecValue(char ** const input, unsigned int * output) { char * str = *input; - int + unsigned int val = 0; unsigned char cur; // Convert to a number and test it. while ((cur = (unsigned char)(*str - '0')) < 10) { + // Pre-check for overflow: (old_val * 10) <= UINT32_MAX + if (val > UINT32_MAX_DIV_10_CEIL) + { + *output = 0; + return false; + } + val *= 10; + // Pre-check for overflow: (old_val * 10 + cur) <= UINT32_MAX + if ((UINT32_MAX - cur) < val) + { + *output = 0; + return false; + } // Update the current value. - val = (val * 10) + cur; + val += cur; // Update the current pointer. ++str; } // Save the pointer. *input = str; // Covert the sign and return without an if. - return val; + *output = val; + return true; } -int - GetDec(char ** const input) +bool + GetDec(char ** const input, int * output) { char * str = *input; - int - neg = 1; + bool + neg = false; switch (*str) { case '-': - neg = -1; + neg = true; // FALLTHROUGH case '+': // Check there is valid data after @@ -684,21 +732,32 @@ int } } *input = str; - return GetDecValue(input) * neg; + unsigned int raw_value; + if (!GetDecValue(input, &raw_value)) + { + *output = 0; + return false; + } + return ConvertUnsignedValue(neg, raw_value, output); } -int - GetOctValue(char ** const input) +bool + GetOctValue(char ** const input, unsigned int * output) { char * str = *input; - int + unsigned int val = 0; unsigned char cur; // Convert to a number and test it. while ((cur = (unsigned char)(*str - '0')) < 8) { + if (val >= 0x20000000) + { + *output = 0; + return false; + } // Update the current value. val = (val * 8) + cur; // Update the current pointer. @@ -707,20 +766,21 @@ int // Save the pointer. *input = str; // Covert the sign and return without an if. - return val; + *output = val; + return true; } -int - GetOct(char ** const input) +bool + GetOct(char ** const input, int * output) { char * str = *input; - int - neg = 1; + bool + neg = false; switch (*str) { case '-': - neg = -1; + neg = true; // FALLTHROUGH case '+': // Check there is valid data after @@ -731,79 +791,49 @@ int } } *input = str; - return GetOctValue(input) * neg; + unsigned int raw_value; + if (!GetOctValue(input, &raw_value)) + { + *output = 0; + return false; + } + return ConvertUnsignedValue(neg, raw_value, output); } -int - GetHexValue(char ** const input) +bool + GetHexValue(char ** const input, unsigned int * output) { char * str = *input; - int + unsigned int val = 0; // Rewrote it using a big switch. for ( ; ; ) { - switch (*str) + unsigned char digit; + if ((*str >= '0') && (*str <= '9')) + { + digit = *str - '0'; + } + else if ((*str >= 'a') && (*str <= 'f')) + { + digit = *str - 'a' + 10; + } + else if ((*str >= 'A') && (*str <= 'F')) + { + digit = *str - 'A' + 10; + } + else { - case '0': - val = (val * 16) + 0x00; - break; - case '1': - val = (val * 16) + 0x01; - break; - case '2': - val = (val * 16) + 0x02; - break; - case '3': - val = (val * 16) + 0x03; - break; - case '4': - val = (val * 16) + 0x04; - break; - case '5': - val = (val * 16) + 0x05; - break; - case '6': - val = (val * 16) + 0x06; - break; - case '7': - val = (val * 16) + 0x07; - break; - case '8': - val = (val * 16) + 0x08; - break; - case '9': - val = (val * 16) + 0x09; - break; - case 'a': - case 'A': - val = (val * 16) + 0x0A; - break; - case 'b': - case 'B': - val = (val * 16) + 0x0B; - break; - case 'c': - case 'C': - val = (val * 16) + 0x0C; - break; - case 'd': - case 'D': - val = (val * 16) + 0x0D; - break; - case 'e': - case 'E': - val = (val * 16) + 0x0E; - break; - case 'f': - case 'F': - val = (val * 16) + 0x0F; break; - default: - // UGLY UGLY UGLY! - goto sscanf_hex_switch; } + + if (val >= 0x10000000) + { + *output = 0; + return false; + } + val = val * 16 + digit; ++str; } // UGLY UGLY UGLY - Needed for the double level break, which isn't native @@ -812,20 +842,20 @@ int // Save the pointer. *input = str; // Covert the sign and return without an if. - return val; + *output = val; + return true; } -int - GetHex(char ** const input) +bool + GetHex(char ** const input, int * output) { char * str = *input; - int - neg = 1; + bool neg = false; switch (*str) { case '-': - neg = -1; + neg = true; // FALLTHROUGH case '+': // Check there is valid data after @@ -854,7 +884,13 @@ int return 0; } *input = str; - return GetHexValue(input) * neg; + unsigned int raw_value; + if (!GetHexValue(input, &raw_value)) + { + *output = 0; + return false; + } + return ConvertUnsignedValue(neg, raw_value, output); // Convert to a number and test it. Horribly manually optimised - one of // these days I'll try write an ASM hex reader and see how well that works. // Actually I think I have written one before, but I don't know where it is @@ -929,7 +965,11 @@ unsigned int } // Get the underlying HEX value. *input = str; - unsigned int ret = (unsigned int)GetHexValue(input); + unsigned int ret; + if (!GetHexValue(input, &ret)) + { + return 0; + } // Determine the input type. switch (prefix) { @@ -1045,17 +1085,16 @@ int return (int)GetBoolValue(input); } -int - GetNumber(char ** const input) +bool + GetNumber(char ** const input, int * output) { char * str = *input; - int - neg = 1; + bool neg = false; switch (*str) { case '-': - neg = -1; + neg = true; // FALLTHROUGH case '+': // Check there is valid data after @@ -1077,7 +1116,13 @@ int if (((*str >= '0') && (*str <= '9')) || (((*str | 0x20) >= 'a') && ((*str | 0x20) <= 'f'))) { *input = str; - return GetHexValue(input) * neg; + unsigned int raw_value; + if (!GetHexValue(input, &raw_value)) + { + *output = 0; + return false; + } + return ConvertUnsignedValue(neg, raw_value, output); } else { @@ -1087,7 +1132,7 @@ int case 'b': case 'B': // Bool. - if (neg == -1) + if (neg) { // Can't have negative booleans. *input = str; @@ -1117,7 +1162,13 @@ int case '7': // Octal. *input = str; - return GetOctValue(input) * neg; + unsigned int raw_value; + if (!GetOctValue(input, &raw_value)) + { + *output = 0; + return false; + } + return ConvertUnsignedValue(neg, raw_value, output); case '8': case '9': // Decimal. @@ -1133,7 +1184,13 @@ int return 0; } *input = str; - return GetDecValue(input) * neg; + unsigned int raw_value; + if (!GetDecValue(input, &raw_value)) + { + *output = 0; + return false; + } + return ConvertUnsignedValue(neg, raw_value, output); } bool @@ -1329,7 +1386,11 @@ int } else { - length = GetDec(input); + if (!GetDec(input, &length)) + { + length = 1; + SscanfError(17, "Invalid data length."); + } str = *input; if (length <= 0) { @@ -1483,4 +1544,3 @@ float // Normalise the number of matching pairs and multiply. return (1.0f - ((float)unique1 / (float)ngrams1)) * (1.0f - ((float)unique2 / (float)ngrams2)); } - diff --git a/data.h b/data.h index ae26927..1cb7993 100644 --- a/data.h +++ b/data.h @@ -71,14 +71,14 @@ char * unsigned int GetUserString(char * string, char ** end); -int - GetDec(char ** const input); +bool + GetDec(char ** const input, int * output); -int - GetOct(char ** const input); +bool + GetOct(char ** const input, int * output); -int - GetHex(char ** const input); +bool + GetHex(char ** const input, int * output); unsigned int GetColour(char ** const input, int * type, unsigned int alpha); @@ -86,8 +86,8 @@ unsigned int int GetBool(char ** const input); -int - GetNumber(char ** const input); +bool + GetNumber(char ** const input, int * output); bool GetLogical(char ** const input); @@ -103,4 +103,3 @@ int bool FindDefaultStart(char ** const str); - diff --git a/enum.cpp b/enum.cpp index 5c48749..ddfc911 100644 --- a/enum.cpp +++ b/enum.cpp @@ -608,14 +608,14 @@ int } // Loop cleanup - only skip one spacer so that we can detect // multiple explicit delimiters in a row, for example: - // + // // hi there - // + // // is NOT multiple explicit delimiters in a row (they're // whitespace). This however is: - // + // // hi , , , there - // + // SkipOneSpacer(&string); } } @@ -764,4 +764,3 @@ bool } return true; } - diff --git a/error.cpp b/error.cpp index 40c28f8..cceb39c 100644 --- a/error.cpp +++ b/error.cpp @@ -193,4 +193,3 @@ int return -1; } } - diff --git a/error.h b/error.h index e66beae..84c27aa 100644 --- a/error.h +++ b/error.h @@ -56,4 +56,3 @@ extern void extern int GetErrorCategory(int error); - diff --git a/kustom.cpp b/kustom.cpp index 3b60c19..c082ba0 100644 --- a/kustom.cpp +++ b/kustom.cpp @@ -147,7 +147,13 @@ bool } else { - *cptr = (cell)GetNumber(&opts); + int value; + if (!GetNumber(&opts, &value)) + { + SetErrorCode(1012); + return false; + } + *cptr = (cell)value; return true; } } @@ -254,4 +260,3 @@ bool } return true; } - diff --git a/pawn.json b/pawn.json index f901e83..050522f 100644 --- a/pawn.json +++ b/pawn.json @@ -1,7 +1,7 @@ { - "user": "Y-Less", - "repo": "sscanf", - "contributors": [ + "user": "Y-Less", + "repo": "sscanf", + "contributors": [ "Cheaterman", "DEntisT", "Emmet_", @@ -14,23 +14,27 @@ "Y_Less", "ziggi" ], - "resources": [ - { - "name": "^sscanf-(.+)-linux.tar.gz$", - "platform": "linux", - "archive": true, - "includes": ["pawno/include"], - "plugins": ["plugins/sscanf.so"] - }, - { - "name": "^sscanf-(.*)-win32.zip$", - "platform": "windows", - "archive": true, - "includes": ["pawno/include"], - "plugins": ["plugins/sscanf.dll"] - } - ], - "runtime": { - "plugins": ["Y-Less/sscanf"] - } + "entry": "tests/sscanfErr.pwn", + "output": "gamemodes/sscanfErr.amx", + "dev_dependencies": ["pawn-lang/samp-stdlib@master"], + "runtime": { + "plugins": ["Y-Less/sscanf"], + "mode": "main" + }, + "resources": [ + { + "name": "^sscanf-(.+)-linux.tar.gz$", + "platform": "linux", + "archive": true, + "includes": ["pawno/include"], + "plugins": ["plugins/sscanf.so"] + }, + { + "name": "^sscanf-(.*)-win32.zip$", + "platform": "windows", + "archive": true, + "includes": ["pawno/include"], + "plugins": ["plugins/sscanf.dll"] + } + ] } diff --git a/specifiers.cpp b/specifiers.cpp index a9d28a0..407753c 100644 --- a/specifiers.cpp +++ b/specifiers.cpp @@ -76,7 +76,11 @@ float bool DoI(char ** input, int * ret) { - *ret = GetDec(input); + if (!GetDec(input, ret)) + { + SetErrorCode(1011); + return false; + } if (GetReturn(input)) { return true; @@ -88,7 +92,11 @@ bool bool DoN(char ** input, int * ret) { - *ret = GetNumber(input); + if (!GetNumber(input, ret)) + { + SetErrorCode(1012); + return false; + } if (GetReturn(input)) { return true; @@ -100,7 +108,11 @@ bool bool DoH(char ** input, int * ret) { - *ret = GetHex(input); + if (!GetHex(input, ret)) + { + SetErrorCode(1013); + return false; + } if (GetReturn(input)) { return true; @@ -132,7 +144,11 @@ bool bool DoO(char ** input, int * ret) { - *ret = GetOct(input); + if (!GetOct(input, ret)) + { + SetErrorCode(1015); + return false; + } if (GetReturn(input)) { return true; @@ -1102,7 +1118,11 @@ int ++(*input); return -GetReturnDefault(input); } - *ret = GetDec(input); + if (!GetDec(input, ret)) + { + SetErrorCode(1011); + return 0; + } return GetReturnDefault(input); } @@ -1118,7 +1138,11 @@ int ++(*input); return -GetReturnDefault(input); } - *ret = GetNumber(input); + if (!GetDec(input, ret)) + { + SetErrorCode(1012); + return 0; + } return GetReturnDefault(input); } @@ -1134,7 +1158,11 @@ int ++(*input); return -GetReturnDefault(input); } - *ret = GetHex(input); + if (!GetHex(input, ret)) + { + SetErrorCode(1013); + return 0; + } return GetReturnDefault(input); } @@ -1168,7 +1196,11 @@ int ++(*input); return -GetReturnDefault(input); } - *ret = GetOct(input); + if (!GetOct(input, ret)) + { + SetErrorCode(1015); + return 0; + } return GetReturnDefault(input); } @@ -1445,4 +1477,3 @@ int DoL(input, ret); return GetReturnDefault(input); } - diff --git a/sscanf2.inc b/sscanf2.inc index f6ee62e..d6e10fd 100644 --- a/sscanf2.inc +++ b/sscanf2.inc @@ -71,19 +71,19 @@ * specifiers which can easily extract data types, at the expense * of fine-grained control. To convert a string in to two numbers would * look like: - * + * * * new num1, num2;
* sscanf("45 100", "ii", num1, num2); *
- * + * * ii is the specifier string, which here means "integer integer"; * stating that the input string should be two whole numbers in a row (which * is - "45 100"). num1 and num2 are the destination * variables to store the found numbers in (after conversion from strings). * You can check if the conversion failed by looking for a non-naught return * value: - * + * * * new num1, num2;
* if (sscanf("hello 100", "ii", num1, num2))
@@ -91,7 +91,7 @@ * printf("The input was not two numbers.");
* } *
- * + * * This will fail because "hello" is not a whole number (or indeed * any type of number at all). For more information on using the function * refer to the tutorials or the reference documentation in @@ -793,7 +793,7 @@ stock const MATCH_NAME_PARTIAL[] = "MATCH_NAME_PARTIAL"; * anyone. To detect this case, if more than one player will match the * specified string then sscanf will return an ID of cellmin * instead. This can be combined with U for a lot more power: - * + * * * sscanf(params, "?<CELLMIN_ON_MATCHES=1>U(-1)", id);
* if (id == -1)
@@ -824,14 +824,14 @@ stock const CELLMIN_ON_MATCHES[] = "CELLMIN_ON_MATCHES"; * compile-time spell checking on the name. As with U, K used to * require a valid identifier as the default and would parse it using the * specified callback, so this would not work: - * + * * * K<vehicle>(Veyron) * * * Because that is not a valid vehicle name in GTA. The new version now just * takes a number and returns that regardless: - * + * * * K<vehicle>(999) * @@ -1650,7 +1650,7 @@ stock SSCANF_GetClosestValue(const input[], const candidates[][], const results[ "Thermal Goggles", "Parachute" }; - + // This function is VERY basic, needs VASTLY improving to detect variations. if ('0' <= string[0] <= '9') { @@ -2148,12 +2148,12 @@ Return: Notes: A fail is either insufficient variables to store the data or insufficient data for the format string - excess data is disgarded. - + A string in the middle of the input data is extracted as a single word, a string at the end of the data collects all remaining text. - + The format codes are: - + c - A character. d, i - An integer. h, x - A hex number (e.g. a colour). @@ -2163,13 +2163,13 @@ Notes: pX - An additional delimiter where X is another character. '' - Encloses a litteral string to locate. u - User, takes a name, part of a name or an id and returns the id if they're connected. - + Now has IsNumeric integrated into the code. - + Added additional delimiters in the form of all whitespace and an optioanlly specified one in the format string. -*----------------------------------------------------------------------------*/ - + stock sscanf(string[], format[], {Float,_}:...) { #if defined isnull @@ -2267,15 +2267,15 @@ stock sscanf(string[], format[], {Float,_}:...) } case 'f': { - - new changestr[16], changepos = 0, strpos = stringPos; + + new changestr[16], changepos = 0, strpos = stringPos; while(changepos < 16 && string[strpos] && string[strpos] != delim) { changestr[changepos++] = string[strpos++]; - } - changestr[changepos] = '\0'; - setarg(paramPos,0,_:floatstr(changestr)); - } + } + changestr[changepos] = '\0'; + setarg(paramPos,0,_:floatstr(changestr)); + } case 'p': { delim = format[formatPos++]; @@ -2420,4 +2420,3 @@ stock sscanf(string[], format[], {Float,_}:...) while (delim > ' '); return 0; } - diff --git a/tests/sscanfErr.pwn b/tests/sscanfErr.pwn index 47c3a6e..2d13668 100644 --- a/tests/sscanfErr.pwn +++ b/tests/sscanfErr.pwn @@ -3,81 +3,81 @@ #include #include -ASSERT(bool:test, const message[]) +ASSERT_EQ(actual, expected, const message[]) { - if (!test) + if (actual != expected) { - printf("SSCANF test failed: %s", message); + printf("SSCANF test failed: %s, expected: %d, got: %d", message, expected, actual); } } main() { - SSCANF_Option(SSCANF_QUIET, 1); - - ASSERT(SSCANF_GetErrorCategory(1004) == SSCANF_ERROR_MISSING, "SSCANF_ERROR_MISSING"); - ASSERT(SSCANF_GetErrorCategory(1009) == SSCANF_ERROR_EXCESS, "SSCANF_ERROR_EXCESS"); - ASSERT(SSCANF_GetErrorCategory(1001) == SSCANF_ERROR_COLOUR, "SSCANF_ERROR_COLOUR"); - ASSERT(SSCANF_GetErrorCategory(2) == SSCANF_ERROR_OVERFLOW, "SSCANF_ERROR_OVERFLOW"); - ASSERT(SSCANF_GetErrorCategory(1003) == SSCANF_ERROR_NOT_FOUND, "SSCANF_ERROR_NOT_FOUND"); - ASSERT(SSCANF_GetErrorCategory(1010) == SSCANF_ERROR_NO_ALTS, "SSCANF_ERROR_NO_ALTS"); - + SSCANF_Option(SSCANF_QUIET, 1); + + ASSERT_EQ(SSCANF_GetErrorCategory(1004), SSCANF_ERROR_MISSING, "SSCANF_ERROR_MISSING"); + ASSERT_EQ(SSCANF_GetErrorCategory(1009), SSCANF_ERROR_EXCESS, "SSCANF_ERROR_EXCESS"); + ASSERT_EQ(SSCANF_GetErrorCategory(1001), SSCANF_ERROR_COLOUR, "SSCANF_ERROR_COLOUR"); + ASSERT_EQ(SSCANF_GetErrorCategory(2), SSCANF_ERROR_OVERFLOW, "SSCANF_ERROR_OVERFLOW"); + ASSERT_EQ(SSCANF_GetErrorCategory(1003), SSCANF_ERROR_NOT_FOUND, "SSCANF_ERROR_NOT_FOUND"); + ASSERT_EQ(SSCANF_GetErrorCategory(1010), SSCANF_ERROR_NO_ALTS, "SSCANF_ERROR_NO_ALTS"); + new str[8]; - ASSERT(sscanf("this-is-a-too-long-string", "s[8]", str) == 0, "1"); - ASSERT(sscanf("this-is-a-too-long-string", "?s[8]", str) == 2, "2"); - ASSERT(sscanf("this-is-a-too-long-string", "??s[8]", str) == 3 | (2 << 16), "3"); - ASSERT(sscanf("this-is-a-too-long-string", "?s[8]", str) == 0, "4"); - ASSERT(sscanf("this-is-a-too-long-string", "??s[8]", str) == 3, "5"); - ASSERT(sscanf("this-is-a-too-long-string", "??s[8]", str) == 0, "6"); - ASSERT(sscanf("this-is-a-too-long-string", "?s[8]", str) == 0, "7a"); - ASSERT(sscanf("this-is-a-too-long-string", "??s[8]", str) == 2, "7b"); - ASSERT(sscanf("this-is-a-too-long-string", "???s[8]", str) == 4 | (7 << 16), "8"); - + ASSERT_EQ(sscanf("this-is-a-too-long-string", "s[8]", str), 0, "1"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "?s[8]", str), 2, "2"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "??s[8]", str), 3 | (2 << 16), "3"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "?s[8]", str), 0, "4"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "??s[8]", str), 3, "5"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "??s[8]", str), 0, "6"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "?s[8]", str), 0, "7a"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "??s[8]", str), 2, "7b"); + ASSERT_EQ(sscanf("this-is-a-too-long-string", "???s[8]", str), 4 | (7 << 16), "8"); + new int; - ASSERT(sscanf("not-a-number", "i", int) == 1, "9"); - ASSERT(sscanf("not-a-number", "?i", int) == 2, "10"); - ASSERT(sscanf("not-a-number", "??i", int) == 3 | (1011 << 16), "11"); - ASSERT(sscanf("not-a-number", "?i", int) == 2 | (1011 << 16), "12"); - ASSERT(sscanf("not-a-number", "??i", int) == 3, "13"); - ASSERT(sscanf("not-a-number", "??i", int) == 3 | (3 << 16), "14"); - ASSERT(sscanf("not-a-number", "?i", int) == 2, "15a"); - ASSERT(sscanf("not-a-number", "??i", int) == 2, "15b"); - ASSERT(sscanf("not-a-number", "???i", int) == 4 | (3 << 16), "18"); - - ASSERT(sscanf("not-a-number", "i", int) == 1, "9"); - ASSERT(sscanf("not-a-number", "?i", int) == 2, "10"); - ASSERT(sscanf("not-a-number", "??i", int) == SSCANF_ERROR(3, 1011), "11"); - ASSERT(sscanf("not-a-number", "?i", int) == SSCANF_ERROR(2, 1011), "12"); - ASSERT(sscanf("not-a-number", "??i", int) == 3, "13"); - ASSERT(sscanf("not-a-number", "??i", int) == SSCANF_ERROR(3, 3), "14"); - ASSERT(sscanf("not-a-number", "?i", int) == 2, "15a"); - ASSERT(sscanf("not-a-number", "??i", int) == 2, "15b"); - ASSERT(sscanf("not-a-number", "???i", int) == SSCANF_ERROR(4, SSCANF_ERROR_INVALID), "18"); - - ASSERT(sscanf("4 5 6", "ii", int, int) == 0, "Unstrict"); - ASSERT(sscanf("4 5 6", "ii!", int, int) == 3, "Strict"); - ASSERT(sscanf("4 5 ", "ii", int, int) == 0, "Unstrict"); - ASSERT(sscanf("4 5 ", "ii!", int, int) == 0, "Strict"); - ASSERT(sscanf("4 5 6", "?ii!", int, int) == SSCANF_ERROR(4, 1009), "Code"); - + ASSERT_EQ(sscanf("not-a-number", "i", int), 1, "9"); + ASSERT_EQ(sscanf("not-a-number", "?i", int), 2, "10"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), 3 | (1011 << 16), "11"); + ASSERT_EQ(sscanf("not-a-number", "?i", int), 2 | (1011 << 16), "12"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), 3, "13"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), 3 | (3 << 16), "14"); + ASSERT_EQ(sscanf("not-a-number", "?i", int), 2, "15a"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), 2, "15b"); + ASSERT_EQ(sscanf("not-a-number", "???i", int), 4 | (3 << 16), "18"); + + ASSERT_EQ(sscanf("not-a-number", "i", int), 1, "9"); + ASSERT_EQ(sscanf("not-a-number", "?i", int), 2, "10"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), SSCANF_ERROR(3, 1011), "11"); + ASSERT_EQ(sscanf("not-a-number", "?i", int), SSCANF_ERROR(2, 1011), "12"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), 3, "13"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), SSCANF_ERROR(3, 3), "14"); + ASSERT_EQ(sscanf("not-a-number", "?i", int), 2, "15a"); + ASSERT_EQ(sscanf("not-a-number", "??i", int), 2, "15b"); + ASSERT_EQ(sscanf("not-a-number", "???i", int), SSCANF_ERROR(4, SSCANF_ERROR_INVALID), "18"); + + ASSERT_EQ(sscanf("4 5 6", "ii", int, int), 0, "Unstrict"); + ASSERT_EQ(sscanf("4 5 6", "ii!", int, int), 3, "Strict"); + ASSERT_EQ(sscanf("4 5 ", "ii", int, int), 0, "Unstrict"); + ASSERT_EQ(sscanf("4 5 ", "ii!", int, int), 0, "Strict"); + ASSERT_EQ(sscanf("4 5 6", "?ii!", int, int), SSCANF_ERROR(4, 1009), "Code"); + new alt, a, b, c, d; - ASSERT(sscanf("4 5", "bb|ii", alt, a, b, c, d) == 0, "Alt 1a"); - ASSERT(alt == 2, "Alt 1b"); - ASSERT(c == 4, "Alt 1c"); - ASSERT(d == 5, "Alt 1d"); - - ASSERT(sscanf("C C", "bb|ii", alt, a, b, c, d) == 1, "Alt 2a"); - ASSERT(alt == 0, "Alt 2b"); - ASSERT(sscanf("C C", "bb|xx", alt, a, b, c, d) == 0, "Alt 3a"); - ASSERT(alt == 2, "Alt 3b"); - ASSERT(c == 0xC, "Alt 3c"); - ASSERT(d == 0xC, "Alt 3d"); - - ASSERT(sscanf("20 10", "xx|ii", alt, a, b, c, d) == 0, "Alt 4a"); - ASSERT(alt == 1, "Alt 4b"); - ASSERT(a == 0x20, "Alt 4c"); - ASSERT(b == 0x10, "Alt 4d"); - + ASSERT_EQ(sscanf("4 5", "bb|ii", alt, a, b, c, d), 0, "Alt 1a"); + ASSERT_EQ(alt, 2, "Alt 1b"); + ASSERT_EQ(c, 4, "Alt 1c"); + ASSERT_EQ(d, 5, "Alt 1d"); + + ASSERT_EQ(sscanf("C C", "bb|ii", alt, a, b, c, d), 1, "Alt 2a"); + ASSERT_EQ(alt, 0, "Alt 2b"); + ASSERT_EQ(sscanf("C C", "bb|xx", alt, a, b, c, d), 0, "Alt 3a"); + ASSERT_EQ(alt, 2, "Alt 3b"); + ASSERT_EQ(c, 0xC, "Alt 3c"); + ASSERT_EQ(d, 0xC, "Alt 3d"); + + ASSERT_EQ(sscanf("20 10", "xx|ii", alt, a, b, c, d), 0, "Alt 4a"); + ASSERT_EQ(alt, 1, "Alt 4b"); + ASSERT_EQ(a, 0x20, "Alt 4c"); + ASSERT_EQ(b, 0x10, "Alt 4d"); + switch (int) { case SSCANF_ERROR_NONE: {} @@ -105,17 +105,45 @@ main() case SSCANF_ERROR(1, NOT_FOUND): {} case SSCANF_ERROR(1, NO_ALTS): {} } - + Shop(0, "weapon 4 5"); Shop(0, "armour"); Shop(0, "health"); Shop(0, "vehicle Infernus 5 6"); Shop(0, "vehicle 500 9 10"); Shop(0, "food"); - - ASSERT(sscanf("hello", "i|f", alt, int, int) == 1, "call"); - ASSERT(SSCANF_GetLastError() == 1016, "error"); - ASSERT(SSCANF_GetErrorSpecifier() == 3, "index"); + + ASSERT_EQ(sscanf("hello", "i|f", alt, int, int), 1, "call"); + ASSERT_EQ(SSCANF_GetLastError(), 1016, "error"); + ASSERT_EQ(SSCANF_GetErrorSpecifier(), 3, "index"); + + ASSERT_EQ(sscanf("2147483648", "i", a), 1, "Overflow (decimal)"); + ASSERT_EQ(sscanf("2147483647", "i", a), 0, "No overflow (decimal)"); + ASSERT_EQ(a, 2147483647, "No overflow (decimal)"); + ASSERT_EQ(sscanf("-2147483648", "i", a), 0, "No overflow (decimal)"); + ASSERT_EQ(a, -2147483648, "No overflow (decimal)"); + ASSERT_EQ(sscanf("-2147483649", "i", a), 1, "Overflow (decimal)"); + ASSERT_EQ(sscanf("4294967294", "i", a), 1, "Overflow (decimal)"); + + ASSERT_EQ(sscanf("0x100000000", "x", a), 1, "Overflow (hex)"); + ASSERT_EQ(sscanf("0x80000000", "x", a), 1, "Overflow (hex)"); + ASSERT_EQ(sscanf("0x7FFFFFFF", "x", a), 0, "No overflow (hex)"); + ASSERT_EQ(a, 2147483647, "No overflow (hex)"); + ASSERT_EQ(sscanf("-0x80000000", "x", a), 0, "No overflow (hex)"); + ASSERT_EQ(a, -2147483648, "No overflow (hex)"); + + ASSERT_EQ(sscanf("020000000000", "o", a), 1, "Overflow (octal)"); + ASSERT_EQ(sscanf("017777777777", "o", a), 0, "No overflow (octal)"); + ASSERT_EQ(a, 2147483647, "No overflow (octal)"); + ASSERT_EQ(sscanf("-020000000000", "o", a), 0, "No overflow (octal)"); + ASSERT_EQ(a, -2147483648, "No overflow (octal)"); + + ASSERT_EQ(sscanf("2147483648", "n", a), 1, "Overflow (number, decimal)"); + ASSERT_EQ(sscanf("2147483647", "n", a), 0, "No overflow (number, decimal)"); + ASSERT_EQ(a, 2147483647, "No overflow (number, decimal)"); + ASSERT_EQ(sscanf("0x80000000", "n", a), 1, "Overflow (number, hex)"); + ASSERT_EQ(sscanf("0x7FFFFFFF", "n", a), 0, "No overflow (number, hex)"); + ASSERT_EQ(a, 2147483647, "No overflow (number, hex)"); } #define COLOUR_ERROR (0xFF0000AA) @@ -162,4 +190,3 @@ Shop(playerid, const params[]) } return 1; } -