Skip to content

Commit 4360b97

Browse files
committed
wip: datetime: Replace char arrays with packed structures
Structures provides same layout but allows compiler to check for invalid access. Downside is that all fields are in native byte order and needs to be converted to big-endian in pickle/unpickle.
1 parent 3987050 commit 4360b97

File tree

2 files changed

+68
-56
lines changed

2 files changed

+68
-56
lines changed

Include/datetime.h

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,40 @@ extern "C" {
2121
* 10
2222
*/
2323

24+
typedef struct
25+
{
26+
unsigned short year;
27+
unsigned char month;
28+
unsigned char day;
29+
} __attribute__((packed)) _PyDateTime_DateData;
30+
2431
/* # of bytes for year, month, and day. */
25-
#define _PyDateTime_DATE_DATASIZE 4
32+
#define _PyDateTime_DATE_DATASIZE sizeof(_PyDateTime_DateData)
33+
34+
typedef struct
35+
{
36+
unsigned char hour;
37+
unsigned char minute;
38+
unsigned int second : 8;
39+
unsigned int microsecond : 24;
40+
} __attribute__((packed)) _PyDateTime_TimeData;
2641

2742
/* # of bytes for hour, minute, second, and usecond. */
28-
#define _PyDateTime_TIME_DATASIZE 6
43+
#define _PyDateTime_TIME_DATASIZE sizeof(_PyDateTime_TimeData)
44+
45+
typedef struct
46+
{
47+
unsigned short year;
48+
unsigned char month;
49+
unsigned char day;
50+
unsigned char hour;
51+
unsigned char minute;
52+
unsigned int second : 8;
53+
unsigned int microsecond : 24;
54+
} __attribute__((packed)) _PyDateTime_DateTimeData;
2955

3056
/* # of bytes for year, month, day, hour, minute, second, and usecond. */
31-
#define _PyDateTime_DATETIME_DATASIZE 10
57+
#define _PyDateTime_DATETIME_DATASIZE sizeof(_PyDateTime_DateTimeData)
3258

3359

3460
typedef struct
@@ -71,7 +97,7 @@ typedef struct
7197
*/
7298
#define _PyDateTime_TIMEHEAD \
7399
_PyTZINFO_HEAD \
74-
unsigned char data[_PyDateTime_TIME_DATASIZE];
100+
_PyDateTime_TimeData data;
75101

76102
typedef struct
77103
{
@@ -94,12 +120,12 @@ typedef struct
94120
typedef struct
95121
{
96122
_PyTZINFO_HEAD
97-
unsigned char data[_PyDateTime_DATE_DATASIZE];
123+
_PyDateTime_DateData data;
98124
} PyDateTime_Date;
99125

100126
#define _PyDateTime_DATETIMEHEAD \
101127
_PyTZINFO_HEAD \
102-
unsigned char data[_PyDateTime_DATETIME_DATASIZE];
128+
_PyDateTime_DateTimeData data;
103129

104130
typedef struct
105131
{
@@ -119,30 +145,23 @@ typedef struct
119145
// o is a pointer to a time or a datetime object.
120146
#define _PyDateTime_HAS_TZINFO(o) (((_PyDateTime_BaseTZInfo *)(o))->hastzinfo)
121147

122-
#define PyDateTime_GET_YEAR(o) ((((PyDateTime_Date*)(o))->data[0] << 8) | \
123-
((PyDateTime_Date*)(o))->data[1])
124-
#define PyDateTime_GET_MONTH(o) (((PyDateTime_Date*)(o))->data[2])
125-
#define PyDateTime_GET_DAY(o) (((PyDateTime_Date*)(o))->data[3])
126-
127-
#define PyDateTime_DATE_GET_HOUR(o) (((PyDateTime_DateTime*)(o))->data[4])
128-
#define PyDateTime_DATE_GET_MINUTE(o) (((PyDateTime_DateTime*)(o))->data[5])
129-
#define PyDateTime_DATE_GET_SECOND(o) (((PyDateTime_DateTime*)(o))->data[6])
130-
#define PyDateTime_DATE_GET_MICROSECOND(o) \
131-
((((PyDateTime_DateTime*)(o))->data[7] << 16) | \
132-
(((PyDateTime_DateTime*)(o))->data[8] << 8) | \
133-
((PyDateTime_DateTime*)(o))->data[9])
148+
#define PyDateTime_GET_YEAR(o) (((PyDateTime_Date*)(o))->data.year)
149+
#define PyDateTime_GET_MONTH(o) (((PyDateTime_Date*)(o))->data.month)
150+
#define PyDateTime_GET_DAY(o) (((PyDateTime_Date*)(o))->data.day)
151+
152+
#define PyDateTime_DATE_GET_HOUR(o) (((PyDateTime_DateTime*)(o))->data.hour)
153+
#define PyDateTime_DATE_GET_MINUTE(o) (((PyDateTime_DateTime*)(o))->data.minute)
154+
#define PyDateTime_DATE_GET_SECOND(o) (((PyDateTime_DateTime*)(o))->data.second)
155+
#define PyDateTime_DATE_GET_MICROSECOND(o) (((PyDateTime_DateTime*)(o))->data.microsecond)
134156
#define PyDateTime_DATE_GET_FOLD(o) (((PyDateTime_DateTime*)(o))->fold)
135157
#define PyDateTime_DATE_GET_TZINFO(o) (_PyDateTime_HAS_TZINFO((o)) ? \
136158
((PyDateTime_DateTime *)(o))->tzinfo : Py_None)
137159

138160
/* Apply for time instances. */
139-
#define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)(o))->data[0])
140-
#define PyDateTime_TIME_GET_MINUTE(o) (((PyDateTime_Time*)(o))->data[1])
141-
#define PyDateTime_TIME_GET_SECOND(o) (((PyDateTime_Time*)(o))->data[2])
142-
#define PyDateTime_TIME_GET_MICROSECOND(o) \
143-
((((PyDateTime_Time*)(o))->data[3] << 16) | \
144-
(((PyDateTime_Time*)(o))->data[4] << 8) | \
145-
((PyDateTime_Time*)(o))->data[5])
161+
#define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)(o))->data.hour)
162+
#define PyDateTime_TIME_GET_MINUTE(o) (((PyDateTime_Time*)(o))->data.minute)
163+
#define PyDateTime_TIME_GET_SECOND(o) (((PyDateTime_Time*)(o))->data.second)
164+
#define PyDateTime_TIME_GET_MICROSECOND(o) (((PyDateTime_Time*)(o))->data.microsecond)
146165
#define PyDateTime_TIME_GET_FOLD(o) (((PyDateTime_Time*)(o))->fold)
147166
#define PyDateTime_TIME_GET_TZINFO(o) (_PyDateTime_HAS_TZINFO(o) ? \
148167
((PyDateTime_Time *)(o))->tzinfo : Py_None)

