Skip to content

Commit f0e183c

Browse files
committed
[[ Bug 19214 ]] Increase useful range of IntSize and IntSSize
This patch ensures that IntSize and IntSSize values are truncated to the maximum exact integer value double will allow - +/- 2^53 rather than the range of an Int32.
1 parent 3d7c724 commit f0e183c

3 files changed

Lines changed: 223 additions & 59 deletions

File tree

docs/lcb/notes/19214.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# LiveCode Builder Standard Library
2+
3+
## Foreign function interface
4+
5+
* When passing a Number to one of the foreign integer types (`LCInt`, `LCUInt`,
6+
`IntSize`, `UIntSize`), an error will be thrown if the value is outside the
7+
range of the requested type.
8+
9+
* The `IntSize` and `UIntSize` types can hold the full 64-bit integer range,
10+
however the maximum magnitude which is supported for converting to and from
11+
Number is 2^53. An error will be thrown for any conversions outside this
12+
range.
13+
14+
# [19214] Increase usable range of IntSize and UIntSize types

libfoundation/src/foundation-foreign.cpp

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#include "foundation-private.h"
2121

22+
#include <limits>
23+
2224
////////////////////////////////////////////////////////////////////////////////
2325

2426
MC_DLLEXPORT_DEF MCTypeInfoRef kMCBoolTypeInfo;
@@ -173,6 +175,15 @@ bool __MCForeignValueCopyDescription(__MCForeignValue *self, MCStringRef& r_desc
173175

174176
////////////////////////////////////////////////////////////////////////////////
175177

178+
static bool
179+
__throw_numeric_overflow(MCTypeInfoRef p_error, MCTypeInfoRef p_type)
180+
{
181+
return MCErrorCreateAndThrow(p_error,
182+
"type", p_type,
183+
"reason", MCSTR("numeric overflow"),
184+
nil);
185+
}
186+
176187
// bool foreign type handlers
177188

178189
static void __bool_finalize(void *)
@@ -322,39 +333,52 @@ __size_import (void *contents,
322333
bool release,
323334
MCValueRef & r_value)
324335
{
325-
size_t t_value = *(size_t *) contents;
336+
size_t t_value = *static_cast<size_t *>(contents);
326337

327-
if (t_value > UINTEGER_MAX)
328-
{
329-
MCErrorCreateAndThrow (kMCForeignImportErrorTypeInfo,
330-
"type", kMCSizeTypeInfo,
331-
"reason", MCSTR("too large for Number representation"),
332-
nil);
333-
return false;
338+
if (t_value <= UINTEGER_MAX)
339+
{
340+
return MCNumberCreateWithUnsignedInteger(uinteger_t(t_value),
341+
reinterpret_cast<MCNumberRef&>(r_value));
342+
}
343+
#ifdef __64_BIT__
344+
else if (t_value <= (1ULL << std::numeric_limits<double>::digits))
345+
{
346+
return MCNumberCreateWithReal(double(t_value),
347+
reinterpret_cast<MCNumberRef&>(r_value));
348+
}
349+
#endif
350+
else
351+
{
352+
return __throw_numeric_overflow(kMCForeignImportErrorTypeInfo,
353+
kMCSizeTypeInfo);
334354
}
335-
336-
return MCNumberCreateWithUnsignedInteger((uinteger_t) t_value,
337-
(MCNumberRef &) r_value);
338355
}
339356

340357
static bool
341358
__ssize_import (void *contents,
342359
bool release,
343360
MCValueRef & r_value)
344361
{
345-
ssize_t t_value = *(ssize_t *) contents;
362+
ssize_t t_value = *static_cast<ssize_t *>(contents);
346363

347-
if (t_value < INTEGER_MIN || t_value > INTEGER_MAX)
348-
{
349-
MCErrorCreateAndThrow (kMCForeignImportErrorTypeInfo,
350-
"type", kMCSizeTypeInfo,
351-
"reason", MCSTR("too large for Number representation"),
352-
nil);
353-
return false;
364+
if (t_value >= INTEGER_MIN && t_value <= INTEGER_MAX)
365+
{
366+
return MCNumberCreateWithInteger(integer_t(t_value),
367+
reinterpret_cast<MCNumberRef&>(r_value));
368+
}
369+
#ifdef __64_BIT__
370+
else if (t_value >= -(1LL << std::numeric_limits<double>::digits) &&
371+
t_value <= (1LL << std::numeric_limits<double>::digits))
372+
{
373+
return MCNumberCreateWithReal(double(t_value),
374+
reinterpret_cast<MCNumberRef&>(r_value));
375+
}
376+
#endif
377+
else
378+
{
379+
return __throw_numeric_overflow(kMCForeignImportErrorTypeInfo,
380+
kMCSSizeTypeInfo);
354381
}
355-
356-
return MCNumberCreateWithInteger((integer_t) t_value,
357-
(MCNumberRef &) r_value);
358382
}
359383

360384
static bool __float_import(void *contents, bool release, MCValueRef& r_value)
@@ -367,47 +391,64 @@ static bool __double_import(void *contents, bool release, MCValueRef& r_value)
367391
return MCNumberCreateWithReal(*(double *)contents, (MCNumberRef&)r_value);
368392
}
369393

370-
static bool __int_export(MCValueRef value, bool release, void *contents)
371-
{
372-
*(integer_t *)contents = MCNumberFetchAsInteger((MCNumberRef)value);
373-
if (release)
374-
MCValueRelease(value);
375-
return true;
376-
}
377-
378394
template <typename T>
379395
static bool
380-
__uint_export (MCValueRef value,
381-
bool release,
382-
void *contents,
383-
MCTypeInfoRef typeinfo)
384-
{
385-
/* Unsigned values can't be negative */
386-
if (0 > MCNumberFetchAsReal ((MCNumberRef) value))
387-
{
388-
MCErrorCreateAndThrow (kMCForeignExportErrorTypeInfo,
389-
"type", typeinfo,
390-
"reason", MCSTR("cannot store negative value in unsigned integer"),
391-
nil);
392-
return false;
393-
}
396+
__any_int_export(MCValueRef value,
397+
bool release,
398+
void *contents,
399+
MCTypeInfoRef typeinfo)
400+
{
401+
// Fetch the number as a double
402+
double t_value =
403+
MCNumberFetchAsReal(static_cast<MCNumberRef>(value));
404+
405+
// First check that the value is within the contiguous integer range
406+
// of doubles. If that succeeds, then check it fits within the target
407+
// integer type.
408+
if (t_value < double(-(1LL << std::numeric_limits<double>::digits)) ||
409+
t_value > double(1LL << std::numeric_limits<double>::digits) ||
410+
t_value < double(std::numeric_limits<T>::min()) ||
411+
t_value > double(std::numeric_limits<T>::max()))
412+
{
413+
return __throw_numeric_overflow(kMCForeignExportErrorTypeInfo,
414+
typeinfo);
415+
}
394416

395-
*(T *)contents = MCNumberFetchAsUnsignedInteger((MCNumberRef)value);
417+
*(T *)contents = T(t_value);
396418

397419
if (release)
398420
MCValueRelease(value);
421+
399422
return true;
400423
}
401424

425+
static bool
426+
__int_export(MCValueRef value, bool release, void *contents)
427+
{
428+
return __any_int_export<integer_t>(value, release, contents, kMCIntTypeInfo);
429+
}
430+
402431
static bool
403432
__uint_export(MCValueRef value, bool release, void *contents)
404433
{
405-
return __uint_export<uinteger_t>(value, release, contents, kMCUIntTypeInfo);
434+
return __any_int_export<uinteger_t>(value, release, contents, kMCUIntTypeInfo);
435+
}
436+
437+
static bool
438+
__size_export(MCValueRef value, bool release, void *contents)
439+
{
440+
return __any_int_export<size_t>(value, release, contents, kMCSizeTypeInfo);
441+
}
442+
443+
static bool
444+
__ssize_export(MCValueRef value, bool release, void *contents)
445+
{
446+
return __any_int_export<ssize_t>(value, release, contents, kMCSSizeTypeInfo);
406447
}
407448

408449
static bool __float_export(MCValueRef value, bool release, void *contents)
409450
{
410-
*(float *)contents = MCNumberFetchAsReal((MCNumberRef)value);
451+
*(float *)contents = float(MCNumberFetchAsReal((MCNumberRef)value));
411452
if (release)
412453
MCValueRelease(value);
413454
return true;
@@ -421,18 +462,6 @@ static bool __double_export(MCValueRef value, bool release, void *contents)
421462
return true;
422463
}
423464

424-
static bool
425-
__size_export(MCValueRef value, bool release, void *contents)
426-
{
427-
return __uint_export<size_t>(value, release, contents, kMCSizeTypeInfo);
428-
}
429-
430-
static bool
431-
__ssize_export(MCValueRef value, bool release, void *contents)
432-
{
433-
return __uint_export<ssize_t>(value, release, contents, kMCSSizeTypeInfo);
434-
}
435-
436465
static bool
437466
__bool_describe (void *contents,
438467
MCStringRef & r_string)

tests/lcb/stdlib/foreign.lcb

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module com.livecode.foreign.tests
2020
use com.livecode.foreign
2121
use com.livecode.__INTERNAL._testlib
2222

23+
----------
24+
2325
handler TestZStringNative_Null()
2426
variable tList as ZStringNative
2527
put "\u{0}" into tList
@@ -49,4 +51,123 @@ public handler TestZStringUTF16()
4951
MCUnitTestHandlerThrowsBroken(TestZStringUTF16_Null, "ZStringUTF16 (nulls)", "bug 14829")
5052
end handler
5153

54+
----------
55+
56+
private foreign handler printf_int(in pFormat as ZStringNative, in pValue as LCInt) returns nothing binds to "printf"
57+
private foreign handler printf_uint(in pFormat as ZStringNative, in pValue as LCUInt) returns nothing binds to "printf"
58+
private foreign handler printf_intsize(in pFormat as ZStringNative, in pValue as IntSize) returns nothing binds to "printf"
59+
private foreign handler printf_uintsize(in pFormat as ZStringNative, in pValue as UIntSize) returns nothing binds to "printf"
60+
61+
foreign handler MCHandlerTryToInvokeWithList(in Handler as any, inout Arguments as optional List, out Result as optional any) returns optional any binds to "<builtin>"
62+
63+
private handler __Is64Bit() returns nothing
64+
variable tSize as UIntSize
65+
put 4294967296.0 into tSize
66+
unsafe
67+
printf_uintsize("", tSize)
68+
end unsafe
69+
end handler
70+
71+
private handler Is64Bit()
72+
variable tResult as optional any
73+
variable tArguments as optional List
74+
variable tError as optional any
75+
put [] into tArguments
76+
unsafe
77+
put MCHandlerTryToInvokeWithList(__Is64Bit, tArguments, tResult) into tError
78+
end unsafe
79+
return tError is nothing
80+
end handler
81+
82+
handler TestIntRange_Min()
83+
variable tInt as LCInt
84+
put -2147483649.0 into tInt
85+
unsafe
86+
printf_int("", tInt)
87+
end unsafe
88+
end handler
89+
90+
handler TestIntRange_Max()
91+
variable tInt as LCInt
92+
put 2147483648.0 into tInt
93+
unsafe
94+
printf_int("", tInt)
95+
end unsafe
96+
end handler
97+
98+
handler TestUIntRange_Min()
99+
variable tUInt as LCUInt
100+
put -1.0 into tUInt
101+
unsafe
102+
printf_uint("", tUInt)
103+
end unsafe
104+
end handler
105+
106+
handler TestUIntRange_Max()
107+
variable tUInt as LCUInt
108+
put 4294967296.0 into tUInt
109+
unsafe
110+
printf_uint("", tUInt)
111+
end unsafe
112+
end handler
113+
114+
handler TestIntSizeRange_Min()
115+
variable tInt as IntSize
116+
if Is64Bit() then
117+
put -9007199254740994.0 into tInt
118+
else
119+
put -2147483649.0 into tInt
120+
end if
121+
unsafe
122+
printf_intsize("", tInt)
123+
end unsafe
124+
end handler
125+
126+
handler TestIntSizeRange_Max()
127+
variable tInt as IntSize
128+
if Is64Bit() then
129+
put 9007199254740994.0 into tInt
130+
else
131+
put 2147483648.0 into tInt
132+
end if
133+
unsafe
134+
printf_intsize("", tInt)
135+
end unsafe
136+
end handler
137+
138+
handler TestUIntSizeRange_Min()
139+
variable tUInt as UIntSize
140+
put -1.0 into tUInt
141+
unsafe
142+
printf_uintsize("", tUInt)
143+
end unsafe
144+
end handler
145+
146+
handler TestUIntSizeRange_Max()
147+
variable tUInt as UIntSize
148+
if Is64Bit() then
149+
test diagnostic "64-bit"
150+
put 9007199254740994.0 into tUInt
151+
else
152+
put 4294967296.0 into tUInt
153+
end if
154+
unsafe
155+
printf_uintsize("", tUInt)
156+
end unsafe
157+
end handler
158+
159+
public handler TestForeignIntRanges()
160+
MCUnitTestHandlerThrows(TestIntRange_Min, "LCInt minimum value")
161+
MCUnitTestHandlerThrows(TestIntRange_Max, "LCInt maximum value")
162+
163+
MCUnitTestHandlerThrows(TestUIntRange_Min, "LCUInt minimum value")
164+
MCUnitTestHandlerThrows(TestUIntRange_Max, "LCUInt maximum value")
165+
166+
MCUnitTestHandlerThrows(TestIntSizeRange_Min, "IntSize minimum value")
167+
MCUnitTestHandlerThrows(TestIntSizeRange_Max, "IntSize maximum value")
168+
169+
MCUnitTestHandlerThrows(TestUIntSizeRange_Min, "UIntSize minimum value")
170+
MCUnitTestHandlerThrows(TestUIntSizeRange_Max, "UIntSize maximum value")
171+
end handler
172+
52173
end module

0 commit comments

Comments
 (0)