/
saneobj.c
350 lines (288 loc) · 9.27 KB
/
saneobj.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/* saneobj.c - Minimalistic Type-Safe Object System For gcc/clang
by Proger_XP | https://github.com/ProgerXP/SaneC | public domain (CC0) */
#include <stddef.h>
#include "saneobj.h"
/*** Object's methods ********************************************************/
Object_vt *vtObject(void) {
static Object_vt vt = {NULL, {
sizeof(Object_vt), sizeof(Object), "Object",
(ctor_t *) Object_new, (dtor_t *) Object_del,
}};
return &vt;
}
Object *Object_new(Object *o, void *params) {
#ifdef SJ_OBJECT_MAGIC
// gcc complains with -Werror=stringop-truncation.
strncpy(o->magic, objectMagic, sizeof(o->magic) - 1);
o->magic[sizeof(o->magic)-1] = objectMagic[sizeof(o->magic)-1];
#endif
return o;
}
void Object_del(Object *o) {
#ifndef SJ_NO_EXTRA
if (o->extra) {
sjFree(o->extra);
o->extra = NULL;
}
#endif
}
/*** Autoref's methods *******************************************************/
Autoref_vt *vtAutoref(void) {
linkvt(Autoref, Object) {
vt.take = Autoref_take;
vt.release = Autoref_release;
}
return &vt;
}
// In certain cases Autoref->del() may be called even when refs is non-zero.
// For example, on-stack objects don't even update refs. When ->del() is called,
// go ahead and prepare for deletion regardless of the counter's value.
Autoref *Autoref_new(Autoref *o, void *params) {
initnew(Autoref);
return o;
}
int Autoref_take(Autoref *o) {
return atomic_fetch_add(&o->refs, 1);
}
int Autoref_release(Autoref *o) {
return atomic_fetch_sub(&o->refs, 1);
}
/*** Other Functions *********************************************************/
#ifdef SJ_OBJECT_MAGIC
const char objectMagic[4] = SJ_OBJECT_MAGIC;
#endif
void sjObjectCallbackStub(Object *obj) { }
void sjObjectCallbackStdErr(Object *obj) {
#ifdef SJ_TRACE_LIFE
enum { creating, deleting } action = obj->delFile ? deleting : creating;
fprintf(stderr, " [%s] %s (%s:%d)\n",
action == creating ? "++" : "--",
obj->vt->className,
action == creating ? obj->newFile : obj->delFile,
action == creating ? obj->newLine : obj->delLine
);
#endif
}
SjObjectCallback *sjCreating = sjObjectCallbackStub;
SjObjectCallback *sjDeleting = sjObjectCallbackStub;
_Atomic unsigned sjObjectsCreated;
_Atomic unsigned sjObjectsDeleted;
static struct SxTraceEntry makeEx(const char *file, int line) {
struct SxTraceEntry entry = {.line = line};
sxlcpy(entry.file, file);
return entry;
}
void *sjNew(ctor_t *ctor, void *params, size_t size,
const char *file, int line) {
Object *const allocated = sjAlloc(size);
if (!allocated) {
sxThrow(sxprintf(makeEx(file, line),
"sjAlloc(%d) error.",
size));
}
// Using volatile is necessary because the compiler can't predict that
// try will always return normally and all longjmp()s (results of throw()
// called from ctor and elsewhere in this function) will always pass through
// the o=ctor(...) assignment. See man 3 longjmp, the NOTES section.
Object *volatile o;
try {
o = ctor(allocated, params);
#ifdef SJ_TRACE_LIFE
if (o == allocated) {
o->newFile = file;
o->newLine = line;
sjCreating(o);
++sjObjectsCreated;
}
#endif
} catchall {
sjFree(allocated);
sxRethrow(sxprintf(makeEx(file, line),
"ctor(%p) error.",
ctor));
} endtry
if (o != allocated) {
sjFree(allocated);
if (o == NULL) {
sxThrow(sxprintf(makeEx(file, line),
"ctor(%p) returned NULL.",
ctor));
} else if (!sjHasClass(o, vtAutoref())) {
// Not freeing o as potentially leaking memory is better than potentially
// double-freeing it (and possibly not calling the destructor).
sxThrow(sxprintf(makeEx(file, line),
"ctor(%p) returned a non-input object (%s at %p) that doesn't"
" extend Autoref.",
ctor, o->vt->className, o));
}
}
return o;
}
char sjRelease(void *obj) {
Autoref *ar = (Autoref *) obj;
return !sjHasClass(obj, vtAutoref()) || ar->vt->release(ar) == 1;
}
char sjDel(void *obj, const char* file, int line) {
if (!sjRelease(obj)) {
return 0;
}
try {
Object *o = (Object *) obj;
#ifdef SJ_TRACE_LIFE
++sjObjectsDeleted;
o->delFile = file;
o->delLine = line;
sjDeleting(o);
#endif
o->vt->del(o);
} finally {
sjFree(obj);
} endtry
return 1;
}
struct SjInheritedMethod sjInheritedMethod(const Object_vt *vt,
const void *vtMethod, const void *methodBody) {
static const size_t ptrSize = sizeof(void *);
const char *className = vt->className;
size_t vtOffset = vtMethod - (void *) vt;
if (vtMethod < (void *) vt || vtOffset > vt->size - ptrSize ||
// NULL methodBody = abstract method, not sure what behavior the caller
// expected in this case so bail out.
!methodBody ||
sizeof(vtOffset) < sizeof(ptrdiff_t)) {
sxThrow(sxprintf(
newex(),
"sjInheritedMethod(%s): vtMethod doesn't belong to vt.",
className));
}
int foundMethod = 0;
do {
const void *const *ptr =
(const void *const *) (((const void *) vt) + vtOffset);
if (foundMethod) {
if (!*ptr) {
// Target (found) VT has a NULL in placce of the method. By convenition,
// this is an abstract method which isn't meant for calling, so it's
// being treated as "no previos declaration" as if the class didn't
// have it declared at all.
return (struct SjInheritedMethod) {vt, NULL};
} else if (*ptr != methodBody) {
// When a subclass (C) doesn't override a method (of P), C's vt->method
// points to the same place as P's vt->method:
// void P_method(o) { inherited(); }
// In this example, inherited method would be the same P_method so an
// infinite recursion will occur.
// It's still possible for it to occur by specifically assigning the same
// method to different points in inheritance chain as below, but this
// is not a normal situation so it's not handled:
// CC vt->m == M2 C vt->m == M1 P vt->m == M2
// calling inherited from CC (subclass of C) will call M1 which will
// call M2 again because it thinks it's C's inherited method
return (struct SjInheritedMethod) {vt, ptr};
}
} else if (*ptr == methodBody) {
foundMethod = 1;
}
} while ((vt = vt->parent) &&
vt->size - ptrSize >= vtOffset);
if (foundMethod) {
// method belongs to the first class where it's declared (not necessary
// where it's defined). There's no parent class beyond that class that has
// method so this is a correct termination.
return (struct SjInheritedMethod) {NULL, NULL};
} else {
sxThrow(sxprintf(
newex(),
"sjInheritedMethod(%s): methodBody doesn't belong to any vt in the chain.",
className));
}
}
// A rather inefficient implementation but it's not like this function is meant
// to be used often.
struct SjInheritedMethod sjBaseMethod(const Object_vt *vt,
const void *vtMethod) {
void *methodBody = * (void **) vtMethod;
struct SjInheritedMethod lastInh = {vt, methodBody};
while (1) {
struct SjInheritedMethod inh = sjInheritedMethod(vt, vtMethod, lastInh.method);
if (!inh.method) {
if (inh.vt) { // got an abstract declaration.
lastInh = inh;
}
break;
}
lastInh = inh;
}
return lastInh;
}
int sjHasClass(const void *obj, const void *vt) {
const Object_vt *ovt = ((Object *) obj)->vt;
do {
if (ovt == vt) {
return 1;
}
} while ((ovt = ovt->parent));
return 0;
}
void *sjClassCast(void *obj, const void *vt) {
if (sjHasClass(obj, vt)) {
return obj;
} else {
sxThrow(sxprintf(newex(),
"Object of class %s cannot be cast to %s.",
((Object *) obj)->vt->className, ((Object_vt *) vt)->className));
}
}
int sjClassList(const Object_vt *vt, const char *list[], int maxList) {
int i = 0;
while (i < maxList && vt) {
list[i++] = vt->className;
vt = vt->parent;
}
return i;
}
char *sjJoinClassList(const void *vt, const char *joiner, int parentFirst) {
static const int maxList = 64;
const char *list[maxList];
const int stringExtra = 4; // for "...\0".
const int maxString = 1000;
char *res = malloc(maxString + stringExtra);
try {
int numList = sjClassList(vt, list, maxList);
int numString = 0;
int joinerLen = 0;
for (int i = parentFirst ? numList - 1 : 0;
i >= 0 && i < numList;
i += parentFirst ? -1 : +1) {
int len = strlen(list[i]);
if (joinerLen + numString + len >= maxString) {
strcpy(&res[numString], "...");
numString += 3;
break;
}
strncpy(&res[numString], joiner, joinerLen);
numString += joinerLen; // \0 intentionally not included.
strcpy(&res[numString], list[i]);
numString += len; // \0 intentionally not included.
joinerLen = strlen(joiner);
}
res[numString] = '\0';
} catchall {
free(res);
sxRethrow(newex());
} endtry
return res;
}
int sjCountParents(const void *vt) {
int res = -1;
do {
res++;
} while ((vt = ((Object_vt *) vt)->parent));
return res;
}
Object_vt *sjNthParent(const void *vt, int n) {
n = sjCountParents(vt) - n;
if (n < 0) { vt = NULL; }
while (n-- > 0) { vt = ((Object_vt *) vt)->parent; }
return (Object_vt *) vt; // suppress -W: discarded const qualifier.
}