Objectively
Object oriented framework for C.
Loading...
Searching...
No Matches
Class.c File Reference
#include "Config.h"
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Class.h"
#include "Object.h"

Go to the source code of this file.

Data Structures

struct  ClassImage
 A registered image: the handle the application holds, and the base address that the Classes it declares record in Class::image. More...
 

Functions

ident _alloc (Class *clazz)
 Instantiate a type through the given Class.
 
ident _cast (const Class *clazz, const ident obj)
 Perform a type-checking cast.
 
Class * _initialize (const ClassDef *def)
 Initializes the given Class.
 
void addClassImage (ident handle, const ident address)
 Registers an image that provides Classes, e.g. a plugin.
 
Class * classForName (const char *name)
 
static const ident imageForAddress (const ident address)
 
ident release (ident obj)
 Atomically decrement the given Object's reference count. If the resulting reference count is 0, the Object is deallocated.
 
void removeClassImage (ident handle)
 Unregisters an image, and every Class it declared.
 
ident retain (ident obj)
 Atomically increment the given Object's reference count.
 
static void setup (void)
 Called when initializing Object to setup Objectively.
 
static void teardown (void)
 Called atexit to teardown Objectively.
 

Variables

static Class * _classes
 
static pthread_mutex_t _classesLock = PTHREAD_MUTEX_INITIALIZER
 Guards the structure of _classes. MUST NOT be held across dlsym, dlopen, or a Class initializer, each of which can reenter _initialize.
 
static ClassImage * _images
 The registered images providing Classes, most recently added first. Published atomically rather than under _classesLock, which cannot be held across the dlsym this list exists for. A plain list rather than a MutableArray, because Class is beneath the collections.
 
size_t _pageSize
 

Function Documentation

◆ _alloc()

ident _alloc ( Class *  clazz)

Instantiate a type through the given Class.

Definition at line 198 of file Class.c.

198 {
199
200 ident obj = calloc(1, clazz->def.instanceSize);
201 assert(obj);
202
203 Object *object = (Object *) obj;
204
205 object->magic = OBJECTIVELY_MAGIC;
206 object->clazz = clazz;
207 object->referenceCount = 1;
208
209 return obj;
210}
#define obj
#define OBJECTIVELY_MAGIC
The header value identifying Objectively types.
Definition Class.h:209
void * ident
The identity type, similar to Objective-C id.
Definition Types.h:49
size_t instanceSize
The instance size (required).
Definition Class.h:69
ClassDef def
The Class definition.
Definition Class.h:95
Object is the root Class of The Objectively Class hierarchy.
Definition Object.h:46
unsigned int magic
A header to allow introspection of Object types.
Definition Object.h:50

◆ _cast()

ident _cast ( const Class *  clazz,
const ident  obj 
)

Perform a type-checking cast.

Definition at line 212 of file Class.c.

212 {
213
214 if (obj) {
215 const Class *c = ((Object *) obj)->clazz;
216 while (c) {
217
218 // as a special case, we optimize for _Object
219 if (c == clazz || clazz == _Object()) {
220 break;
221 }
222
223 c = c->def.superclass;
224 }
225 assert(c);
226 }
227
228 return (ident) obj;
229}
Class * _Object(void)
Definition Object.c:136
Class * superclass
The superclass (required). e.g. _Object().
Definition Class.h:84
The runtime representation of a Class.
Definition Class.h:90

◆ _initialize()

Class * _initialize ( const ClassDef *  clazz)

Initializes the given Class.

Parameters
clazzThe Class descriptor.
Returns
The initialized Class.

Definition at line 151 of file Class.c.

