summaryrefslogtreecommitdiff
path: root/tests/suite/ecore/src/lib/eina_magic.c
blob: 266c1474d82113ae13b2dde0dfbceab8c411ab2b (plain)
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
/* EINA - EFL data type library
 * Copyright (C) 2008 Cedric Bail
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library;
 * if not, see <https://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>

#ifdef HAVE_EVIL
#include <Evil.h>
#endif

#include "eina_config.h"
#include "eina_private.h"
#include "eina_error.h"
#include "eina_log.h"

/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */
#include "eina_safety_checks.h"
#include "eina_magic.h"

/*============================================================================*
*                                  Local                                     *
*============================================================================*/

/**
 * @cond LOCAL
 */

typedef struct _Eina_Magic_String Eina_Magic_String;
struct _Eina_Magic_String {
	Eina_Magic magic;
	Eina_Bool string_allocated;
	const char *string;
};

static int _eina_magic_string_log_dom = -1;

#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_magic_string_log_dom, __VA_ARGS__)

#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_magic_string_log_dom, __VA_ARGS__)

static Eina_Magic_String *_eina_magic_strings = NULL;
static size_t _eina_magic_strings_count = 0;
static size_t _eina_magic_strings_allocated = 0;
static Eina_Bool _eina_magic_strings_dirty = 0;

static int _eina_magic_strings_sort_cmp(const void *p1, const void *p2)
{
	const Eina_Magic_String *a = p1, *b = p2;
	return a->magic - b->magic;
}

static int _eina_magic_strings_find_cmp(const void *p1, const void *p2)
{
	Eina_Magic a = (long) p1;
	const Eina_Magic_String *b = p2;
	return a - b->magic;
}