Modules/_datetimemodule.c

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -277,19 +277,15 @@ clear_current_module(PyInterpreterState *interp, PyObject *expected)
277277
#define DATE_GET_FOLD PyDateTime_DATE_GET_FOLD
278278

279279
/* Date accessors for date and datetime. */
280-
#define SET_YEAR(o, v) (((o)->data[0] = ((v) & 0xff00) >> 8), \
281-
((o)->data[1] = ((v) & 0x00ff)))
280+
#define SET_YEAR(o, v) (PyDateTime_GET_YEAR(o) = (v))
282281
#define SET_MONTH(o, v) (PyDateTime_GET_MONTH(o) = (v))
283282
#define SET_DAY(o, v) (PyDateTime_GET_DAY(o) = (v))
284283

285284
/* Date/Time accessors for datetime. */
286285
#define DATE_SET_HOUR(o, v) (PyDateTime_DATE_GET_HOUR(o) = (v))
287286
#define DATE_SET_MINUTE(o, v) (PyDateTime_DATE_GET_MINUTE(o) = (v))
288287
#define DATE_SET_SECOND(o, v) (PyDateTime_DATE_GET_SECOND(o) = (v))
289-
#define DATE_SET_MICROSECOND(o, v) \
290-
(((o)->data[7] = ((v) & 0xff0000) >> 16), \
291-
((o)->data[8] = ((v) & 0x00ff00) >> 8), \
292-
((o)->data[9] = ((v) & 0x0000ff)))
288+
#define DATE_SET_MICROSECOND(o, v) (PyDateTime_DATE_GET_MICROSECOND(o) = (v))
293289
#define DATE_SET_FOLD(o, v) (PyDateTime_DATE_GET_FOLD(o) = (v))
294290

295291
/* Time accessors for time. */
@@ -301,10 +297,7 @@ clear_current_module(PyInterpreterState *interp, PyObject *expected)
301297
#define TIME_SET_HOUR(o, v) (PyDateTime_TIME_GET_HOUR(o) = (v))
302298
#define TIME_SET_MINUTE(o, v) (PyDateTime_TIME_GET_MINUTE(o) = (v))
303299
#define TIME_SET_SECOND(o, v) (PyDateTime_TIME_GET_SECOND(o) = (v))
304-
#define TIME_SET_MICROSECOND(o, v) \
305-
(((o)->data[3] = ((v) & 0xff0000) >> 16), \
306-
((o)->data[4] = ((v) & 0x00ff00) >> 8), \
307-
((o)->data[5] = ((v) & 0x0000ff)))
300+
#define TIME_SET_MICROSECOND(o, v) (PyDateTime_TIME_GET_MICROSECOND(o) = (v))
308301
#define TIME_SET_FOLD(o, v) (PyDateTime_TIME_GET_FOLD(o) = (v))
309302