151 {
152
153 static Once once;
154 do_once(&once, setup());
155
156 assert(def);
157 assert(def->name);
158 assert(def->instanceSize);
159 assert(def->interfaceSize);
160
161 Class *clazz = calloc(1, sizeof(Class));
162 assert(clazz);
163
164 clazz->def = *def;
165
166 clazz->interface = calloc(1, def->interfaceSize);
167 assert(clazz->interface);
168
169 Class *superclass = clazz->def.superclass;
170 if (superclass) {
171
172 assert(superclass->def.instanceSize <= def->instanceSize);
173 assert(superclass->def.interfaceSize <= def->interfaceSize);
174
175 memcpy(clazz->interface, superclass->interface, superclass->def.interfaceSize);
176 }
177
178 if (clazz->def.initialize) {
179 clazz->def.initialize(clazz);
180 }
181
182 /* def.name is a literal in the declaring image, where the ClassDef itself is
183 * a compound literal with automatic storage. */
184 clazz->image = imageForAddress((ident) def->name);
185
186 /* Taken here rather than around the whole function: def.initialize above can
187 * reach other archetypes, and so this, before that Class is published. */
188 pthread_mutex_lock(&_classesLock);
189
190 clazz->next = _classes;
191 _classes = clazz;
192
193 pthread_mutex_unlock(&_classesLock);
194
195 return clazz;
196}
static const ident imageForAddress(const ident address)
Definition Class.c:131
static pthread_mutex_t _classesLock
Guards the structure of _classes. MUST NOT be held across dlsym, dlopen, or a Class initializer,...
Definition Class.c:52
static void setup(void)
Called when initializing Object to setup Objectively.
Definition Class.c:113
static Class * _classes
Definition Class.c:46
long Once
The Once type.
Definition Once.h:37
#define do_once(once, block)
Executes the given block at most one time.
Definition Once.h:43
size_t interfaceSize
The interface size (required).
Definition Class.h:74
const char * name
The Class name (required).
Definition Class.h:79
void(* initialize)(Class *clazz)
The Class initializer (optional).
Definition Class.h:64
ident image
The base address of the image that declared this Class.
Definition Class.h:113
Class * next
Provides chaining of initialized Classes.
Definition Class.h:105
ident interface
The interface of the Class.
Definition Class.h:100

◆ addClassImage()

void addClassImage ( ident  handle,
const ident  address 
)

Registers an image that provides Classes, e.g. a plugin.

Parameters
handleA handle from dlopen.
addressAny address within that image, such as the entry point the application resolved from handle.

classForName resolves a name it has not yet initialized through the process-wide namespace, which holds only images loaded RTLD_GLOBAL, and which Windows does not have at all. An application that loads Classes from a plugin registers it here instead, and may then load it RTLD_LOCAL - which is how it keeps two plugins exporting the same symbols from coalescing.

Remarks
Registered images are searched most recently added first, so a newly loaded plugin answers ahead of the one it replaced.
Resolution is by Objectively's own convention, the Class name prefixed with an underscore, so nothing about the image has to be declared.
address is what identifies the image. Classes record the base address of the image that declared them, and the platforms report that for an address, not for a handle, so this takes one that the caller can vouch for. Aborts if no loaded image contains it.

Definition at line 231 of file Class.c.

231 {
232
233 assert(handle);
234 assert(address);
235
236 const ident image = imageForAddress(address);
237 if (image == NULL) {
238 fprintf(stderr, "%s: no image contains %p\n", __func__, address);
239 abort();
240 }
241
242 ClassImage *classImage = calloc(1, sizeof(ClassImage));
243 assert(classImage);
244
245 classImage->handle = handle;
246 classImage->image = image;
247
248 /* Published the same way a Class is, and for the same reason: classForName
249 * walks this list on any thread. */
250 classImage->next = __atomic_load_n(&_images, __ATOMIC_RELAXED);
251 while (!__atomic_compare_exchange_n(&_images, &classImage->next, classImage, 1,
252 __ATOMIC_RELEASE, __ATOMIC_RELAXED)) ;
253}
static ClassImage * _images
The registered images providing Classes, most recently added first. Published atomically rather than ...
Definition Class.c:71
A registered image: the handle the application holds, and the base address that the Classes it declar...
Definition Class.c:59
ident image
Definition Class.c:61
ident handle
Definition Class.c:60
ClassImage * next
Definition Class.c:62

◆ classForName()

Class * classForName ( const char *  name)
Returns
The Class with the given name, or NULL if no such Class has been initialized.
Remarks
Classes already initialized are answered first, then each registered image, then the process-wide namespace.

Definition at line 300 of file Class.c.

