Skip to content

Commit cbaba64

Browse files
authored
Merge pull request #470 from bazsi/filterx-allow-stack-based-string-wrappers
Filterx allow stack based string wrappers
2 parents e7f5bf6 + 6b3afc7 commit cbaba64

10 files changed

+148
-55
lines changed

lib/filterx/filterx-object.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ filterx_object_freeze(FilterXObject *self)
119119
if (filterx_object_is_frozen(self))
120120
return FALSE;
121121
g_assert(g_atomic_counter_get(&self->ref_cnt) == 1);
122-
g_atomic_counter_set(&self->ref_cnt, FILTERX_OBJECT_MAGIC_BIAS);
122+
g_atomic_counter_set(&self->ref_cnt, FILTERX_OBJECT_REFCOUNT_FROZEN);
123123
return TRUE;
124124
}
125125

lib/filterx/filterx-object.h

+81-8
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ void _filterx_type_init_methods(FilterXType *type);
7474
__VA_ARGS__ \
7575
}
7676

77-
#define FILTERX_OBJECT_MAGIC_BIAS G_MAXINT32
77+
#define FILTERX_OBJECT_REFCOUNT_FROZEN (G_MAXINT32)
78+
#define FILTERX_OBJECT_REFCOUNT_STACK (G_MAXINT32-1)
79+
#define FILTERX_OBJECT_REFCOUNT_OFLOW_MARK (FILTERX_OBJECT_REFCOUNT_STACK-1024)
7880

7981

8082
FILTERX_DECLARE_TYPE(object);
@@ -110,7 +112,7 @@ void filterx_object_free_method(FilterXObject *self);
110112
static inline gboolean
111113
filterx_object_is_frozen(FilterXObject *self)
112114
{
113-
return g_atomic_counter_get(&self->ref_cnt) == FILTERX_OBJECT_MAGIC_BIAS;
115+
return g_atomic_counter_get(&self->ref_cnt) == FILTERX_OBJECT_REFCOUNT_FROZEN;
114116
}
115117

116118
static inline FilterXObject *
@@ -119,12 +121,53 @@ filterx_object_ref(FilterXObject *self)
119121
if (!self)
120122
return NULL;
121123

122-
if (filterx_object_is_frozen(self))
123-
return self;
124+
gint r = g_atomic_counter_get(&self->ref_cnt);
125+
if (r < FILTERX_OBJECT_REFCOUNT_OFLOW_MARK && r > 0)
126+
{
127+
/* NOTE: getting into this path is racy, as two threads might be
128+
* checking the overflow mark in parallel and then decide we need to
129+
* run this (normal) path. In this case, the race could cause ref_cnt
130+
* to reach FILTERX_OBJECT_REFCOUNT_OFLOW_MARK, without triggering the
131+
* overflow assert below.
132+
*
133+
* To mitigate this, FILTERX_OBJECT_REFCOUNT_OFLOW_MARK is set to 1024
134+
* less than the first value that we handle specially. This means
135+
* that even if the race is lost, we would need 1024 competing CPUs
136+
* concurrently losing the race and incrementing ref_cnt here. And
137+
* even in this case the only issue is that we don't detect an actual
138+
* overflow at runtime that should never occur in the first place.
139+
*
140+
* This is _really_ unlikely, and we will detect ref_cnt overflows in
141+
* non-doom scenarios first, so we can address the actual issue (which
142+
* might be a reference counting bug somewhere).
143+
*
144+
* If less than 1024 CPUs lose the race, then the refcount would end
145+
* up in the range between FILTERX_OBJECT_REFCOUNT_OFLOW_MARK and
146+
* FILTERX_OBJECT_REFCOUNT_STACK, causing the assertion at the end of
147+
* this function to trigger an abort.
148+
*
149+
* The non-racy solution would be to use a
150+
* g_atomic_int_exchange_and_add() call and checking the old_value
151+
* against FILTERX_OBJECT_REFCOUNT_OFLOW_MARK another time, but that's
152+
* an extra conditional in a hot-path.
153+
*/
154+
155+
g_atomic_counter_inc(&self->ref_cnt);
156+
return self;
157+
}
124158