310303
/* Delta accessors for timedelta. */
@@ -3197,7 +3190,7 @@ date_from_pickle(PyTypeObject *type, PyObject *state)
31973190
me = (PyDateTime_Date *) (type->tp_alloc(type, 0));
31983191
if (me != NULL) {
31993192
const char *pdata = PyBytes_AS_STRING(state);
3200-
memcpy(me->data, pdata, _PyDateTime_DATE_DATASIZE);
3193+
memcpy(&me->data, pdata, _PyDateTime_DATE_DATASIZE);
32013194
me->hashcode = -1;
32023195
}
32033196
return (PyObject *)me;
@@ -3893,8 +3886,8 @@ date_richcompare(PyObject *self, PyObject *other, int op)
38933886
* classes.
38943887
*/
38953888
if (PyDate_Check(other) && !PyDateTime_Check(other)) {
3896-
int diff = memcmp(((PyDateTime_Date *)self)->data,
3897-
((PyDateTime_Date *)other)->data,
3889+
int diff = memcmp(&((PyDateTime_Date *)self)->data,
3890+
&((PyDateTime_Date *)other)->data,
38983891
_PyDateTime_DATE_DATASIZE);
38993892
return diff_to_bool(diff, op);
39003893
}
@@ -3945,7 +3938,7 @@ date_hash(PyObject *op)
39453938
Py_hash_t hash = FT_ATOMIC_LOAD_SSIZE_RELAXED(self->hashcode);
39463939
if (hash == -1) {
39473940
hash = generic_hash(
3948-
(unsigned char *)self->data, _PyDateTime_DATE_DATASIZE);
3941+
(unsigned char *)&self->data, _PyDateTime_DATE_DATASIZE);
39493942
FT_ATOMIC_STORE_SSIZE_RELAXED(self->hashcode, hash);
39503943
}
39513944

