aboutsummaryrefslogtreecommitdiff
path: root/include/erofs/flex-array.h
blob: 59168d05ee5a1eed07c29849b6dfe48a01b5083f (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
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __EROFS_FLEX_ARRAY_H
#define __EROFS_FLEX_ARRAY_H

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdint.h>

#include "defs.h"
#include "print.h"

/*
 * flex-array.h
 *
 * Some notes to make sense of the code.
 *
 * Flex-arrays:
 *   - Flex-arrays became standard in C99 and are defined by "array[]" (at the
 *     end of a struct)
 *   - Pre-C99 flex-arrays can be accomplished by "array[1]"
 *   - There is a GNU extension where they are defined using "array[0]"
 *     Allegedly there is/was a bug in gcc whereby foo[1] generated incorrect
 *     code, so it's safest to use [0] (https://lkml.org/lkml/2015/2/18/407).
 *
 * For C89 and C90, __STDC__ is 1
 * For later standards, __STDC_VERSION__ is defined according to the standard.
 * For example: 199901L or 201112L
 *
 * Whilst we're on the subject, in version 5 of gcc, the default std was
 * changed from gnu89 to gnu11. In jgmenu, CFLAGS therefore contains -std=gnu89
 * You can check your default gcc std by doing:
 * gcc -dM -E - </dev/null | grep '__STDC_VERSION__\|__STDC__'
 *
 * The code below is copied from git's git-compat-util.h in support of
 * hashmap.c
 */

#ifndef FLEX_ARRAY
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
	(!defined(__SUNPRO_C) || (__SUNPRO_C > 0x580))
# define FLEX_ARRAY /* empty */
#elif defined(__GNUC__)
# if (__GNUC__ >= 3)
#  define FLEX_ARRAY /* empty */
# else
#  define FLEX_ARRAY 0 /* older GNU extension */
# endif
#endif

/* Otherwise, default to safer but a bit wasteful traditional style */
#ifndef FLEX_ARRAY
# define FLEX_ARRAY 1
#endif
#endif

#define bitsizeof(x) (CHAR_BIT * sizeof(x))

#define maximum_signed_value_of_type(a) \
	(INTMAX_MAX >> (bitsizeof(intmax_t) - bitsizeof(a)))

#define maximum_unsigned_value_of_type(a) \
	(UINTMAX_MAX >> (bitsizeof(uintmax_t) - bitsizeof(a)))

/*
 * Signed integer overflow is undefined in C, so here's a helper macro
 * to detect if the sum of two integers will overflow.
 * Requires: a >= 0, typeof(a) equals typeof(b)
 */
#define signed_add_overflows(a, b) \
	((b) > maximum_signed_value_of_type(a) - (a))

#define unsigned_add_overflows(a, b) \
	((b) > maximum_unsigned_value_of_type(a) - (a))

static inline size_t st_add(size_t a, size_t b)
{
	if (unsigned_add_overflows(a, b)) {
		erofs_err("size_t overflow: %llu + %llu", a | 0ULL, b | 0ULL);
		BUG_ON(1);
		return -1;
	}
	return a + b;
}

#define st_add3(a, b, c) st_add(st_add((a), (b)), (c))
#define st_add4(a, b, c, d) st_add(st_add3((a), (b), (c)), (d))

/*
 * These functions help you allocate structs with flex arrays, and copy
 * the data directly into the array. For example, if you had:
 *
 *   struct foo {
 *     int bar;
 *     char name[FLEX_ARRAY];
 *   };
 *
 * you can do:
 *
 *   struct foo *f;
 *   FLEX_ALLOC_MEM(f, name, src, len);
 *
 * to allocate a "foo" with the contents of "src" in the "name" field.
 * The resulting struct is automatically zero'd, and the flex-array field
 * is NUL-terminated (whether the incoming src buffer was or not).
 *
 * The FLEXPTR_* variants operate on structs that don't use flex-arrays,
 * but do want to store a pointer to some extra data in the same allocated
 * block. For example, if you have:
 *
 *   struct foo {
 *     char *name;
 *     int bar;
 *   };
 *
 * you can do:
 *
 *   struct foo *f;
 *   FLEXPTR_ALLOC_STR(f, name, src);
 *
 * and "name" will point to a block of memory after the struct, which will be
 * freed along with the struct (but the pointer can be repointed anywhere).
 *
 * The *_STR variants accept a string parameter rather than a ptr/len
 * combination.
 *
 * Note that these macros will evaluate the first parameter multiple
 * times, and it must be assignable as an lvalue.
 */
#define FLEX_ALLOC_MEM(x, flexname, buf, len) do { \
	size_t flex_array_len_ = (len); \
	(x) = calloc(1, st_add3(sizeof(*(x)), flex_array_len_, 1)); \
	BUG_ON(!(x)); \
	memcpy((void *)(x)->flexname, (buf), flex_array_len_); \
} while (0)
#define FLEXPTR_ALLOC_MEM(x, ptrname, buf, len) do { \
	size_t flex_array_len_ = (len); \
	(x) = xcalloc(1, st_add3(sizeof(*(x)), flex_array_len_, 1)); \
	memcpy((x) + 1, (buf), flex_array_len_); \
	(x)->ptrname = (void *)((x) + 1); \
} while (0)
#define FLEX_ALLOC_STR(x, flexname, str) \
	FLEX_ALLOC_MEM((x), flexname, (str), strlen(str))
#define FLEXPTR_ALLOC_STR(x, ptrname, str) \
	FLEXPTR_ALLOC_MEM((x), ptrname, (str), strlen(str))

#endif