125-
g_atomic_counter_inc(&self->ref_cnt);
159+
if (r == FILTERX_OBJECT_REFCOUNT_FROZEN)
160+
return self;
126161

127-
return self;
162+
if (r == FILTERX_OBJECT_REFCOUNT_STACK)
163+
{
164+
/* we can't use filterx_object_clone() directly, as that's an inline
165+
* function declared further below. Also, filterx_object_clone() does
166+
* not clone inmutable objects. We only support allocating inmutable
167+
* objects on the stack */
168+
return self->type->clone(self);
169+
}
170+
g_assert_not_reached();
128171
}
129172

130173
static inline void
@@ -133,10 +176,30 @@ filterx_object_unref(FilterXObject *self)
133176
if (!self)
134177
return;
135178

136-
if (filterx_object_is_frozen(self))
179+
gint r = g_atomic_counter_get(&self->ref_cnt);
180+
if (r == FILTERX_OBJECT_REFCOUNT_FROZEN)
137181
return;
182+
if (r == FILTERX_OBJECT_REFCOUNT_STACK)
183+
{
184+
/* NOTE: Normally, stack based allocations are only used by a single
185+
* thread. Furthermore, code where we use this object will only have
186+
* a borrowed reference, e.g. it can't cross thread boundaries. With
187+
* that said, even though the condition of this if() statement is
188+
* racy, it's not actually racing, as we only have a single relevant
189+
* thread.
190+
*
191+
* In the rare case where we do want to pass a stack based allocation
192+
* to another thread, we would need to pass a new reference to it, and
193+
* filterx_object_ref() clones a new object in this case, which again
194+
* means, that we won't have actual race here.
195+
*/
196+
197+
g_atomic_counter_set(&self->ref_cnt, 0);
198+
return;
199+
}
200+
if (r <= 0)
201+
g_assert_not_reached();
138202

139-
g_assert(g_atomic_counter_get(&self->ref_cnt) > 0);
140203
if (g_atomic_counter_dec_and_test(&self->ref_cnt))
141204
{
142205
self->type->free_fn(self);
@@ -354,4 +417,14 @@ filterx_object_set_modified_in_place(FilterXObject *self, gboolean modified)
354417
self->modified_in_place = modified;
355418
}
356419

420+
#define FILTERX_OBJECT_STACK_INIT(_type) \
421+
{ \
422+
.ref_cnt = { .counter = FILTERX_OBJECT_REFCOUNT_STACK }, \
423+
.fx_ref_cnt = { .counter = 0 }, \
424+
.modified_in_place = FALSE, \
425+
.readonly = TRUE, \
426+
.weak_referenced = FALSE, \
427+
.type = &FILTERX_TYPE_NAME(_type) \
428+
}
429+
357430
#endif

lib/filterx/object-json-object.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ static gboolean
200200
_iter_inner(FilterXJsonObject *self, const gchar *obj_key, struct json_object *jso,
201201
FilterXDictIterFunc func, gpointer user_data)
202202
{
203-
FilterXObject *key = filterx_string_new(obj_key, -1);
203+
FILTERX_STRING_DECLARE_ON_STACK(key, obj_key, strlen(obj_key));
204204
FilterXObject *value = filterx_json_convert_json_to_object_cached(&self->super.super, &self->root_container,
205205
jso);
206206

lib/filterx/object-string.c

+15-10
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@
2929
#include "str-format.h"
3030
#include "str-utils.h"
3131

32-
struct _FilterXString
33-
{
34-
FilterXObject super;
35-
gsize str_len;
36-
gchar str[];
37-
};
3832

3933
/* NOTE: Consider using filterx_object_extract_string_ref() to also support message_value. */
4034
const gchar *
@@ -142,6 +136,15 @@ _string_add(FilterXObject *s, FilterXObject *object)
142136
return filterx_string_new(buffer->str, buffer->len);
143137
}
144138

