From 1f670e22e39936651f576401c63393e2ea1e0d86 Mon Sep 17 00:00:00 2001 From: F1F88 <0xf1f88@gmail.com> Date: Sun, 26 Apr 2026 17:25:43 +0800 Subject: [PATCH 1/4] Add native StringToInt64Ex --- core/logic/smn_string.cpp | 20 ++++ plugins/include/string.inc | 10 ++ plugins/include/testing.inc | 14 +++ plugins/testsuite/mock/test_string.sp | 162 ++++++++++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 plugins/testsuite/mock/test_string.sp diff --git a/core/logic/smn_string.cpp b/core/logic/smn_string.cpp index be7488dcd6..6e32bd1096 100644 --- a/core/logic/smn_string.cpp +++ b/core/logic/smn_string.cpp @@ -155,6 +155,25 @@ static cell_t StringToInt64(IPluginContext *pCtx, const cell_t *params) return dummy - str; } +static cell_t StringToInt64Ex(IPluginContext *pCtx, const cell_t *params) +{ + cell_t* out; + if (int err = pCtx->LocalToPhysAddr(params[1], &out); err != SP_ERROR_NONE) + return pCtx->ThrowNativeErrorEx(err, "Could not read argument (error %d)", err); + + char *str, *dummy = nullptr; + if (int err = pCtx->LocalToString(params[2], &str); err != SP_ERROR_NONE) + return pCtx->ThrowNativeErrorEx(err, "Invalid str (error %d)", err); + + cell_t *bytes; + if (int err = pCtx->LocalToPhysAddr(params[3], &bytes); err != SP_ERROR_NONE) + return pCtx->ThrowNativeErrorEx(err, "Invalid bytes (error %d)", err); + + *reinterpret_cast(out) = strtoll(str, &dummy, params[4]); + *bytes = dummy - str; + return 0; +} + static cell_t sm_numtostr(IPluginContext *pCtx, const cell_t *params) { char *str; @@ -636,6 +655,7 @@ REGISTER_NATIVES(basicStrings) {"StringToInt", sm_strconvint}, {"StringToIntEx", StringToIntEx}, {"StringToInt64", StringToInt64}, + {"StringToInt64Ex", StringToInt64Ex}, {"StringToFloat", sm_strtofloat}, {"StringToFloatEx", StringToFloatEx}, {"StripQuotes", StripQuotes}, diff --git a/plugins/include/string.inc b/plugins/include/string.inc index c3851009cd..cfef0fa3be 100644 --- a/plugins/include/string.inc +++ b/plugins/include/string.inc @@ -212,6 +212,16 @@ native int StringToIntEx(const char[] str, int &result, int nBase=10); */ native int StringToInt64(const char[] str, int result[2], int nBase=10); +/** + * Converts a string to an int64 value. + * + * @param str String to convert. + * @param bytes Number of characters consumed. + * @param nBase Numerical base to use. 10 is default. + * @return int64 conversion of string, or 0 on failure. + */ +native int64 StringToInt64Ex(const char[] str, int &bytes=0, int nBase=10); + /** * Converts an integer to a string. * diff --git a/plugins/include/testing.inc b/plugins/include/testing.inc index 6ff6d73201..657612cf65 100644 --- a/plugins/include/testing.inc +++ b/plugins/include/testing.inc @@ -141,3 +141,17 @@ stock void AssertStrArrayEq(const char[] text, const char[][] value, const char[ } PrintToServer("[%d] %s: '%s' arrays are equal OK", TestNumber, TestContext, text); } + +stock void AssertInt64Eq(const char[] text, int64 value, int64 expected) +{ + TestNumber++; + if (value == expected) + { + PrintToServer("[%d] %s: %s == %ld OK", TestNumber, TestContext, text, expected); + } + else + { + PrintToServer("[%d] %s FAIL: %s should be %ld, got %ld", TestNumber, TestContext, text, expected, value); + ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext); + } +} diff --git a/plugins/testsuite/mock/test_string.sp b/plugins/testsuite/mock/test_string.sp new file mode 100644 index 0000000000..9fdf2b0f78 --- /dev/null +++ b/plugins/testsuite/mock/test_string.sp @@ -0,0 +1,162 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include + + +public void OnPluginStart() +{ + Test_StringToInt64Ex(); + + // Bnechmark_StringToInt64Ex(); +} + + +void Test_StringToInt64Ex() +{ + SetTestContext("Test StringToInt64Ex"); + + AssertInt64Eq("DEC 0", StringToInt64Ex("0"), 0); + AssertInt64Eq("DEC 1234567", StringToInt64Ex("1234567"), 1234567); + AssertInt64Eq("DEC 1234567654321", StringToInt64Ex("1234567654321"), 1234567654321); + AssertInt64Eq("DEC 9223372036854775807", StringToInt64Ex("9223372036854775807"), 9223372036854775807); + AssertInt64Eq("DEC -1234567", StringToInt64Ex("-1234567"), -1234567); + AssertInt64Eq("DEC -1234567654321", StringToInt64Ex("-1234567654321"), -1234567654321); + AssertInt64Eq("DEC -9223372036854775807", StringToInt64Ex("-9223372036854775807"), -9223372036854775807); + + int bytes; + + // bytes + AssertInt64Eq( + "result 0", + StringToInt64Ex("0", bytes), + 0); + AssertEq("bytes 0", bytes, 1); + + AssertInt64Eq( + "result 1234567", + StringToInt64Ex("1234567", bytes), + 1234567); + AssertEq("bytes 1234567", bytes, 7); + + AssertInt64Eq( + "result 1234567654321", + StringToInt64Ex("1234567654321", bytes), + 1234567654321); + AssertEq("bytes 1234567654321", bytes, 13); + + AssertInt64Eq( + "result 9223372036854775807", + StringToInt64Ex("9223372036854775807", bytes), + 9223372036854775807); + AssertEq("bytes 9223372036854775807", bytes, 19); + + AssertInt64Eq( + "result -1234567", + StringToInt64Ex("-1234567", bytes), + -1234567); + AssertEq("bytes -1234567", bytes, 8); + + AssertInt64Eq( + "result -1234567654321", + StringToInt64Ex("-1234567654321", bytes), + -1234567654321); + AssertEq("bytes -1234567654321", bytes, 14); + + AssertInt64Eq( + "result -9223372036854775807", + StringToInt64Ex("-9223372036854775807", bytes), + -9223372036854775807); + AssertEq("bytes -9223372036854775807", bytes, 20); + + // Special nBase + AssertInt64Eq( + "result 10001111101110001111101110110101110110001", + StringToInt64Ex("10001111101110001111101110110101110110001", bytes, 2), + 1234567654321); + AssertEq("bytes 10001111101110001111101110110101110110001", bytes, 41); + + AssertInt64Eq( + "result 21756175665661", + StringToInt64Ex("21756175665661", bytes, 8), + 1234567654321); + AssertEq("bytes 11F71F76BB1", bytes, 14); + + AssertInt64Eq( + "result 11F71F76BB1", + StringToInt64Ex("11F71F76BB1", bytes, 16), + 1234567654321); + AssertEq("bytes 11F71F76BB1", bytes, 11); + + // Orther + AssertInt64Eq( + "result a1b2c3d4e5f6g7", + StringToInt64Ex("a1b2c3d4e5f6g7", bytes), + 0); + AssertEq("bytes a1b2c3d4e5f6g7", bytes, 0); + + AssertInt64Eq( + "result 0b10101", + StringToInt64Ex("0b10101", bytes), + 0); + AssertEq("bytes 0b10101", bytes, 1); + + AssertInt64Eq( + "result 1_234_567", + StringToInt64Ex("1_234_567", bytes), + 1); + AssertEq("bytes 1_234_567", bytes, 1); +} + + + +// ================================================================================================ + +/* +stock int64 StringToInt64Ex_IncOnly(const char[] str, int &bytes=0, int nBase=10) +{ + // Error in: 9223372036854775807, -1234567654321 + int result[2]; + bytes = StringToInt64(str, result, nBase); + return (view_as(result[1]) << 32) | result[0]; +} + +#include + +void Bnechmark_StringToInt64Ex() +{ + Profiler profile = new Profiler(); + int iters = 1_000_000; + + profile.Start(); + for (int i = 0; i < iters; ++i) + { + StringToInt64Ex("1234567654321"); + } + profile.Stop(); + float delta = profile.Time; + + profile.Start(); + for (int i = 0; i < iters; ++i) + { + StringToInt64Ex_IncOnly("1234567654321"); + } + profile.Stop(); + float deltaIncOnly = profile.Time; + + delete profile; + + PrintToServer( + "[benchmark] [StringToInt64Ex] | Iters %7d | Elapsed %6.3f secs %9d/sec", + iters, + delta, + RoundToFloor(iters / delta)); + + PrintToServer( + "[benchmark] [StringToInt64Ex_IncOnly] | Iters %7d | Elapsed %6.3f secs %9d/sec", + iters, + deltaIncOnly, + RoundToFloor(iters / deltaIncOnly)); +} +*/ From 8a1a3628605cd990c0974ae81b99d88db9f42c37 Mon Sep 17 00:00:00 2001 From: F1F88 <0xf1f88@gmail.com> Date: Sun, 26 Apr 2026 21:32:52 +0800 Subject: [PATCH 2/4] Delete useless test code --- plugins/testsuite/mock/test_string.sp | 54 --------------------------- 1 file changed, 54 deletions(-) diff --git a/plugins/testsuite/mock/test_string.sp b/plugins/testsuite/mock/test_string.sp index 9fdf2b0f78..8163374b2f 100644 --- a/plugins/testsuite/mock/test_string.sp +++ b/plugins/testsuite/mock/test_string.sp @@ -8,8 +8,6 @@ public void OnPluginStart() { Test_StringToInt64Ex(); - - // Bnechmark_StringToInt64Ex(); } @@ -108,55 +106,3 @@ void Test_StringToInt64Ex() 1); AssertEq("bytes 1_234_567", bytes, 1); } - - - -// ================================================================================================ - -/* -stock int64 StringToInt64Ex_IncOnly(const char[] str, int &bytes=0, int nBase=10) -{ - // Error in: 9223372036854775807, -1234567654321 - int result[2]; - bytes = StringToInt64(str, result, nBase); - return (view_as(result[1]) << 32) | result[0]; -} - -#include - -void Bnechmark_StringToInt64Ex() -{ - Profiler profile = new Profiler(); - int iters = 1_000_000; - - profile.Start(); - for (int i = 0; i < iters; ++i) - { - StringToInt64Ex("1234567654321"); - } - profile.Stop(); - float delta = profile.Time; - - profile.Start(); - for (int i = 0; i < iters; ++i) - { - StringToInt64Ex_IncOnly("1234567654321"); - } - profile.Stop(); - float deltaIncOnly = profile.Time; - - delete profile; - - PrintToServer( - "[benchmark] [StringToInt64Ex] | Iters %7d | Elapsed %6.3f secs %9d/sec", - iters, - delta, - RoundToFloor(iters / delta)); - - PrintToServer( - "[benchmark] [StringToInt64Ex_IncOnly] | Iters %7d | Elapsed %6.3f secs %9d/sec", - iters, - deltaIncOnly, - RoundToFloor(iters / deltaIncOnly)); -} -*/ From 4a31036d69e4144c9f45796d267e1e650e5fa891 Mon Sep 17 00:00:00 2001 From: F1F88 <0xf1f88@gmail.com> Date: Thu, 7 May 2026 22:28:23 +0800 Subject: [PATCH 3/4] Rename native StringToInt64Ex to StringToI64 --- core/logic/smn_string.cpp | 14 ++-- plugins/include/string.inc | 6 +- plugins/testsuite/mock/test_string.sp | 104 +++++++++++++------------- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/core/logic/smn_string.cpp b/core/logic/smn_string.cpp index 6e32bd1096..ca181d41d6 100644 --- a/core/logic/smn_string.cpp +++ b/core/logic/smn_string.cpp @@ -155,7 +155,7 @@ static cell_t StringToInt64(IPluginContext *pCtx, const cell_t *params) return dummy - str; } -static cell_t StringToInt64Ex(IPluginContext *pCtx, const cell_t *params) +static cell_t StringToI64(IPluginContext *pCtx, const cell_t *params) { cell_t* out; if (int err = pCtx->LocalToPhysAddr(params[1], &out); err != SP_ERROR_NONE) @@ -165,12 +165,12 @@ static cell_t StringToInt64Ex(IPluginContext *pCtx, const cell_t *params) if (int err = pCtx->LocalToString(params[2], &str); err != SP_ERROR_NONE) return pCtx->ThrowNativeErrorEx(err, "Invalid str (error %d)", err); - cell_t *bytes; - if (int err = pCtx->LocalToPhysAddr(params[3], &bytes); err != SP_ERROR_NONE) - return pCtx->ThrowNativeErrorEx(err, "Invalid bytes (error %d)", err); + cell_t *consumed; + if (int err = pCtx->LocalToPhysAddr(params[4], &consumed); err != SP_ERROR_NONE) + return pCtx->ThrowNativeErrorEx(err, "Invalid nConsumed (error %d)", err); - *reinterpret_cast(out) = strtoll(str, &dummy, params[4]); - *bytes = dummy - str; + *reinterpret_cast(out) = strtoll(str, &dummy, params[3]); + *consumed = dummy - str; return 0; } @@ -655,7 +655,7 @@ REGISTER_NATIVES(basicStrings) {"StringToInt", sm_strconvint}, {"StringToIntEx", StringToIntEx}, {"StringToInt64", StringToInt64}, - {"StringToInt64Ex", StringToInt64Ex}, + {"StringToI64", StringToI64}, {"StringToFloat", sm_strtofloat}, {"StringToFloatEx", StringToFloatEx}, {"StripQuotes", StripQuotes}, diff --git a/plugins/include/string.inc b/plugins/include/string.inc index cfef0fa3be..ab467504a6 100644 --- a/plugins/include/string.inc +++ b/plugins/include/string.inc @@ -213,14 +213,14 @@ native int StringToIntEx(const char[] str, int &result, int nBase=10); native int StringToInt64(const char[] str, int result[2], int nBase=10); /** - * Converts a string to an int64 value. + * Converts a string to an int64 type value. * * @param str String to convert. - * @param bytes Number of characters consumed. * @param nBase Numerical base to use. 10 is default. + * @param nConsumed Number of characters consumed. * @return int64 conversion of string, or 0 on failure. */ -native int64 StringToInt64Ex(const char[] str, int &bytes=0, int nBase=10); +native int64 StringToI64(const char[] str, int nBase=10, int &nConsumed=0); /** * Converts an integer to a string. diff --git a/plugins/testsuite/mock/test_string.sp b/plugins/testsuite/mock/test_string.sp index 8163374b2f..3f07af3547 100644 --- a/plugins/testsuite/mock/test_string.sp +++ b/plugins/testsuite/mock/test_string.sp @@ -7,102 +7,102 @@ public void OnPluginStart() { - Test_StringToInt64Ex(); + Test_StringToI64(); } -void Test_StringToInt64Ex() +void Test_StringToI64() { - SetTestContext("Test StringToInt64Ex"); + SetTestContext("Test StringToI64"); - AssertInt64Eq("DEC 0", StringToInt64Ex("0"), 0); - AssertInt64Eq("DEC 1234567", StringToInt64Ex("1234567"), 1234567); - AssertInt64Eq("DEC 1234567654321", StringToInt64Ex("1234567654321"), 1234567654321); - AssertInt64Eq("DEC 9223372036854775807", StringToInt64Ex("9223372036854775807"), 9223372036854775807); - AssertInt64Eq("DEC -1234567", StringToInt64Ex("-1234567"), -1234567); - AssertInt64Eq("DEC -1234567654321", StringToInt64Ex("-1234567654321"), -1234567654321); - AssertInt64Eq("DEC -9223372036854775807", StringToInt64Ex("-9223372036854775807"), -9223372036854775807); + AssertInt64Eq("DEC 0", StringToI64("0"), 0); + AssertInt64Eq("DEC 1234567", StringToI64("1234567"), 1234567); + AssertInt64Eq("DEC 1234567654321", StringToI64("1234567654321"), 1234567654321); + AssertInt64Eq("DEC 9223372036854775807", StringToI64("9223372036854775807"), 9223372036854775807); + AssertInt64Eq("DEC -1234567", StringToI64("-1234567"), -1234567); + AssertInt64Eq("DEC -1234567654321", StringToI64("-1234567654321"), -1234567654321); + AssertInt64Eq("DEC -9223372036854775807", StringToI64("-9223372036854775807"), -9223372036854775807); - int bytes; + int nConsumed; - // bytes + // nConsumed AssertInt64Eq( - "result 0", - StringToInt64Ex("0", bytes), + "result 0", + StringToI64("0", .nConsumed=nConsumed), 0); - AssertEq("bytes 0", bytes, 1); + AssertEq("nConsumed 0", nConsumed, 1); AssertInt64Eq( - "result 1234567", - StringToInt64Ex("1234567", bytes), + "result 1234567", + StringToI64("1234567", .nConsumed=nConsumed), 1234567); - AssertEq("bytes 1234567", bytes, 7); + AssertEq("nConsumed 1234567", nConsumed, 7); AssertInt64Eq( - "result 1234567654321", - StringToInt64Ex("1234567654321", bytes), + "result 1234567654321", + StringToI64("1234567654321", .nConsumed=nConsumed), 1234567654321); - AssertEq("bytes 1234567654321", bytes, 13); + AssertEq("nConsumed 1234567654321", nConsumed, 13); AssertInt64Eq( - "result 9223372036854775807", - StringToInt64Ex("9223372036854775807", bytes), + "result 9223372036854775807", + StringToI64("9223372036854775807", .nConsumed=nConsumed), 9223372036854775807); - AssertEq("bytes 9223372036854775807", bytes, 19); + AssertEq("nConsumed 9223372036854775807", nConsumed, 19); AssertInt64Eq( - "result -1234567", - StringToInt64Ex("-1234567", bytes), + "result -1234567", + StringToI64("-1234567", .nConsumed=nConsumed), -1234567); - AssertEq("bytes -1234567", bytes, 8); + AssertEq("nConsumed -1234567", nConsumed, 8); AssertInt64Eq( - "result -1234567654321", - StringToInt64Ex("-1234567654321", bytes), + "result -1234567654321", + StringToI64("-1234567654321", .nConsumed=nConsumed), -1234567654321); - AssertEq("bytes -1234567654321", bytes, 14); + AssertEq("nConsumed -1234567654321", nConsumed, 14); AssertInt64Eq( - "result -9223372036854775807", - StringToInt64Ex("-9223372036854775807", bytes), + "result -9223372036854775807", + StringToI64("-9223372036854775807", .nConsumed=nConsumed), -9223372036854775807); - AssertEq("bytes -9223372036854775807", bytes, 20); + AssertEq("nConsumed -9223372036854775807", nConsumed, 20); // Special nBase AssertInt64Eq( - "result 10001111101110001111101110110101110110001", - StringToInt64Ex("10001111101110001111101110110101110110001", bytes, 2), + "result 10001111101110001111101110110101110110001", + StringToI64("10001111101110001111101110110101110110001", 2, nConsumed), 1234567654321); - AssertEq("bytes 10001111101110001111101110110101110110001", bytes, 41); + AssertEq("nConsumed 10001111101110001111101110110101110110001", nConsumed, 41); AssertInt64Eq( - "result 21756175665661", - StringToInt64Ex("21756175665661", bytes, 8), + "result 21756175665661", + StringToI64("21756175665661", 8, nConsumed), 1234567654321); - AssertEq("bytes 11F71F76BB1", bytes, 14); + AssertEq("nConsumed 21756175665661", nConsumed, 14); AssertInt64Eq( - "result 11F71F76BB1", - StringToInt64Ex("11F71F76BB1", bytes, 16), + "result 11F71F76BB1", + StringToI64("11F71F76BB1", 16, nConsumed), 1234567654321); - AssertEq("bytes 11F71F76BB1", bytes, 11); + AssertEq("nConsumed 11F71F76BB1", nConsumed, 11); - // Orther + // Other AssertInt64Eq( - "result a1b2c3d4e5f6g7", - StringToInt64Ex("a1b2c3d4e5f6g7", bytes), + "result a1b2c3d4e5f6g7", + StringToI64("a1b2c3d4e5f6g7", .nConsumed=nConsumed), 0); - AssertEq("bytes a1b2c3d4e5f6g7", bytes, 0); + AssertEq("nConsumed a1b2c3d4e5f6g7", nConsumed, 0); AssertInt64Eq( - "result 0b10101", - StringToInt64Ex("0b10101", bytes), + "result 0b10101", + StringToI64("0b10101", .nConsumed=nConsumed), 0); - AssertEq("bytes 0b10101", bytes, 1); + AssertEq("nConsumed 0b10101", nConsumed, 1); AssertInt64Eq( - "result 1_234_567", - StringToInt64Ex("1_234_567", bytes), + "result 1_234_567", + StringToI64("1_234_567", .nConsumed=nConsumed), 1); - AssertEq("bytes 1_234_567", bytes, 1); + AssertEq("nConsumed 1_234_567", nConsumed, 1); } From ae603e5a2aa7e58c9d5ef7ccbfc3d8140b6675a5 Mon Sep 17 00:00:00 2001 From: F1F88 <0xf1f88@gmail.com> Date: Thu, 7 May 2026 22:30:36 +0800 Subject: [PATCH 4/4] Add documentation for out-of-bounds situations. --- plugins/include/string.inc | 8 ++++++ plugins/testsuite/mock/test_string.sp | 36 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/plugins/include/string.inc b/plugins/include/string.inc index ab467504a6..af15d88f2f 100644 --- a/plugins/include/string.inc +++ b/plugins/include/string.inc @@ -204,6 +204,10 @@ native int StringToIntEx(const char[] str, int &result, int nBase=10); /** * Converts a string to a 64-bit integer. * + * @note If the value is outside the range of representable values, + * If the value is positive, this function will return INT64_MAX. + * If the value is negative, this function will return INT64_MIN. + * * @param str String to convert. * @param result Array to store the upper and lower * 32-bits of the 64-bit integer. @@ -215,6 +219,10 @@ native int StringToInt64(const char[] str, int result[2], int nBase=10); /** * Converts a string to an int64 type value. * + * @note If the value is outside the range of representable values, + * If the value is positive, this function will return INT64_MAX. + * If the value is negative, this function will return INT64_MIN. + * * @param str String to convert. * @param nBase Numerical base to use. 10 is default. * @param nConsumed Number of characters consumed. diff --git a/plugins/testsuite/mock/test_string.sp b/plugins/testsuite/mock/test_string.sp index 3f07af3547..5c9ea07205 100644 --- a/plugins/testsuite/mock/test_string.sp +++ b/plugins/testsuite/mock/test_string.sp @@ -105,4 +105,40 @@ void Test_StringToI64() StringToI64("1_234_567", .nConsumed=nConsumed), 1); AssertEq("nConsumed 1_234_567", nConsumed, 1); + + AssertInt64Eq( + "result 9223372036854775807", + StringToI64("9223372036854775807", .nConsumed=nConsumed), + 9223372036854775807); + AssertEq("nConsumed 9223372036854775807", nConsumed, 19); + + AssertInt64Eq( + "result 9223372036854775808", + StringToI64("9223372036854775807", .nConsumed=nConsumed), + 9223372036854775807); + AssertEq("nConsumed 9223372036854775808", nConsumed, 19); + + AssertInt64Eq( + "result 92233720368547758070", + StringToI64("9223372036854775807", .nConsumed=nConsumed), + 9223372036854775807); + AssertEq("nConsumed 92233720368547758070", nConsumed, 19); + + AssertInt64Eq( + "result -9223372036854775808", + StringToI64("-9223372036854775808", .nConsumed=nConsumed), + -9223372036854775807-1); + AssertEq("nConsumed -9223372036854775808", nConsumed, 20); + + AssertInt64Eq( + "result -9223372036854775809", + StringToI64("-9223372036854775808", .nConsumed=nConsumed), + -9223372036854775807-1); + AssertEq("nConsumed -9223372036854775809", nConsumed, 20); + + AssertInt64Eq( + "result -92233720368547758080", + StringToI64("-9223372036854775808", .nConsumed=nConsumed), + -9223372036854775807-1); + AssertEq("nConsumed -92233720368547758080", nConsumed, 20); }