]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
json: add support for using static const strings directly as JsonVariant objects
authorLennart Poettering <lennart@poettering.net>
Fri, 12 Oct 2018 14:46:38 +0000 (16:46 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 18 Oct 2018 14:44:51 +0000 (16:44 +0200)
This is a nice little optimization when using static const strings: we
can now use them directly as JsonVariant objecs, without any additional
allocation.

src/basic/json.c
src/basic/json.h
src/test/test-json.c

index f3db01eb993e736127dab60d9721f933fdf718a2..c6e4ea6aa805490e6906d7a16b1596e0629e8b83 100644 (file)
@@ -139,6 +139,23 @@ static bool json_source_equal(JsonSource *a, JsonSource *b) {
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(JsonSource*, json_source_unref);
 
+/* There are four kind of JsonVariant* pointers:
+ *
+ *    1. NULL
+ *    2. A 'regular' one, i.e. pointing to malloc() memory
+ *    3. A 'magic' one, i.e. one of the special JSON_VARIANT_MAGIC_XYZ values, that encode a few very basic values directly in the pointer.
+ *    4. A 'const string' one, i.e. a pointer to a const string.
+ *
+ * The four kinds of pointers can be discerned like this:
+ *
+ *    Detecting #1 is easy, just compare with NULL. Detecting #3 is similarly easy: all magic pointers are below
+ *    _JSON_VARIANT_MAGIC_MAX (which is pretty low, within the first memory page, which is special on Linux and other
+ *    OSes, as it is a faulting page). In order to discern #2 and #4 we check the lowest bit. If it's off it's #2,
+ *    otherwise #4. This makes use of the fact that malloc() will return "maximum aligned" memory, which definitely
+ *    means the pointer is even. This means we can use the uneven pointers to reference static strings, as long as we
+ *    make sure that all static strings used like this are aligned to 2 (or higher), and that we mask the bit on
+ *    access. The JSON_VARIANT_STRING_CONST() macro encodes strings as JsonVariant* pointers, with the bit set. */
+
 static bool json_variant_is_magic(const JsonVariant *v) {
         if (!v)
                 return false;
@@ -146,6 +163,25 @@ static bool json_variant_is_magic(const JsonVariant *v) {
         return v < _JSON_VARIANT_MAGIC_MAX;
 }
 
+static bool json_variant_is_const_string(const JsonVariant *v) {
+
+        if (v < _JSON_VARIANT_MAGIC_MAX)
+                return false;
+
+        /* A proper JsonVariant is aligned to whatever malloc() aligns things too, which is definitely not uneven. We
+         * hence use all uneven pointers as indicators for const strings. */
+
+        return (((uintptr_t) v) & 1) != 0;
+}
+
+static bool json_variant_is_regular(const JsonVariant *v) {
+
+        if (v < _JSON_VARIANT_MAGIC_MAX)
+                return false;
+
+        return (((uintptr_t) v) & 1) == 0;
+}
+
 static JsonVariant *json_variant_dereference(JsonVariant *v) {
 
         /* Recursively dereference variants that are references to other variants */
@@ -153,7 +189,7 @@ static JsonVariant *json_variant_dereference(JsonVariant *v) {
         if (!v)
                 return NULL;
 
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return v;
 
         if (!v->is_reference)
@@ -168,7 +204,7 @@ static uint16_t json_variant_depth(JsonVariant *v) {
         if (!v)
                 return 0;
 
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return 0;
 
         return v->depth;
@@ -226,7 +262,7 @@ static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) {
         if (!v)
                 return NULL;
 
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return v;
 
         if (v->source || v->line > 0 || v->column > 0)
@@ -420,7 +456,7 @@ static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) {
         assert(v);
         assert(from);
 
-        if (json_variant_is_magic(from))
+        if (!json_variant_is_regular(from))
                 return;
 
         v->line = from->line;
@@ -610,7 +646,7 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
 static void json_variant_free_inner(JsonVariant *v) {
         assert(v);
 
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return;
 
         json_source_unref(v->source);
@@ -631,7 +667,7 @@ static void json_variant_free_inner(JsonVariant *v) {
 JsonVariant *json_variant_ref(JsonVariant *v) {
         if (!v)
                 return NULL;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return v;
 
         if (v->is_embedded)
@@ -647,7 +683,7 @@ JsonVariant *json_variant_ref(JsonVariant *v) {
 JsonVariant *json_variant_unref(JsonVariant *v) {
         if (!v)
                 return NULL;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return NULL;
 
         if (v->is_embedded)
@@ -681,6 +717,13 @@ const char *json_variant_string(JsonVariant *v) {
                 return "";
         if (json_variant_is_magic(v))
                 goto mismatch;
+        if (json_variant_is_const_string(v)) {
+                uintptr_t p = (uintptr_t) v;
+
+                assert((p & 1) != 0);
+                return (const char*) (p ^ 1U);
+        }
+
         if (v->is_reference)
                 return json_variant_string(v->reference);
         if (v->type != JSON_VARIANT_STRING)
@@ -700,7 +743,7 @@ bool json_variant_boolean(JsonVariant *v) {
                 return true;
         if (v == JSON_VARIANT_MAGIC_FALSE)
                 return false;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (v->type != JSON_VARIANT_BOOLEAN)
                 goto mismatch;
@@ -721,7 +764,7 @@ intmax_t json_variant_integer(JsonVariant *v) {
             v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
             v == JSON_VARIANT_MAGIC_ZERO_REAL)
                 return 0;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (v->is_reference)
                 return json_variant_integer(v->reference);
@@ -769,7 +812,7 @@ uintmax_t json_variant_unsigned(JsonVariant *v) {
             v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
             v == JSON_VARIANT_MAGIC_ZERO_REAL)
                 return 0;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (v->is_reference)
                 return json_variant_integer(v->reference);
@@ -817,7 +860,7 @@ long double json_variant_real(JsonVariant *v) {
             v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
             v == JSON_VARIANT_MAGIC_ZERO_REAL)
                 return 0.0;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (v->is_reference)
                 return json_variant_real(v->reference);
@@ -867,7 +910,7 @@ bool json_variant_is_negative(JsonVariant *v) {
             v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
             v == JSON_VARIANT_MAGIC_ZERO_REAL)
                 return false;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (v->is_reference)
                 return json_variant_is_negative(v->reference);
@@ -901,6 +944,9 @@ JsonVariantType json_variant_type(JsonVariant *v) {
         if (!v)
                 return _JSON_VARIANT_TYPE_INVALID;
 
+        if (json_variant_is_const_string(v))
+                return JSON_VARIANT_STRING;
+
         if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE)
                 return JSON_VARIANT_BOOLEAN;
 
@@ -937,6 +983,10 @@ bool json_variant_has_type(JsonVariant *v, JsonVariantType type) {
         if (rt == type)
                 return true;
 
+        /* If it's a const string, then it only can be a string, and if it is not, it's not */
+        if (json_variant_is_const_string(v))
+                return false;
+
         /* All three magic zeroes qualify as integer, unsigned and as real */
         if ((v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) &&
             IN_SET(type, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL, JSON_VARIANT_NUMBER))
@@ -980,7 +1030,7 @@ size_t json_variant_elements(JsonVariant *v) {
         if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
             v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
                 return 0;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
                 goto mismatch;
@@ -1000,7 +1050,7 @@ JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) {
         if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
             v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
                 return NULL;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
                 goto mismatch;
@@ -1025,7 +1075,7 @@ JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVaria
                 goto not_found;
         if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
                 goto not_found;
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 goto mismatch;
         if (v->type != JSON_VARIANT_OBJECT)
                 goto mismatch;
@@ -1174,13 +1224,13 @@ int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *r
         assert_return(v, -EINVAL);
 
         if (ret_source)
-                *ret_source = json_variant_is_magic(v) || !v->source ? NULL : v->source->name;
+                *ret_source = json_variant_is_regular(v) && v->source ? v->source->name : NULL;
 
         if (ret_line)
-                *ret_line = json_variant_is_magic(v) ? 0 : v->line;
+                *ret_line = json_variant_is_regular(v) ? v->line : 0;
 
         if (ret_column)
-                *ret_column = json_variant_is_magic(v) ? 0 : v->column;
+                *ret_column = json_variant_is_regular(v) ? v->column : 0;
 
         return 0;
 }
@@ -1191,7 +1241,7 @@ static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace
         if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY))
                 return 0;
 
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return 0;
 
         if (!v->source && v->line == 0 && v->column == 0)
@@ -1632,7 +1682,7 @@ static bool json_single_ref(JsonVariant *v) {
 
         /* Checks whether the caller is the single owner of the object, i.e. can get away with changing it */
 
-        if (json_variant_is_magic(v))
+        if (!json_variant_is_regular(v))
                 return false;
 
         if (v->is_embedded)
@@ -1659,7 +1709,7 @@ static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned
         if (source && column > source->max_column)
                 source->max_column = column;
 
-        if (json_variant_is_magic(*v)) {
+        if (!json_variant_is_regular(*v)) {
 
                 if (!source && line == 0 && column == 0)
                         return 0;
@@ -1683,7 +1733,7 @@ static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned
         if (r < 0)
                 return r;
 
-        assert(!json_variant_is_magic(w));
+        assert(json_variant_is_regular(w));
         assert(!w->is_embedded);
         assert(w->n_ref == 1);
         assert(!w->source);
index 1f06fe225731bc0eb79d09dde01b7ac544b6d9cb..42be60e0554d813518adada556899f4bcb81fb15 100644 (file)
@@ -270,5 +270,14 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi
                         : -abs(_e);                                     \
         })
 
+#define JSON_VARIANT_STRING_CONST(x) _JSON_VARIANT_STRING_CONST(UNIQ, (x))
+
+#define _JSON_VARIANT_STRING_CONST(xq, x)                                       \
+        ({                                                              \
+                __attribute__((aligned(2))) static const char UNIQ_T(json_string_const, xq)[] = (x); \
+                assert((((uintptr_t) UNIQ_T(json_string_const, xq)) & 1) == 0); \
+                (JsonVariant*) ((uintptr_t) UNIQ_T(json_string_const, xq) + 1); \
+        })
+
 const char *json_variant_type_to_string(JsonVariantType t);
 JsonVariantType json_variant_type_from_string(const char *s);
index bcf94b211c2e4752c536364a9f7014284f938667..6a57f88882e9072709c94de6909566531b59a2e0 100644 (file)
@@ -292,6 +292,7 @@ static void test_build(void) {
                                                   JSON_BUILD_STRING(NULL),
                                                   JSON_BUILD_NULL,
                                                   JSON_BUILD_INTEGER(77),
+                                                  JSON_BUILD_ARRAY(JSON_BUILD_VARIANT(JSON_VARIANT_STRING_CONST("foobar")), JSON_BUILD_VARIANT(JSON_VARIANT_STRING_CONST("zzz"))),
                                                   JSON_BUILD_STRV(STRV_MAKE("one", "two", "three", "four")))) >= 0);
 
         assert_se(json_variant_format(a, 0, &s) >= 0);
@@ -355,12 +356,11 @@ static void test_source(void) {
 }
 
 static void test_depth(void) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *k = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         unsigned i;
         int r;
 
-        assert_se(json_variant_new_string(&k, "hallo") >= 0);
-        v = json_variant_ref(k);
+        v = JSON_VARIANT_STRING_CONST("start");
 
         /* Let's verify that the maximum depth checks work */
 
@@ -371,7 +371,7 @@ static void test_depth(void) {
                 if (i & 1)
                         r = json_variant_new_array(&w, &v, 1);
                 else
-                        r = json_variant_new_object(&w, (JsonVariant*[]) { k, v }, 2);
+                        r = json_variant_new_object(&w, (JsonVariant*[]) { JSON_VARIANT_STRING_CONST("key"), v }, 2);
                 if (r == -ELNRNG) {
                         log_info("max depth at %u", i);
                         break;
@@ -384,6 +384,7 @@ static void test_depth(void) {
         }
 
         json_variant_dump(v, 0, stdout, NULL);
+        fputs("\n", stdout);
 }
 
 int main(int argc, char *argv[]) {