139+
/* we support clone of stack allocated strings */
140+
static FilterXObject *
141+
_string_clone(FilterXObject *s)
142+
{
143+
FilterXString *self = (FilterXString *) s;
144+
145+
return filterx_string_new(self->str, self->str_len);
146+
}
147+
145148
static inline FilterXString *
146149
_string_new(const gchar *str, gssize str_len, FilterXStringTranslateFunc translate)
147150
{
@@ -154,10 +157,11 @@ _string_new(const gchar *str, gssize str_len, FilterXStringTranslateFunc transla
154157

155158
self->str_len = str_len;
156159
if (translate)
157-
translate(self->str, str, str_len);
160+
translate(self->storage, str, str_len);
158161
else
159-
memcpy(self->str, str, str_len);
160-
self->str[str_len] = 0;
162+
memcpy(self->storage, str, str_len);
163+
self->storage[str_len] = 0;
164+
self->str = self->storage;
161165

162166
return self;
163167
}
@@ -291,7 +295,7 @@ filterx_typecast_string(FilterXExpr *s, FilterXObject *args[], gsize args_len)
291295
return NULL;
292296
}
293297

294-
return filterx_string_new(buf->str, -1);
298+
return filterx_string_new(buf->str, buf->len);
295299
}
296300

297301
FilterXObject *
@@ -353,6 +357,7 @@ FILTERX_DEFINE_TYPE(string, FILTERX_TYPE_NAME(object),
353357
.truthy = _truthy,
354358
.repr = _string_repr,
355359
.add = _string_add,
360+
.clone = _string_clone,
356361
);
357362