300 {
301
302 if (name) {
303 pthread_mutex_lock(&_classesLock);
304
305 Class *c = _classes;
306 while (c) {
307 if (strcmp(name, c->def.name) == 0) {
308 break;
309 }
310 c = c->next;
311 }
312
313 pthread_mutex_unlock(&_classesLock);
314
315 if (c) {
316 return c;
317 }
318
319 char *s;
320 if (asprintf(&s, "_%s", name) > 0) {
321 Class *clazz = NULL;
322 Class *(*archetype)(void) = NULL;
323
324 for (ClassImage *i = __atomic_load_n(&_images, __ATOMIC_ACQUIRE);
325 i && archetype == NULL; i = i->next) {
326
327 ident handle = __atomic_load_n(&i->handle, __ATOMIC_ACQUIRE);
328 if (handle) {
329 archetype = dlsym(handle, s);
330 }
331 }
332
333 if (archetype == NULL) {
334#if defined(_WIN32)
335 static Once once;
336 static ident handle;
337 do_once(&once, { handle = dlopen(NULL, RTLD_LAZY); });
338 archetype = handle ? dlsym(handle, s) : NULL;
339#else
340 archetype = dlsym(RTLD_DEFAULT, s);
341#endif
342 }
343
344 if (archetype) {
345 clazz = archetype();
346 }
347
348 free(s);
349 return clazz;
350 }
351 }
352
353 return NULL;
354}

◆ imageForAddress()

static const ident imageForAddress ( const ident  address)
static
Returns
The base address of the image containing address, or NULL.
Remarks
The Windows dlfcn shim has no dladdr. UNCHANGED_REFCOUNT matters: without it this would pin the very module the caller is about to release.

Definition at line 131 of file Class.c.

131 {
132
133 assert(address);
134
135#if defined(_WIN32)
136 HMODULE module;
137 if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
138 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR) address, &module)) {
139 return module;
140 }
141#else
142 Dl_info info;
143 if (dladdr(address, &info)) {
144 return info.dli_fbase;
145 }
146#endif
147
148 return NULL;
149}
static void info(const Log *self, const char *fmt,...)
Definition Log.c:118

◆ release()

ident release ( ident  obj)

Atomically decrement the given Object's reference count. If the resulting reference count is 0, the Object is deallocated.

Returns
This function always returns NULL.

Definition at line 356 of file Class.c.

356 {
357
358 if (obj) {
359 Object *object = cast(Object, obj);
360
361 assert(object);
362
363 if (__atomic_fetch_sub(&object->referenceCount, 1, __ATOMIC_RELEASE) == 1) {
364 __atomic_thread_fence(__ATOMIC_ACQUIRE);
365 $(object, dealloc);
366 }
367 }
368
369 return NULL;
370}
static void dealloc(Object *self)
Definition Array.c:99
#define cast(type, obj)
Safely cast obj to type.
Definition Class.h:232

◆ removeClassImage()

void removeClassImage ( ident  handle)

Unregisters an image, and every Class it declared.

Parameters
handleThe handle given to addClassImage.
Remarks
Classes initialized from an image outlive it otherwise: they are cached by name, and classForName answers from that cache ahead of any image. On a platform where closing an image really unmaps it - which dlclose does on Linux and FreeLibrary does on Windows - the next lookup would then read a ClassDef that is no longer mapped. classForName compares the name of every Class it walks, so one left behind breaks every lookup, not only its own.
The Classes are not destroyed, and this is not an oversight. dlclose does not unmap on macOS, so an archetype that has already run keeps answering from its own static Class * for as long as the process lives, and would hand back whatever this freed. Unregistering is the only thing that is safe whether the image goes away or stays: the Class becomes unreachable by name, and remains valid for the archetype that owns it. The cost is the Class and its interface, which are not reclaimed.
MUST be called while the handle is still open, and only once nothing instantiated from that image survives.
Aborts if handle was not registered, rather than leaving the Classes it declared behind, which is the failure this exists to prevent. Two calls for the same handle abort on the second, whichever order they arrive in.

Definition at line 255 of file Class.c.