static Eina_Magic_String *_eina_magic_strings_alloc(void)
{
	size_t idx;

	if (_eina_magic_strings_count == _eina_magic_strings_allocated) {
		void *tmp;
		size_t size;

		if (EINA_UNLIKELY(_eina_magic_strings_allocated == 0))
			size = 48;
		else
			size = _eina_magic_strings_allocated + 16;

		tmp =
		    realloc(_eina_magic_strings,
			    sizeof(Eina_Magic_String) * size);
		if (!tmp) {
#ifdef _WIN32
			ERR("could not realloc magic_strings from %Iu to %Iu buckets.",
#else
			ERR("could not realloc magic_strings from %zu to %zu buckets.",
#endif
			    _eina_magic_strings_allocated, size);
			return NULL;
		}

		_eina_magic_strings = tmp;
		_eina_magic_strings_allocated = size;
	}

	idx = _eina_magic_strings_count;
	_eina_magic_strings_count++;
	return _eina_magic_strings + idx;
}

/**
 * @endcond
 */

/*============================================================================*
*                                 Global                                     *
*============================================================================*/

/**
 * @internal
 * @brief Initialize the magic string module.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function sets up the magic string module of Eina. It is called by
 * eina_init().
 *
 * @see eina_init()
 */
Eina_Bool eina_magic_string_init(void)
{
	_eina_magic_string_log_dom = eina_log_domain_register
	    ("eina_magic_string", EINA_LOG_COLOR_DEFAULT);
	if (_eina_magic_string_log_dom < 0) {
		EINA_LOG_ERR
		    ("Could not register log domain: eina_magic_string");
		return EINA_FALSE;
	}

	return EINA_TRUE;
}

/**
 * @internal
 * @brief Shut down the magic string module.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function shuts down the magic string module set up by
 * eina_magic string_init(). It is called by eina_shutdown().
 *
 * @see eina_shutdown()
 */
Eina_Bool eina_magic_string_shutdown(void)
{
	Eina_Magic_String *ems, *ems_end;

	ems = _eina_magic_strings;
	ems_end = ems + _eina_magic_strings_count;

	for (; ems < ems_end; ems++)
		if (ems->string_allocated)
			free((char *) ems->string);

	free(_eina_magic_strings);
	_eina_magic_strings = NULL;
	_eina_magic_strings_count = 0;
	_eina_magic_strings_allocated = 0;

	eina_log_domain_unregister(_eina_magic_string_log_dom);
	_eina_magic_string_log_dom = -1;

	return EINA_TRUE;
}

/*============================================================================*
*                                   API                                      *
*============================================================================*/

/**
 * @addtogroup Eina_Magic_Group Magic
 *
 * @brief These functions provide runtime type-checking (magic checks)
 * management for projects.
 *
 * C is a weak statically typed language, in other words, it will just
 * check for types during compile time and any cast will make the
 * compiler believe the type is correct.
 *
 * In real world projects we often need to deal with casts, either
 * explicit or implicit by means of @c void*. We also need to resort
 * to casts when doing inheritance in C, as seen in the example below:
 *
 * @code
 * struct base {
 *    int id;
 *    char *name;
 * };
 * int base_id_get(struct base *ptr) {
 *    return ptr->id;
 * }
 *
 * struct subtype {
 *    struct base base;
 *    time_t date;
 * };
 * @endcode
 *
 * It is perfectly valid to use @c {struct subtype} blobs for functions
 * that expect @c {struct base}, since the fields will have the same
 * offset (as base member is the first, at offset 0). We could give
 * the functions the @c {&subtype->base} and avoid the cast, but often
 * we just cast.
 *
 * In any case, we might be safe and check if the given pointer is
 * actually of the expected type. We can do so by using eina_magic,
 * that is nothing more than attaching an unique type identifier to
 * the members and check for it elsewhere.
 *
 * @code
 * #define BASE_MAGIC 0x12345
 * #define SUBTYPE_MAGIC 0x3333
 * struct base {
 *    int id;
 *    char *name;
 *    EINA_MAGIC;
 * };
 * int base_id_get(struct base *ptr) {
 *    if (!EINA_MAGIC_CHECK(ptr, BASE_MAGIC)) {
 *       EINA_MAGIC_FAIL(ptr, BASE_MAGIC);
 *       return -1;
 *    }
 *    return ptr->id;
 * }
 * void base_free(struct base *ptr) {
 *    if (!EINA_MAGIC_CHECK(ptr, BASE_MAGIC)) {
 *       EINA_MAGIC_FAIL(ptr, BASE_MAGIC);
 *       return;
 *    }
 *    EINA_MAGIC_SET(ptr, EINA_MAGIC_NONE);
 *    free(ptr->name);
 *    free(ptr);
 * }
 * struct base *base_new(int id, const char *name) {
 *    struct base *ptr = malloc(sizeof(struct base));
 *    EINA_MAGIC_SET(ptr, BASE_MAGIC);
 *    ptr->id = id;
 *    ptr->name = strdup(name);
 * }
 *
 * struct subtype {
 *    struct base base;
 *    EINA_MAGIC;
 *    time_t date;
 * };
 *
 * int my_init(void) {
 *    eina_init();
 *    eina_magic_string_set(BASE_MAGIC, "base type");
 *    eina_magic_string_set(SUBTYPE_MAGIC, "subtype");
 * }
 * @endcode
 *
 * This code also shows that it is a good practice to set magic to
 * #EINA_MAGIC_NONE before freeing pointer. Sometimes the pointers are
 * in pages that are still live in memory, so kernel will not send
 * SEGV signal to the process and it may go unnoticed that you're
 * using already freed pointers. By setting them to #EINA_MAGIC_NONE
 * you avoid using the bogus pointer any further and gets a nice error
 * message.
 *
 * @{
 */

/**
 * @brief Return the string associated to the given magic identifier.
 *
 * @param magic The magic identifier.
 * @return The string associated to the identifier.
 *
 * This function returns the string associated to @p magic. If none
 * are found, the this function still returns non @c NULL, in this
 * case an identifier such as "(none)", "(undefined)" or
 * "(unknown)". The returned value must not be freed.
 *
 * The following identifiers may be returned whenever magic is
 * invalid, with their meanings:
 *
 *   - (none): no magic was registered exists at all.
 *   - (undefined): magic was registered and found, but no string associated.
 *   - (unknown): magic was not found in the registry.
 */
EAPI const char *eina_magic_string_get(Eina_Magic magic)
{
	Eina_Magic_String *ems;

	if (!_eina_magic_strings)
		return "(none)";

	if (_eina_magic_strings_dirty) {
		qsort(_eina_magic_strings, _eina_magic_strings_count,
		      sizeof(Eina_Magic_String),
		      _eina_magic_strings_sort_cmp);
		_eina_magic_strings_dirty = 0;
	}

	ems = bsearch((void *) (long) magic, _eina_magic_strings,
		      _eina_magic_strings_count, sizeof(Eina_Magic_String),
		      _eina_magic_strings_find_cmp);
	if (ems)
		return ems->string ? ems->string : "(undefined)";

	return "(unknown)";
}

/**
 * @brief Set the string associated to the given magic identifier.
 *
 * @param magic The magic identifier.
 * @param magic_name The string associated to the identifier, must not
 *        be @c NULL.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function sets the string @p magic_name to @p magic. It is not
 * checked if number or string are already set, then you might end
 * with duplicates in that case.
 *
 * @see eina_magic_string_static_set()
 */
EAPI Eina_Bool
eina_magic_string_set(Eina_Magic magic, const char *magic_name)
{
	Eina_Magic_String *ems;

	EINA_SAFETY_ON_NULL_RETURN_VAL(magic_name, EINA_FALSE);

	ems = _eina_magic_strings_alloc();
	if (!ems)
		return EINA_FALSE;

	ems->magic = magic;
	ems->string_allocated = EINA_TRUE;
	ems->string = strdup(magic_name);
	if (!ems->string) {
		ERR("could not allocate string '%s'", magic_name);
		_eina_magic_strings_count--;
		return EINA_FALSE;
	}

	_eina_magic_strings_dirty = 1;
	return EINA_TRUE;
}

/**
 * @brief Set the string associated to the given magic identifier.
 *
 * @param magic The magic identifier.
 * @param magic_name The string associated to the identifier, must not be
 *        @c NULL, it will not be duplcated, just referenced thus it must
 *        be live during magic number usage.
 *
 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
 *
 * This function sets the string @p magic_name to @p magic. It is not
 * checked if number or string are already set, then you might end
 * with duplicates in that case.
 *
 * @see eina_magic_string_set()
 */
EAPI Eina_Bool
eina_magic_string_static_set(Eina_Magic magic, const char *magic_name)
{
	Eina_Magic_String *ems;

	EINA_SAFETY_ON_NULL_RETURN_VAL(magic_name, EINA_FALSE);

	ems = _eina_magic_strings_alloc();
	if (!ems)
		return EINA_FALSE;

	ems->magic = magic;
	ems->string_allocated = EINA_FALSE;
	ems->string = magic_name;

	_eina_magic_strings_dirty = 1;
	return EINA_TRUE;
}

#ifdef eina_magic_fail
#undef eina_magic_fail
#endif

/**
 * @brief Display a message or abort is a magic check failed.
 *
 * @param d The checked data pointer.
 * @param m The magic identifer to check.
 * @param req_m The requested magic identifier to check.
 * @param file The file in which the magic check failed.
 * @param fnc The function in which the magic check failed.
 * @param line The line at which the magic check failed.
 *
 * This function displays an error message if a magic check has
 * failed, using the following logic in the following order:
 * @li If @p d is @c NULL, a message warns about a @c NULL pointer.
 * @li Otherwise, if @p m is equal to #EINA_MAGIC_NONE, a message
 * warns about a handle that was already freed.
 * @li Otherwise, if @p m is equal to @p req_m, a message warns about
 * a handle that is of wrong type.
 * @li Otherwise, a message warns you about ab-using that function...
 *
 * If the environment variable EINA_ERROR_ABORT is set, abort() is
 * called and the program stops. It is useful for debugging programs
 * with gdb.
 */
EAPI void
eina_magic_fail(void *d,
		Eina_Magic m,
		Eina_Magic req_m,
		const char *file, const char *fnc, int line)
{
	if (!d)
		eina_log_print(EINA_LOG_DOMAIN_GLOBAL,
			       EINA_LOG_LEVEL_CRITICAL, file, fnc, line,
			       "*** Eina Magic Check Failed !!!\n"
			       "    Input handle pointer is NULL !\n"
			       "*** NAUGHTY PROGRAMMER!!!\n"
			       "*** SPANK SPANK SPANK!!!\n"
			       "*** Now go fix your code. Tut tut tut!\n"
			       "\n");
	else if (m == EINA_MAGIC_NONE)
		eina_log_print(EINA_LOG_DOMAIN_GLOBAL,
			       EINA_LOG_LEVEL_CRITICAL, file, fnc, line,
			       "*** Eina Magic Check Failed !!!\n"
			       "    Input handle has already been freed!\n"
			       "*** NAUGHTY PROGRAMMER!!!\n"
			       "*** SPANK SPANK SPANK!!!\n"
			       "*** Now go fix your code. Tut tut tut!\n"
			       "\n");
	else if (m != req_m)
		eina_log_print(EINA_LOG_DOMAIN_GLOBAL,
			       EINA_LOG_LEVEL_CRITICAL, file, fnc, line,
			       "*** Eina Magic Check Failed !!!\n"
			       "    Input handle is wrong type\n"
			       "    Expected: %08x - %s\n"
			       "    Supplied: %08x - %s\n"
			       "*** NAUGHTY PROGRAMMER!!!\n"
			       "*** SPANK SPANK SPANK!!!\n"
			       "*** Now go fix your code. Tut tut tut!\n"
			       "\n", req_m, eina_magic_string_get(req_m),
			       m, eina_magic_string_get(m));
	else
		eina_log_print(EINA_LOG_DOMAIN_GLOBAL,
			       EINA_LOG_LEVEL_CRITICAL, file, fnc, line,
			       "*** Eina Magic Check Failed !!!\n"
			       "    Why did you call me !\n"
			       "*** NAUGHTY PROGRAMMER!!!\n"
			       "*** SPANK SPANK SPANK!!!\n"
			       "*** Now go fix your code. Tut tut tut!\n"
			       "\n");
}

/**
 * @}
 */