Skip to content

Commit f6fbf9b

Browse files
authored
Use __index__ if needed to pack integer values in struct (#1383)
1 parent 921c68e commit f6fbf9b

3 files changed

Lines changed: 116 additions & 103 deletions

File tree

Src/IronPython.Modules/_struct.cs

Lines changed: 71 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,12 @@ public void __init__(CodeContext/*!*/ context, object fmt) {
141141
break;
142142
case FormatType.UnsignedInt:
143143
for (int j = 0; j < curFormat.Count; j++) {
144-
WriteUInt(res, _isLittleEndian, GetULongValue(context, curObj++, values, "unsigned int"));
144+
WriteUInt(res, _isLittleEndian, GetULongValue(context, curObj++, values));
145145
}
146146
break;
147147
case FormatType.UnsignedLong:
148148
for (int j = 0; j < curFormat.Count; j++) {
149-
WriteUInt(res, _isLittleEndian, GetULongValue(context, curObj++, values, "unsigned long"));
149+
WriteUInt(res, _isLittleEndian, GetULongValue(context, curObj++, values));
150150
}
151151
break;
152152
case FormatType.Pointer:
@@ -176,7 +176,7 @@ public void __init__(CodeContext/*!*/ context, object fmt) {
176176
break;
177177
case FormatType.LongLong:
178178
for (int j = 0; j < curFormat.Count; j++) {
179-
WriteLong(res, _isLittleEndian, GetLongValue(context, curObj++, values));
179+
WriteLong(res, _isLittleEndian, GetLongLongValue(context, curObj++, values));
180180
}
181181
break;
182182
case FormatType.UnsignedLongLong:
@@ -1066,110 +1066,77 @@ internal static byte GetCharValue(CodeContext/*!*/ context, int index, object[]
10661066
}
10671067

10681068
internal static sbyte GetSByteValue(CodeContext/*!*/ context, int index, object[] args) {
1069-
object val = GetValue(context, index, args);
1070-
if (Converter.TryConvertToSByte(val, out sbyte res)) return res;
1071-
throw Error(context, "expected sbyte value got " + val.ToString());
1069+
BigInteger val = GetIntegerValue(context, index, args);
1070+
if (!val.AsInt32(out int res))
1071+
throw Error(context, "argument out of range");
1072+
CheckRange(context, res, sbyte.MinValue, sbyte.MaxValue, "byte");
1073+
return (sbyte)res;
10721074
}
10731075

10741076
internal static byte GetByteValue(CodeContext/*!*/ context, int index, object[] args) {
1075-
object val = GetValue(context, index, args);
1076-
if (Converter.TryConvertToByte(val, out byte res)) return res;
1077-
throw Error(context, "expected byte value got " + val.ToString());
1077+
BigInteger val = GetIntegerValue(context, index, args);
1078+
if (!val.AsInt32(out int res))
1079+
throw Error(context, "argument out of range");
1080+
CheckRange(context, res, byte.MinValue, byte.MaxValue, "ubyte");
1081+
return (byte)res;
10781082
}
10791083

10801084
internal static short GetShortValue(CodeContext/*!*/ context, int index, object[] args) {
1081-
object val = GetValue(context, index, args);
1082-
if (Converter.TryConvertToInt16(val, out short res)) return res;
1083-
throw Error(context, "expected short value");
1085+
BigInteger val = GetIntegerValue(context, index, args);
1086+
if (!val.AsInt32(out int res))
1087+
throw Error(context, "argument out of range");
1088+
CheckRange(context, res, short.MinValue, short.MaxValue, "short");
1089+
return (short)res;
10841090
}
10851091

10861092
internal static ushort GetUShortValue(CodeContext/*!*/ context, int index, object[] args) {
1087-
object val = GetValue(context, index, args);
1088-
if (Converter.TryConvertToUInt16(val, out ushort res)) return res;
1089-
throw Error(context, "expected ushort value");
1093+
BigInteger val = GetIntegerValue(context, index, args);
1094+
if (!val.AsInt32(out int res))
1095+
throw Error(context, "argument out of range");
1096+
CheckRange(context, res, ushort.MinValue, ushort.MaxValue, "ushort");
1097+
return (ushort)res;
10901098
}
10911099

10921100
internal static int GetIntValue(CodeContext/*!*/ context, int index, object[] args) {
1093-
object val = GetValue(context, index, args);
1094-
if (Converter.TryConvertToInt32(val, out int res)) return res;
1095-
throw Error(context, "expected int value");
1096-
}
1097-
1098-
internal static uint GetULongValue(CodeContext/*!*/ context, int index, object[] args, string type) {
1099-
object val = GetValue(context, index, args);
1100-
if (val is int i) {
1101-
CheckRange(context, i, type);
1102-
return (uint)i;
1103-
} else if (val is BigInteger bi) {
1104-
CheckRange(context, bi, type);
1105-
return (uint)bi;
1106-
} else if (val is Extensible<BigInteger> ebi) {
1107-
CheckRange(context, ebi.Value, type);
1108-
return (uint)ebi.Value;
1109-
} else {
1110-
if (PythonTypeOps.TryInvokeUnaryOperator(DefaultContext.Default, val, "__int__", out object objres)) {
1111-
if (objres is int oi) {
1112-
CheckRange(context, oi, type);
1113-
return (uint)oi;
1114-
}
1115-
}
1116-
1117-
if (Converter.TryConvertToUInt32(val, out uint res)) {
1118-
return res;
1119-
}
1120-
}
1121-
1122-
throw Error(context, "cannot convert argument to integer");
1123-
}
1124-
1125-
private static void CheckRange(CodeContext context, int val, string type) {
1126-
if (val < 0) {
1127-
OutOfRange(context, type);
1128-
}
1129-
}
1130-
1131-
private static void CheckRange(CodeContext context, BigInteger bi, string type) {
1132-
if (bi < 0 || bi > 4294967295) {
1133-
OutOfRange(context, type);
1134-
}
1101+
BigInteger val = GetIntegerValue(context, index, args);
1102+
if (!val.AsInt32(out int res))
1103+
throw Error(context, "argument out of range");
1104+
return res;
11351105
}
11361106

1137-
private static void OutOfRange(CodeContext context, string type) {
1138-
throw Error(context, $"integer out of range for '{(type == "unsigned long" ? "L" : "I")}' format code");
1107+
internal static uint GetULongValue(CodeContext/*!*/ context, int index, object[] args) {
1108+
BigInteger val = GetIntegerValue(context, index, args);
1109+
if (!val.AsUInt32(out uint res))
1110+
throw Error(context, "argument out of range");
1111+
return res;
11391112
}
11401113

11411114
internal static int GetSignedSizeT(CodeContext/*!*/ context, int index, object[] args) {
1142-
object val = GetValue(context, index, args);
1143-
if (Converter.TryConvertToInt32(val, out int res)) return res;
1144-
throw Error(context, "expected signed size_t(aka ssize_t) value");
1115+
return GetIntValue(context, index, args);
11451116
}
11461117

11471118
internal static uint GetSizeT(CodeContext/*!*/ context, int index, object[] args) {
1148-
object val = GetValue(context, index, args);
1149-
if (Converter.TryConvertToUInt32(val, out uint res)) return res;
1150-
throw Error(context, "expected size_t value");
1119+
return GetULongValue(context, index, args);
11511120
}
11521121

11531122
internal static ulong GetPointer(CodeContext/*!*/ context, int index, object[] args) {
1154-
object val = GetValue(context, index, args);
1155-
if (Converter.TryConvertToBigInteger(val, out BigInteger bi)) {
1156-
if (UIntPtr.Size == 4) {
1157-
if (bi < 0) {
1158-
bi += new BigInteger(UInt32.MaxValue) + 1;
1159-
}
1160-
if (Converter.TryConvertToUInt32(bi, out uint res)) {
1161-
return res;
1162-
}
1163-
} else {
1164-
if (bi < 0) {
1165-
bi += new BigInteger(UInt64.MaxValue) + 1;
1166-
}
1167-
if (Converter.TryConvertToUInt64(bi, out ulong res)) {
1168-
return res;
1169-
}
1123+
BigInteger val = GetIntegerValue(context, index, args);
1124+
if (UIntPtr.Size == 4) {
1125+
if (val < 0) {
1126+
val += new BigInteger(UInt32.MaxValue) + 1;
1127+
}
1128+
if (Converter.TryConvertToUInt32(val, out uint res)) {
1129+
return res;
1130+
}
1131+
} else {
1132+
if (val < 0) {
1133+
val += new BigInteger(UInt64.MaxValue) + 1;
1134+
}
1135+
if (Converter.TryConvertToUInt64(val, out ulong res)) {
1136+
return res;
11701137
}
11711138
}
1172-
throw Error(context, "expected pointer value");
1139+
throw Error(context, "int too large to convert");
11731140
}
11741141

11751142
internal static IntPtr GetSignedNetPointer(CodeContext/*!*/ context, int index, object[] args) {
@@ -1192,16 +1159,18 @@ internal static UIntPtr GetUnsignedNetPointer(CodeContext/*!*/ context, int inde
11921159
throw Error(context, "expected .NET pointer value");
11931160
}
11941161

1195-
internal static long GetLongValue(CodeContext/*!*/ context, int index, object[] args) {
1196-
object val = GetValue(context, index, args);
1197-
if (Converter.TryConvertToInt64(val, out long res)) return res;
1198-
throw Error(context, "expected long value");
1162+
internal static long GetLongLongValue(CodeContext/*!*/ context, int index, object[] args) {
1163+
BigInteger val = GetIntegerValue(context, index, args);
1164+
if (!val.AsInt64(out long res))
1165+
throw Error(context, "argument out of range");
1166+
return res;
11991167
}
12001168

12011169
internal static ulong GetULongLongValue(CodeContext/*!*/ context, int index, object[] args) {
1202-
object val = GetValue(context, index, args);
1203-
if (Converter.TryConvertToUInt64(val, out ulong res)) return res;
1204-
throw Error(context, "expected ulong value");
1170+
BigInteger val = GetIntegerValue(context, index, args);
1171+
if (!val.AsUInt64(out ulong res))
1172+
throw Error(context, "argument out of range");
1173+
return res;
12051174
}
12061175

12071176
internal static double GetDoubleValue(CodeContext/*!*/ context, int index, object[] args) {
@@ -1220,6 +1189,19 @@ internal static object GetValue(CodeContext/*!*/ context, int index, object[] ar
12201189
if (index >= args.Length) throw Error(context, "not enough arguments");
12211190
return args[index];
12221191
}
1192+
1193+
private static BigInteger GetIntegerValue(CodeContext/*!*/ context, int index, object[] args) {
1194+
object val = GetValue(context, index, args);
1195+
if (PythonOps.TryToIndex(val, out BigInteger res)) return res;
1196+
throw Error(context, "required argument is not an integer");
1197+
}
1198+
1199+
private static void CheckRange(CodeContext context, int val, int min, int max, string format) {
1200+
if (val < min || val > max) {
1201+
throw Error(context, $"{format} format requires {min} <= number <= {max}");
1202+
}
1203+
}
1204+
12231205
#endregion
12241206

12251207
#region Data creater helpers

Tests/test_regressions.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def test_re_paren_in_char_list_cp20191(self):
213213

214214

215215
def test_struct_uint_bad_value_cp20039(self):
216+
'''Also https://github.com/IronLanguages/ironpython3/issues/1381'''
216217
class x(object):
217218
def __init__(self, value):
218219
self.value = value
@@ -227,19 +228,35 @@ def __int__(self):
227228
global andCalled
228229
andCalled = False
229230

230-
self.assertRaisesRegex(_struct.error, "integer out of range for 'L' format code" if is_cli else "argument out of range",
231-
_struct.Struct('L').pack, 4294967296)
232-
self.assertRaisesRegex(_struct.error, "integer out of range for 'L' format code" if is_cli else "argument out of range",
233-
_struct.Struct('L').pack, -1)
234-
self.assertRaisesRegex(Exception, "foo" if is_cli else "required argument is not an integer", _struct.Struct('L').pack, x(0))
235-
self.assertRaisesRegex(Exception, "foo" if is_cli else "required argument is not an integer", _struct.Struct('L').pack, x(-1))
231+
for code in ['L', 'I']:
232+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct(code).pack, 0x100000000)
233+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct(code).pack, -1)
236234

237-
self.assertRaisesRegex(_struct.error, "integer out of range for 'I' format code" if is_cli else "argument out of range",
238-
_struct.Struct('I').pack, 4294967296)
239-
self.assertRaisesRegex(_struct.error, "integer out of range for 'I' format code" if is_cli else "argument out of range",
240-
_struct.Struct('I').pack, -1)
241-
self.assertRaisesRegex(Exception, "foo" if is_cli else "required argument is not an integer", _struct.Struct('I').pack, x(0))
242-
self.assertRaisesRegex(Exception, "foo" if is_cli else "required argument is not an integer", _struct.Struct('I').pack, x(-1))
235+
for code in ['l', 'i', 'h', 'H', 'B', 'b']:
236+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct(code).pack, 0x80000000)
237+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct(code).pack, -0x80000001)
238+
239+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct('Q').pack, 0x10000000000000000)
240+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct('Q').pack, -1)
241+
242+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct('q').pack, 0x8000000000000000)
243+
self.assertRaisesRegex(_struct.error, "argument out of range", _struct.Struct('q').pack, -0x8000000000000001)
244+
245+
self.assertRaisesRegex(_struct.error, r"ushort format requires 0 <= number .*", _struct.Struct('H').pack, 0x10000)
246+
self.assertRaisesRegex(_struct.error, r"ushort format requires 0 <= number .*", _struct.Struct('H').pack, -1)
247+
248+
self.assertRaisesRegex(_struct.error, r"short format requires .* <= number .*", _struct.Struct('h').pack, 0x8000)
249+
self.assertRaisesRegex(_struct.error, r"short format requires .* <= number .*", _struct.Struct('h').pack, -0x8001)
250+
251+
self.assertRaisesRegex(_struct.error, r"ubyte format requires 0 <= number .*", _struct.Struct('B').pack, 0x100)
252+
self.assertRaisesRegex(_struct.error, r"ubyte format requires 0 <= number .*", _struct.Struct('B').pack, -1)
253+
254+
self.assertRaisesRegex(_struct.error, r"byte format requires .* <= number .*", _struct.Struct('b').pack, 0x80)
255+
self.assertRaisesRegex(_struct.error, r"byte format requires .* <= number .*", _struct.Struct('b').pack, -0x81)
256+
257+
for code in ['b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'n', 'N', 'P']:
258+
self.assertRaisesRegex(_struct.error, "required argument is not an integer", _struct.Struct(code).pack, x(0))
259+
self.assertRaisesRegex(_struct.error, "required argument is not an integer", _struct.Struct(code).pack, x(-1))
243260

244261
# __and__ was called in Python2.6 check that this is no longer True
245262
self.assertTrue(not andCalled)
@@ -1637,5 +1654,19 @@ class MyClass(object):
16371654

16381655
self.assertEqual(MyClass.x, 2)
16391656

1657+
def test_ipy3_gh1381(self):
1658+
class x:
1659+
def __init__(self, value):
1660+
self.value = value
1661+
def __index__(self):
1662+
return self.value
1663+
1664+
import _struct, sys
1665+
1666+
idx = 0 if sys.byteorder == 'little' else -1
1667+
1668+
for code in ['b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'n', 'N', 'P']:
1669+
self.assertEqual(_struct.Struct(code).pack(x(42))[idx], 42)
1670+
16401671

16411672
run_test(__name__)

Tests/test_struct_stdlib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
def load_tests(loader, standard_tests, pattern):
1717
if sys.implementation.name == 'ironpython':
1818
suite = unittest.TestSuite()
19-
suite.addTest(unittest.expectedFailure(test.test_struct.StructTest('test_1530559'))) # TODO: figure out
19+
suite.addTest(test.test_struct.StructTest('test_1530559'))
2020
suite.addTest(unittest.expectedFailure(test.test_struct.StructTest('test_705836'))) # TODO: figure out
2121
suite.addTest(test.test_struct.StructTest('test_Struct_reinitialization'))
2222
suite.addTest(test.test_struct.StructTest('test__sizeof__'))
2323
suite.addTest(unittest.expectedFailure(test.test_struct.StructTest('test_bool'))) # TODO: figure out
2424
suite.addTest(unittest.expectedFailure(test.test_struct.StructTest('test_calcsize'))) # TODO: figure out
2525
suite.addTest(test.test_struct.StructTest('test_consistence'))
2626
suite.addTest(unittest.expectedFailure(test.test_struct.StructTest('test_count_overflow'))) # TODO: figure out
27-
suite.addTest(unittest.expectedFailure(test.test_struct.StructTest('test_integers'))) # TODO: figure out
27+
suite.addTest(test.test_struct.StructTest('test_integers'))
2828
suite.addTest(test.test_struct.StructTest('test_isbigendian'))
2929
suite.addTest(test.test_struct.StructTest('test_nN_code'))
3030
suite.addTest(test.test_struct.StructTest('test_new_features'))

0 commit comments

Comments
 (0)