358363
FILTERX_DEFINE_TYPE(bytes, FILTERX_TYPE_NAME(object),

lib/filterx/object-string.h

+19
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
#include "filterx-object.h"
2727

2828
typedef struct _FilterXString FilterXString;
29+
struct _FilterXString
30+
{
31+
FilterXObject super;
32+
const gchar *str;
33+
gsize str_len;
34+
gchar storage[];
35+
};
36+
2937
typedef void (*FilterXStringTranslateFunc)(gchar *target, const gchar *source, gsize source_len);
3038

3139
FILTERX_DECLARE_TYPE(string);
@@ -46,4 +54,15 @@ FilterXObject *filterx_protobuf_new(const gchar *str, gssize str_len);
4654

4755
FilterXString *filterx_string_typed_new(const gchar *str);
4856

57+
#define FILTERX_STRING_STACK_INIT(cstr, cstr_len) \
58+
{ \
59+
FILTERX_OBJECT_STACK_INIT(string), \
60+
.str = (cstr), \
61+
.str_len = ((cstr_len) < 0 ? strlen(cstr) : (cstr_len)), \
62+
}
63+
64+
#define FILTERX_STRING_DECLARE_ON_STACK(_name, cstr, cstr_len) \
65+
FilterXString __ ## _name ## storage = FILTERX_STRING_STACK_INIT(cstr, cstr_len); \
66+
FilterXObject *_name = &__ ## _name ## storage .super;
67+
4968
#endif

lib/str-format.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ format_int32_padded(GString *result, gint field_len, gchar pad_char, gint base,
262262
}
263263

264264
gchar *
265-
format_hex_string_with_delimiter(gpointer data, gsize data_len, gchar *result, gsize result_len, gchar delimiter)
265+
format_hex_string_with_delimiter(gconstpointer data, gsize data_len, gchar *result, gsize result_len, gchar delimiter)
266266
{
267267
gint i;
268268
gint pos = 0;
@@ -285,7 +285,7 @@ format_hex_string_with_delimiter(gpointer data, gsize data_len, gchar *result, g
285285
}
286286

287287
gchar *
288-
format_hex_string(gpointer data, gsize data_len, gchar *result, gsize result_len)
288+
format_hex_string(gconstpointer data, gsize data_len, gchar *result, gsize result_len)
289289
{
290290
return format_hex_string_with_delimiter(data, data_len, result, result_len, 0);
291291
}

lib/str-format.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ gint format_int32_padded(GString *result, gint field_len, gchar pad_char, gint b
3333
gint format_uint64_padded(GString *result, gint field_len, gchar pad_char, gint base, guint64 value);
3434
gint format_int64_padded(GString *result, gint field_len, gchar pad_char, gint base, gint64 value);
3535

36-
gchar *format_hex_string(gpointer str, gsize str_len, gchar *result, gsize result_len);
37-
gchar *format_hex_string_with_delimiter(gpointer str, gsize str_len, gchar *result, gsize result_len, gchar delimiter);
36+
gchar *format_hex_string(gconstpointer str, gsize str_len, gchar *result, gsize result_len);
37+
gchar *format_hex_string_with_delimiter(gconstpointer str, gsize str_len, gchar *result, gsize result_len,
38+
gchar delimiter);
3839

3940
gboolean scan_positive_int(const gchar **buf, gint *left, gint field_width, gint *num);
4041
gboolean scan_expect_char(const gchar **buf, gint *left, gchar value);

modules/csvparser/filterx-func-parse-csv.c

+6-5
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,9 @@ _fill_object_col(FilterXFunctionParseCSV *self, FilterXObject *cols, gint64 inde
172172
else
173173
col = filterx_list_get_subscript(cols, index);
174174

175-
FilterXObject *val = filterx_string_new(csv_scanner_get_current_value(scanner),
176-
csv_scanner_get_current_value_len(scanner));
175+
FILTERX_STRING_DECLARE_ON_STACK(val,
176+
csv_scanner_get_current_value(scanner),
177+
csv_scanner_get_current_value_len(scanner));
177178

178179
gboolean ok = filterx_object_set_subscript(result, col, &val);
179180

@@ -186,9 +187,9 @@ _fill_object_col(FilterXFunctionParseCSV *self, FilterXObject *cols, gint64 inde
186187
static inline gboolean
187188
_fill_array_element(CSVScanner *scanner, FilterXObject *result)
188189
{
189-
const gchar *current_value = csv_scanner_get_current_value(scanner);
190-
gint current_value_len = csv_scanner_get_current_value_len(scanner);
191-
FilterXObject *val = filterx_string_new(current_value, current_value_len);
190+
FILTERX_STRING_DECLARE_ON_STACK(val,
191+
csv_scanner_get_current_value(scanner),
192+
csv_scanner_get_current_value_len(scanner));
192193

193194
gboolean ok = filterx_list_append(result, &val);
194195

modules/xml/filterx-parse-windows-eventlog-xml.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static gboolean
7272
_convert_to_dict(GMarkupParseContext *context, XmlElemContext *elem_context, GError **error)
7373
{
7474
const gchar *parent_elem_name = (const gchar *) g_markup_parse_context_get_element_stack(context)->next->data;
75-
FilterXObject *key = filterx_string_new(parent_elem_name, -1);
75+
FILTERX_STRING_DECLARE_ON_STACK(key, parent_elem_name, -1);
7676

7777
FilterXObject *dict_obj = filterx_object_create_dict(elem_context->parent_obj);
7878
if (!dict_obj)
@@ -106,7 +106,7 @@ _prepare_elem(const gchar *new_elem_name, XmlElemContext *last_elem_context, Xml
106106
{
107107
xml_elem_context_init(new_elem_context, last_elem_context->current_obj, NULL);
108108

109-
FilterXObject *new_elem_key = filterx_string_new(new_elem_name, -1);
109+
FILTERX_STRING_DECLARE_ON_STACK(new_elem_key, new_elem_name, -1);
110110
FilterXObject *existing_obj = NULL;
111111

112112
if (!filterx_object_is_key_set(new_elem_context->parent_obj, new_elem_key))
@@ -330,8 +330,8 @@ _text(FilterXGeneratorFunctionParseXml *s,
330330
return;
331331
}
332332

333-
FilterXObject *key = filterx_string_new(state->last_data_name->str, state->last_data_name->len);
334-
FilterXObject *text_obj = filterx_string_new(text, text_len);
333+
FILTERX_STRING_DECLARE_ON_STACK(key, state->last_data_name->str, state->last_data_name->len);
334+
FILTERX_STRING_DECLARE_ON_STACK(text_obj, text, text_len);
335335

336336
if (!filterx_object_set_subscript(elem_context->current_obj, key, &text_obj))
337337
{

0 commit comments

Comments
 (0)