I wrote a C function, expecting "long long" as parameter:
int c_my (long long big_offset);
With SWIG I have an interface to Tcl.
In Tcl I call:
c_my 3000000000
in C (c_my) I got:
big_offset = -1294967296.
There is an bug in SWIG_AsVal_long_SS_long.
The whole discussion is in comp.lang.tcl
Subj:"64 bit integers (long long)"
Here is my (working) hotfix. Replace all the code in SWIG_AsVal_long_SS_long with:
SWIGINTERN int
SWIG_AsVal_long_SS_long SWIG_TCL_DECL_ARGS_2(Tcl_Obj *obj, long long *val)
{
if (Tcl_GetWideIntFromObj(0, obj, val) != TCL_OK) {
return SWIG_TypeError;
}
return SWIG_OK;
}
This is still a bug in 2.0.1.
The problem is in tclprimtypes.swg:
SWIGINTERN int
SWIG_AsVal_dec(long long)(Tcl_Obj *obj, long long *val)
{
long v;
if (Tcl_GetLongFromObj(0,obj, &v) == TCL_OK) {
if (val) *val = v;
return SWIG_OK;
} else {
...
When trying to convert a signed int64 (long long), this code attempts to convert first to a signed int32 (long). If the input is positive, but between 2^31 and 2^32, the conversion attempt will appear to succeed (at least, with the supplied test), but will cast the result to a negative quantity)
The solution is to not do this silly optimization at all, or to require Tcl >=8.4, and change this call to Tcl_GetWideIntFromObj
Patch attached. Please fix this, as spending a day chasing a bug that was identified back in 2009 is no kind of fun.
--- tclprimtypes.swg.orig 2010-12-01 12:11:23.000000000 -0700
+++ tclprimtypes.swg 2010-12-01 12:30:40.000000000 -0700
@@ -133,11 +133,11 @@
SWIGINTERN int
SWIG_AsVal_dec(long long)(Tcl_Obj *obj, long long *val)
{
- long v;
- if (Tcl_GetLongFromObj(0,obj, &v) == TCL_OK) {
- if (val) *val = v;
- return SWIG_OK;
- } else {
+ /*
+ * XXX Bug - remove incorrect optimization.
+ * Optimized fix needs GetWideIntFromObj (tcl >=8.4), so
+ * just remove it for now
+ */
int len = 0;
const char *nptr = Tcl_GetStringFromObj(obj, &len);
if (nptr && len > 0) {
@@ -157,7 +157,6 @@
}
}
}
- }
return SWIG_TypeError;
}
}
Here's a regression test for this issue. Add the following as Examples/test-suite/ll_ofl.i:
%module ll_ofl
%inline {
#include <string.h>
#include <stdio.h>
#include <err.h>
void
llofltest(long long a, char *as)
{
char s[22];
snprintf(s, sizeof(s), "%lld", a);
if (strcmp(s, as))
errx(1, "long long convert fail: %s != %s\n", s, as);
}
}
void llofltest(long long a, char *as);
---
Add the test to Examples/test-suite/tcl/Makefile.in:
--- Makefile.in.orig 2010-12-06 16:08:41.000000000 -0700
+++ Makefile.in 2010-12-06 15:30:36.000000000 -0700
@@ -15,6 +15,7 @@
li_cwstring
C_TEST_CASES += \
+ ll_ofl \
li_cstring \
li_cwstring
And add a test to Examples/test-suite/tcl/ll_ofl_runme.tcl
if [ catch { load ./ll_ofl[info sharedlibextension] ll_ofl} err_msg ] {
puts stderr "Could not load shared object:\n$err_msg"
}
llofltest 300000000 300000000
llofltest 3000000000 3000000000
llofltest 30000000000 30000000000
It looks like this is probably still present (at least the code of
SWIG_AsVal_dec(long long)is still as pre the fix here) but I think for linux at least it only affects 32 bit architectures, as otherwisesizeof(long) == sizeof(long long).Here's a regression test just adding a
_runme.tclfor an existing testcase:I'll find a 32 bit machine to test on.
Tcl 8.4.0 was tagged 2002-09-10 so we could just require that now.
Last edit: Olly Betts 2022-03-22
Reproduced on x86 - the first case above returns a negative value, as expected from the original report.
I've opened a PR with a fix: https://github.com/swig/swig/pull/2331
Merged (https://github.com/swig/swig/commit/06d375cdab63842b223ea5b9091ad0336144e899) - fix should be in SWIG 4.1.0.