@@ -3973,7 +3966,7 @@ static PyObject *
39733966
date_getstate(PyDateTime_Date *self)
39743967
{
39753968
PyObject* field;
3976-
field = PyBytes_FromStringAndSize((char*)self->data,
3969+
field = PyBytes_FromStringAndSize((char*)&self->data,
39773970
_PyDateTime_DATE_DATASIZE);
39783971
return Py_BuildValue("(N)", field);
39793972
}
@@ -4658,14 +4651,14 @@ time_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo)
46584651
if (me != NULL) {
46594652
const char *pdata = PyBytes_AS_STRING(state);
46604653

4661-
memcpy(me->data, pdata, _PyDateTime_TIME_DATASIZE);
4654+
memcpy(&me->data, pdata, _PyDateTime_TIME_DATASIZE);
46624655
me->hashcode = -1;
46634656
me->hastzinfo = aware;
46644657
if (aware) {
46654658
me->tzinfo = Py_NewRef(tzinfo);
46664659
}
46674660
if (pdata[0] & (1 << 7)) {
4668-
me->data[0] -= 128;
4661+
GET_YEAR(me) &= 0x7f;
46694662
me->fold = 1;
46704663
}
46714664
else {
@@ -5002,8 +4995,8 @@ time_richcompare(PyObject *self, PyObject *other, int op)
50024995
Py_RETURN_NOTIMPLEMENTED;
50034996

50044997
if (GET_TIME_TZINFO(self) == GET_TIME_TZINFO(other)) {
5005-
diff = memcmp(((PyDateTime_Time *)self)->data,
5006-
((PyDateTime_Time *)other)->data,
4998+
diff = memcmp(&((PyDateTime_Time *)self)->data,
4999+
&((PyDateTime_Time *)other)->data,
50075000
_PyDateTime_TIME_DATASIZE);
50085001
return diff_to_bool(diff, op);
50095002
}
@@ -5020,8 +5013,8 @@ time_richcompare(PyObject *self, PyObject *other, int op)
50205013
if ((offset1 == offset2) ||
50215014
(PyDelta_Check(offset1) && PyDelta_Check(offset2) &&
50225015
delta_cmp(offset1, offset2) == 0)) {
5023-
diff = memcmp(((PyDateTime_Time *)self)->data,
5024-
((PyDateTime_Time *)other)->data,
5016+
diff = memcmp(&((PyDateTime_Time *)self)->data,
5017+
&((PyDateTime_Time *)other)->data,
50255018
_PyDateTime_TIME_DATASIZE);
50265019
result = diff_to_bool(diff, op);
50275020
}
@@ -5091,7 +5084,7 @@ time_hash(PyObject *op)
50915084
/* Reduce this to a hash of another object. */
50925085
if (offset == Py_None) {
50935086
hash = generic_hash(
5094-
(unsigned char *)self->data, _PyDateTime_TIME_DATASIZE);
5087+
(unsigned char *)&self->data, _PyDateTime_TIME_DATASIZE);
50955088
FT_ATOMIC_STORE_SSIZE_RELAXED(self->hashcode, hash);
50965089
} else {
50975090
PyObject *temp1, *temp2;
@@ -5239,7 +5232,7 @@ time_getstate(PyDateTime_Time *self, int proto)
52395232
PyObject *basestate;
52405233
PyObject *result = NULL;
52415234

5242-
basestate = PyBytes_FromStringAndSize((char *)self->data,
5235+
basestate = PyBytes_FromStringAndSize((char *)&self->data,
52435236
_PyDateTime_TIME_DATASIZE);
52445237
if (basestate != NULL) {
52455238
if (proto > 3 && TIME_GET_FOLD(self))
@@ -5434,14 +5427,14 @@ datetime_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo)
54345427
if (me != NULL) {
54355428
const char *pdata = PyBytes_AS_STRING(state);
54365429

5437-
memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE);
5430+
memcpy(&me->data, pdata, _PyDateTime_DATETIME_DATASIZE);
54385431
me->hashcode = -1;
54395432
me->hastzinfo = aware;
54405433
if (aware) {
54415434
me->tzinfo = Py_NewRef(tzinfo);
54425435
}
54435436
if (pdata[2] & (1 << 7)) {
5444-
me->data[2] -= 128;
5437+
me->data.month -= 128;
54455438
me->fold = 1;
54465439
}
54475440
else {
@@ -6566,8 +6559,8 @@ datetime_richcompare(PyObject *self, PyObject *other, int op)
65666559
}
65676560

65686561
if (GET_DT_TZINFO(self) == GET_DT_TZINFO(other)) {
6569-
diff = memcmp(((PyDateTime_DateTime *)self)->data,
6570-
((PyDateTime_DateTime *)other)->data,
6562+
diff = memcmp(&((PyDateTime_DateTime *)self)->data,
6563+
&((PyDateTime_DateTime *)other)->data,
65716564
_PyDateTime_DATETIME_DATASIZE);
65726565
return diff_to_bool(diff, op);
65736566
}
@@ -6584,8 +6577,8 @@ datetime_richcompare(PyObject *self, PyObject *other, int op)
65846577
if ((offset1 == offset2) ||
65856578
(PyDelta_Check(offset1) && PyDelta_Check(offset2) &&
65866579
delta_cmp(offset1, offset2) == 0)) {
6587-
diff = memcmp(((PyDateTime_DateTime *)self)->data,
6588-
((PyDateTime_DateTime *)other)->data,
6580+
diff = memcmp(&((PyDateTime_DateTime *)self)->data,
6581+
&((PyDateTime_DateTime *)other)->data,
65896582
_PyDateTime_DATETIME_DATASIZE);
65906583
if ((op == Py_EQ || op == Py_NE) && diff == 0) {
65916584
int ex = pep495_eq_exception(self, other, offset1, offset2);
@@ -6666,7 +6659,7 @@ datetime_hash(PyObject *op)
66666659
/* Reduce this to a hash of another object. */
66676660
if (offset == Py_None) {
66686661
hash = generic_hash(
6669-
(unsigned char *)self->data, _PyDateTime_DATETIME_DATASIZE);
6662+
(unsigned char *)&self->data, _PyDateTime_DATETIME_DATASIZE);
66706663
FT_ATOMIC_STORE_SSIZE_RELAXED(self->hashcode, hash);
66716664
} else {
66726665
PyObject *temp1, *temp2;
@@ -7161,7 +7154,7 @@ datetime_getstate(PyDateTime_DateTime *self, int proto)
71617154
PyObject *basestate;
71627155
PyObject *result = NULL;
71637156

7164-
basestate = PyBytes_FromStringAndSize((char *)self->data,
7157+
basestate = PyBytes_FromStringAndSize((char *)&self->data,
71657158
_PyDateTime_DATETIME_DATASIZE);
71667159
if (basestate != NULL) {
71677160
if (proto > 3 && DATE_GET_FOLD(self))

0 commit comments

Comments
 (0)