255 {
256
257 assert(handle);
258
259 /* Held from the search to the last unlink, so that retiring an image and
260 * dropping its Classes is one operation, and a second call for the same handle
261 * finds it already gone. Nothing here reaches the loader. */
262 pthread_mutex_lock(&_classesLock);
263
264 ident image = NULL;
265
266 /* Retired in place rather than unlinked, so that a concurrent classForName
267 * parked on this node still has a next to follow, and never reads a node that
268 * has been freed. Retiring is a single store of the handle it matches on, so
269 * that walk sees this image or does not, and never half of it. Retired nodes
270 * are freed at teardown; reusing one would put a newly registered image where
271 * the retired one sat, and lookup order is newest first. */
272 for (ClassImage *i = __atomic_load_n(&_images, __ATOMIC_ACQUIRE); i; i = i->next) {
273 if (__atomic_load_n(&i->handle, __ATOMIC_ACQUIRE) == handle) {
274 image = i->image;
275 __atomic_store_n(&i->handle, NULL, __ATOMIC_RELEASE);
276 break;
277 }
278 }
279
280 if (image == NULL) {
281 fprintf(stderr, "%s: %p was never registered\n", __func__, handle);
282 abort();
283 }
284
285 Class **classes = &_classes;
286 while (*classes) {
287 Class *clazz = *classes;
288
289 if (clazz->image == image) {
290 *classes = clazz->next;
291 clazz->next = NULL;
292 } else {
293 classes = &clazz->next;
294 }
295 }
296
297 pthread_mutex_unlock(&_classesLock);
298}

◆ retain()

ident retain ( ident  obj)

Atomically increment the given Object's reference count.

Returns
The Object.
Remarks
By calling this, the caller is expressing ownership of the Object, and preventing it from being released. Be sure to balance calls to retain with calls to release.

Definition at line 372 of file Class.c.

372 {
373
374 Object *object = cast(Object, obj);
375
376 assert(object);
377
378 /* A reference count of zero means another thread is already inside dealloc,
379 * and the caller is retaining memory that is about to be freed. */
380 unsigned int referenceCount = __atomic_load_n(&object->referenceCount, __ATOMIC_RELAXED);
381 do {
382 if (referenceCount == 0) {
383 fprintf(stderr, "%s: %p is being deallocated\n", __func__, object);
384 abort();
385 }
386 } while (!__atomic_compare_exchange_n(&object->referenceCount, &referenceCount,
387 referenceCount + 1, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
388
389 return obj;
390}

◆ setup()

static void setup ( void  )
static

Called when initializing Object to setup Objectively.

Definition at line 113 of file Class.c.

113 {
114
115 _classes = NULL;
116
117#if !defined(_SC_PAGESIZE)
118 _pageSize = 4096;
119#else
120 _pageSize = sysconf(_SC_PAGESIZE);
121#endif
122
123 atexit(teardown);
124}
static void teardown(void)
Called atexit to teardown Objectively.
Definition Class.c:76
size_t _pageSize
Definition Class.c:44

◆ teardown()

static void teardown ( void  )
static

Called atexit to teardown Objectively.

Definition at line 76 of file Class.c.

76 {
77 Class *c;
78
79 c = _classes;
80 while (c) {
81 if (c->def.destroy) {
82 c->def.destroy(c);
83 }
84
85 c = c->next;
86 }
87
88 c = _classes;
89 while (c) {
90
91 Class *next = c->next;
92
93 free(c->interface);
94 free(c);
95
96 c = next;
97 }
98
100 while (i) {
101
102 ClassImage *next = i->next;
103
104 free(i);
105
106 i = next;
107 }
108}
static Unicode next(StringReader *self, StringReaderMode mode)
void(* destroy)(Class *clazz)
The Class destructor (optional). This method is run for initialized Classes when your application exi...
Definition Class.h:47

Variable Documentation

◆ _classes

Class* _classes
static

Definition at line 46 of file Class.c.

◆ _classesLock

pthread_mutex_t _classesLock = PTHREAD_MUTEX_INITIALIZER
static

Guards the structure of _classes. MUST NOT be held across dlsym, dlopen, or a Class initializer, each of which can reenter _initialize.

Definition at line 52 of file Class.c.

◆ _images

ClassImage* _images
static

The registered images providing Classes, most recently added first. Published atomically rather than under _classesLock, which cannot be held across the dlsym this list exists for. A plain list rather than a MutableArray, because Class is beneath the collections.

Definition at line 71 of file Class.c.

◆ _pageSize

size_t _pageSize

Definition at line 44 of file Class.c.