diff options
Diffstat (limited to 'json-glib')
-rw-r--r-- | json-glib/json-array.c | 48 | ||||
-rw-r--r-- | json-glib/json-builder.c | 106 | ||||
-rw-r--r-- | json-glib/json-builder.h | 2 | ||||
-rw-r--r-- | json-glib/json-node.c | 127 | ||||
-rw-r--r-- | json-glib/json-object.c | 51 | ||||
-rw-r--r-- | json-glib/json-parser.c | 108 | ||||
-rw-r--r-- | json-glib/json-parser.h | 2 | ||||
-rw-r--r-- | json-glib/json-types-private.h | 7 | ||||
-rw-r--r-- | json-glib/json-types.h | 13 | ||||
-rw-r--r-- | json-glib/json-value.c | 23 | ||||
-rw-r--r-- | json-glib/tests/node.c | 320 |
11 files changed, 802 insertions, 5 deletions
diff --git a/json-glib/json-array.c b/json-glib/json-array.c index af67b4a..05cde4a 100644 --- a/json-glib/json-array.c +++ b/json-glib/json-array.c @@ -134,6 +134,54 @@ json_array_unref (JsonArray *array) } /** + * json_array_seal: + * @array: a #JsonArray + * + * Seals the #JsonArray, making it immutable to further changes. This will + * recursively seal all elements in the array too. + * + * If the @array is already immutable, this is a no-op. + * + * Since: UNRELEASED + */ +void +json_array_seal (JsonArray *array) +{ + guint i; + + g_return_if_fail (array != NULL); + g_return_if_fail (array->ref_count > 0); + + if (array->immutable) + return; + + /* Propagate to all members. */ + for (i = 0; i < array->elements->len; i++) + json_node_seal (g_ptr_array_index (array->elements, i)); + + array->immutable = TRUE; +} + +/** + * json_array_is_immutable: + * @array: a #JsonArray + * + * Check whether the given @array has been marked as immutable by calling + * json_array_seal() on it. + * + * Since: UNRELEASED + * Returns: %TRUE if the @array is immutable + */ +gboolean +json_array_is_immutable (JsonArray *array) +{ + g_return_val_if_fail (array != NULL, FALSE); + g_return_val_if_fail (array->ref_count > 0, FALSE); + + return array->immutable; +} + +/** * json_array_get_elements: * @array: a #JsonArray * diff --git a/json-glib/json-builder.c b/json-glib/json-builder.c index d3a5cef..6531b0c 100644 --- a/json-glib/json-builder.c +++ b/json-glib/json-builder.c @@ -2,6 +2,7 @@ * * This file is part of JSON-GLib * Copyright (C) 2010 Luca Bruno <lethalman88@gmail.com> + * Copyright (C) 2015 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,6 +19,7 @@ * * Author: * Luca Bruno <lethalman88@gmail.com> + * Philip Withnall <philip.withnall@collabora.co.uk> */ /** @@ -50,8 +52,17 @@ struct _JsonBuilderPrivate { GQueue *stack; JsonNode *root; + gboolean immutable; }; +enum +{ + PROP_IMMUTABLE = 1, + PROP_LAST +}; + +static GParamSpec *builder_props[PROP_LAST] = { NULL, }; + typedef enum { JSON_BUILDER_MODE_OBJECT, @@ -133,11 +144,70 @@ json_builder_finalize (GObject *gobject) } static void +json_builder_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + JsonBuilderPrivate *priv = JSON_BUILDER (gobject)->priv; + + switch (prop_id) + { + case PROP_IMMUTABLE: + /* Construct-only. */ + priv->immutable = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +json_builder_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + JsonBuilderPrivate *priv = JSON_BUILDER (gobject)->priv; + + switch (prop_id) + { + case PROP_IMMUTABLE: + g_value_set_boolean (value, priv->immutable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void json_builder_class_init (JsonBuilderClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + /** + * JsonBuilder:immutable: + * + * Whether the #JsonNode tree built by the #JsonBuilder should be immutable + * when created. Making the output immutable on creation avoids the expense + * of traversing it to make it immutable later. + * + * Since: UNRELEASED + */ + builder_props[PROP_IMMUTABLE] = + g_param_spec_boolean ("immutable", + "Immutable Output", + "Whether the builder output is immutable.", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + gobject_class->set_property = json_builder_set_property; + gobject_class->get_property = json_builder_get_property; gobject_class->finalize = json_builder_finalize; + + g_object_class_install_properties (gobject_class, PROP_LAST, builder_props); } static void @@ -180,6 +250,21 @@ json_builder_new (void) } /** + * json_builder_new_immutable: + * + * Creates a new #JsonBuilder instance with its #JsonBuilder:immutable property + * set to %TRUE to create immutable output trees. + * + * Since: UNRELEASED + * Returns: (transfer full): a new #JsonBuilder + */ +JsonBuilder * +json_builder_new_immutable (void) +{ + return g_object_new (JSON_TYPE_BUILDER, "immutable", TRUE, NULL); +} + +/** * json_builder_get_root: * @builder: a #JsonBuilder * @@ -199,6 +284,11 @@ json_builder_get_root (JsonBuilder *builder) if (builder->priv->root) root = json_node_copy (builder->priv->root); + /* Sanity check. */ + g_return_val_if_fail (!builder->priv->immutable || + root == NULL || + json_node_is_immutable (root), NULL); + return root; } @@ -292,10 +382,16 @@ json_builder_end_object (JsonBuilder *builder) state = g_queue_pop_head (builder->priv->stack); + if (builder->priv->immutable) + json_object_seal (state->data.object); + if (g_queue_is_empty (builder->priv->stack)) { builder->priv->root = json_node_new (JSON_NODE_OBJECT); json_node_take_object (builder->priv->root, json_object_ref (state->data.object)); + + if (builder->priv->immutable) + json_node_seal (builder->priv->root); } json_builder_state_free (state); @@ -378,10 +474,16 @@ json_builder_end_array (JsonBuilder *builder) state = g_queue_pop_head (builder->priv->stack); + if (builder->priv->immutable) + json_array_seal (state->data.array); + if (g_queue_is_empty (builder->priv->stack)) { builder->priv->root = json_node_new (JSON_NODE_ARRAY); json_node_take_array (builder->priv->root, json_array_ref (state->data.array)); + + if (builder->priv->immutable) + json_node_seal (builder->priv->root); } json_builder_state_free (state); @@ -444,6 +546,10 @@ json_builder_add_value (JsonBuilder *builder, g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); + + if (builder->priv->immutable) + json_node_seal (node); + switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: diff --git a/json-glib/json-builder.h b/json-glib/json-builder.h index 45d4b5d..b35230e 100644 --- a/json-glib/json-builder.h +++ b/json-glib/json-builder.h @@ -80,6 +80,8 @@ GType json_builder_get_type (void) G_GNUC_CONST; JSON_AVAILABLE_IN_1_0 JsonBuilder *json_builder_new (void); +JSON_AVAILABLE_IN_1_2 +JsonBuilder *json_builder_new_immutable (void); JSON_AVAILABLE_IN_1_0 JsonNode *json_builder_get_root (JsonBuilder *builder); JSON_AVAILABLE_IN_1_0 diff --git a/json-glib/json-node.c b/json-glib/json-node.c index fb95f42..092a27f 100644 --- a/json-glib/json-node.c +++ b/json-glib/json-node.c @@ -3,6 +3,7 @@ * This file is part of JSON-GLib * Copyright (C) 2007 OpenedHand Ltd. * Copyright (C) 2009 Intel Corp. + * Copyright (C) 2015 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,6 +20,7 @@ * * Author: * Emmanuele Bassi <ebassi@linux.intel.com> + * Philip Withnall <philip.withnall@collabora.co.uk> */ #include "config.h" @@ -44,6 +46,15 @@ * #JsonObject or the #JsonArray using json_node_get_object() or * json_node_get_array() respectively, and then retrieve the nodes * they contain. + * + * A #JsonNode may be marked as immutable using json_node_seal(). This marks the + * node and all its descendents as read-only, and means that subsequent calls to + * setter functions (such as json_node_set_array()) on them will abort as a + * programmer error. By marking a node tree as immutable, it may be referenced + * in multiple places and its hash value cached for fast lookups, without the + * possibility of a value deep within the tree changing and affecting hash + * values. Immutable #JsonNodes may be passed to functions which retain a + * reference to them without needing to take a copy. */ G_DEFINE_BOXED_TYPE (JsonNode, json_node, json_node_copy, json_node_free); @@ -367,8 +378,12 @@ json_node_new (JsonNodeType type) * json_node_copy: * @node: a #JsonNode * - * Copies @node. If the node contains complex data types then the reference - * count of the objects is increased. + * Copies @node. If the node contains complex data types, their reference + * counts are increased, regardless of whether the node is mutable or + * immutable. + * + * The copy will be immutable if, and only if, @node is immutable. However, + * there should be no need to copy an immutable node. * * Return value: (transfer full): the copied #JsonNode */ @@ -381,6 +396,10 @@ json_node_copy (JsonNode *node) copy = g_slice_new0 (JsonNode); copy->type = node->type; + copy->immutable = node->immutable; + + if (node->immutable) + g_debug ("Copying immutable JsonNode %p", node); switch (copy->type) { @@ -415,6 +434,8 @@ json_node_copy (JsonNode *node) * Sets @objects inside @node. The reference count of @object is increased. * * If @object is %NULL, the node’s existing object is cleared. + * + * It is an error to call this on an immutable node. */ void json_node_set_object (JsonNode *node, @@ -422,6 +443,7 @@ json_node_set_object (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT); + g_return_if_fail (!node->immutable); if (node->data.object != NULL) json_object_unref (node->data.object); @@ -438,6 +460,8 @@ json_node_set_object (JsonNode *node, * @object: (transfer full): a #JsonObject * * Sets @object inside @node. The reference count of @object is not increased. + * + * It is an error to call this on an immutable node. */ void json_node_take_object (JsonNode *node, @@ -445,6 +469,7 @@ json_node_take_object (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT); + g_return_if_fail (!node->immutable); if (node->data.object) { @@ -499,7 +524,9 @@ json_node_dup_object (JsonNode *node) * @node: a #JsonNode initialized to %JSON_NODE_ARRAY * @array: a #JsonArray * - * Sets @array inside @node and increases the #JsonArray reference count + * Sets @array inside @node and increases the #JsonArray reference count. + * + * It is an error to call this on an immutable node. */ void json_node_set_array (JsonNode *node, @@ -507,6 +534,7 @@ json_node_set_array (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY); + g_return_if_fail (!node->immutable); if (node->data.array) json_array_unref (node->data.array); @@ -523,6 +551,8 @@ json_node_set_array (JsonNode *node, * @array: (transfer full): a #JsonArray * * Sets @array into @node without increasing the #JsonArray reference count. + * + * It is an error to call this on an immutable node. */ void json_node_take_array (JsonNode *node, @@ -530,6 +560,7 @@ json_node_take_array (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY); + g_return_if_fail (!node->immutable); if (node->data.array) { @@ -627,7 +658,9 @@ json_node_get_value (JsonNode *node, * @node: a #JsonNode initialized to %JSON_NODE_VALUE * @value: the #GValue to set * - * Sets @value inside @node. The passed #GValue is copied into the #JsonNode + * Sets @value inside @node. The passed #GValue is copied into the #JsonNode. + * + * It is an error to call this on an immutable node. */ void json_node_set_value (JsonNode *node, @@ -636,6 +669,7 @@ json_node_set_value (JsonNode *node, g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); g_return_if_fail (G_VALUE_TYPE (value) != G_TYPE_INVALID); + g_return_if_fail (!node->immutable); if (node->data.value == NULL) node->data.value = json_value_alloc (); @@ -697,6 +731,72 @@ json_node_free (JsonNode *node) } /** + * json_node_seal: + * @node: a #JsonNode + * + * Seals the #JsonNode, making it immutable to further changes. In order to be + * sealed, the @node must have a type and value set. The value will be + * recursively sealed — if the node holds an object, that #JsonObject will be + * sealed, etc. + * + * If the @node is already immutable, this is a no-op. + * + * Since: UNRELEASED + */ +void +json_node_seal (JsonNode *node) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (node->type >= JSON_NODE_OBJECT && + node->type <= JSON_NODE_NULL); + + if (node->immutable) + return; + + switch (node->type) + { + case JSON_NODE_OBJECT: + g_return_if_fail (node->data.object != NULL); + json_object_seal (node->data.object); + break; + case JSON_NODE_ARRAY: + g_return_if_fail (node->data.array != NULL); + json_array_seal (node->data.array); + break; + case JSON_NODE_NULL: + break; + case JSON_NODE_VALUE: + g_return_if_fail (node->data.value != NULL); + json_value_seal (node->data.value); + break; + default: + g_assert_not_reached (); + } + + node->immutable = TRUE; +} + +/** + * json_node_is_immutable: + * @node: a #JsonNode + * + * Check whether the given @node has been marked as immutable by calling + * json_node_seal() on it. + * + * Since: UNRELEASED + * Returns: %TRUE if the @node is immutable + */ +gboolean +json_node_is_immutable (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, FALSE); + g_return_val_if_fail (node->type >= JSON_NODE_OBJECT && + node->type <= JSON_NODE_NULL, FALSE); + + return node->immutable; +} + +/** * json_node_type_name: * @node: a #JsonNode * @@ -755,7 +855,10 @@ json_node_type_get_name (JsonNodeType node_type) * @node: a #JsonNode * @parent: (transfer none): the parent #JsonNode of @node * - * Sets the parent #JsonNode of @node + * Sets the parent #JsonNode of @node. + * + * It is an error to call this with an immutable @parent. @node may be + * immutable. * * Since: 0.8 */ @@ -764,6 +867,8 @@ json_node_set_parent (JsonNode *node, JsonNode *parent) { g_return_if_fail (node != NULL); + g_return_if_fail (parent == NULL || + !json_node_is_immutable (parent)); node->parent = parent; } @@ -792,6 +897,8 @@ json_node_get_parent (JsonNode *node) * * Sets @value as the string content of the @node, replacing any existing * content. + * + * It is an error to call this on an immutable node. */ void json_node_set_string (JsonNode *node, @@ -799,6 +906,7 @@ json_node_set_string (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + g_return_if_fail (!node->immutable); if (node->data.value == NULL) node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_STRING); @@ -854,6 +962,8 @@ json_node_dup_string (JsonNode *node) * * Sets @value as the integer content of the @node, replacing any existing * content. + * + * It is an error to call this on an immutable node. */ void json_node_set_int (JsonNode *node, @@ -861,6 +971,7 @@ json_node_set_int (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + g_return_if_fail (!node->immutable); if (node->data.value == NULL) node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_INT); @@ -905,6 +1016,8 @@ json_node_get_int (JsonNode *node) * * Sets @value as the double content of the @node, replacing any existing * content. + * + * It is an error to call this on an immutable node. */ void json_node_set_double (JsonNode *node, @@ -912,6 +1025,7 @@ json_node_set_double (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + g_return_if_fail (!node->immutable); if (node->data.value == NULL) node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_DOUBLE); @@ -956,6 +1070,8 @@ json_node_get_double (JsonNode *node) * * Sets @value as the boolean content of the @node, replacing any existing * content. + * + * It is an error to call this on an immutable node. */ void json_node_set_boolean (JsonNode *node, @@ -963,6 +1079,7 @@ json_node_set_boolean (JsonNode *node, { g_return_if_fail (node != NULL); g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + g_return_if_fail (!node->immutable); if (node->data.value == NULL) node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_BOOLEAN); diff --git a/json-glib/json-object.c b/json-glib/json-object.c index 214ffef..574d04d 100644 --- a/json-glib/json-object.c +++ b/json-glib/json-object.c @@ -117,6 +117,57 @@ json_object_unref (JsonObject *object) } } +/** + * json_object_seal: + * @object: a #JsonObject + * + * Seals the #JsonObject, making it immutable to further changes. This will + * recursively seal all members of the object too. + * + * If the @object is already immutable, this is a no-op. + * + * Since: UNRELEASED + */ +void +json_object_seal (JsonObject *object) +{ + JsonObjectIter iter; + JsonNode *node; + + g_return_if_fail (object != NULL); + g_return_if_fail (object->ref_count > 0); + + if (object->immutable) + return; + + /* Propagate to all members. */ + json_object_iter_init (&iter, object); + + while (json_object_iter_next (&iter, NULL, &node)) + json_node_seal (node); + + object->immutable = TRUE; +} + +/** + * json_object_is_immutable: + * @object: a #JsonObject + * + * Check whether the given @object has been marked as immutable by calling + * json_object_seal() on it. + * + * Since: UNRELEASED + * Returns: %TRUE if the @object is immutable + */ +gboolean +json_object_is_immutable (JsonObject *object) +{ + g_return_val_if_fail (object != NULL, FALSE); + g_return_val_if_fail (object->ref_count > 0, FALSE); + + return object->immutable; +} + static inline void object_set_member_internal (JsonObject *object, const gchar *member_name, diff --git a/json-glib/json-parser.c b/json-glib/json-parser.c index 812f038..8f1c40c 100644 --- a/json-glib/json-parser.c +++ b/json-glib/json-parser.c @@ -4,6 +4,7 @@ * * Copyright © 2007, 2008, 2009 OpenedHand Ltd * Copyright © 2009, 2010 Intel Corp. + * Copyright © 2015 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,6 +21,7 @@ * * Author: * Emmanuele Bassi <ebassi@linux.intel.com> + * Philip Withnall <philip.withnall@collabora.co.uk> */ /** @@ -58,6 +60,7 @@ struct _JsonParserPrivate guint has_assignment : 1; guint is_filename : 1; + gboolean immutable : 1; }; static const gchar symbol_names[] = @@ -96,6 +99,14 @@ enum static guint parser_signals[LAST_SIGNAL] = { 0, }; +enum +{ + PROP_IMMUTABLE = 1, + PROP_LAST +}; + +static GParamSpec *parser_props[PROP_LAST] = { NULL, }; + G_DEFINE_QUARK (json-parser-error-quark, json_parser_error) G_DEFINE_TYPE_WITH_PRIVATE (JsonParser, json_parser, G_TYPE_OBJECT) @@ -148,14 +159,73 @@ json_parser_finalize (GObject *gobject) } static void +json_parser_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + JsonParserPrivate *priv = JSON_PARSER (gobject)->priv; + + switch (prop_id) + { + case PROP_IMMUTABLE: + /* Construct-only. */ + priv->immutable = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +json_parser_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + JsonParserPrivate *priv = JSON_PARSER (gobject)->priv; + + switch (prop_id) + { + case PROP_IMMUTABLE: + g_value_set_boolean (value, priv->immutable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void json_parser_class_init (JsonParserClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = json_parser_set_property; + gobject_class->get_property = json_parser_get_property; gobject_class->dispose = json_parser_dispose; gobject_class->finalize = json_parser_finalize; /** + * JsonParser:immutable: + * + * Whether the #JsonNode tree built by the #JsonParser should be immutable + * when created. Making the output immutable on creation avoids the expense + * of traversing it to make it immutable later. + * + * Since: UNRELEASED + */ + parser_props[PROP_IMMUTABLE] = + g_param_spec_boolean ("immutable", + "Immutable Output", + "Whether the parser output is immutable.", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (gobject_class, PROP_LAST, parser_props); + + /** * JsonParser::parse-start: * @parser: the #JsonParser that received the signal * @@ -424,6 +494,9 @@ json_parse_value (JsonParser *parser, break; } + if (priv->immutable && *node != NULL) + json_node_seal (*node); + return G_TOKEN_NONE; } @@ -522,6 +595,8 @@ json_parse_array (JsonParser *parser, JSON_NOTE (PARSER, "Array element %d completed", idx); json_node_set_parent (element, priv->current_node); + if (priv->immutable) + json_node_seal (element); json_array_add_element (array, element); g_signal_emit (parser, parser_signals[ARRAY_ELEMENT], 0, @@ -535,7 +610,13 @@ json_parse_array (JsonParser *parser, array_done: json_scanner_get_next_token (scanner); + /* We can guarantee that all the array elements are immutable, so we + * can skip the formal loop over them to seal them again. */ + array->immutable = TRUE; + json_node_take_array (priv->current_node, array); + if (priv->immutable) + json_node_seal (priv->current_node); json_node_set_parent (priv->current_node, old_current); g_signal_emit (parser, parser_signals[ARRAY_END], 0, array); @@ -697,6 +778,8 @@ json_parse_object (JsonParser *parser, JSON_NOTE (PARSER, "Object member '%s' completed", name); json_node_set_parent (member, priv->current_node); + if (priv->immutable) + json_node_seal (member); json_object_set_member (object, name, member); g_signal_emit (parser, parser_signals[OBJECT_MEMBER], 0, @@ -710,7 +793,13 @@ json_parse_object (JsonParser *parser, json_scanner_get_next_token (scanner); + /* We can guarantee that all the object members are immutable, so we + * can skip the formal loop over them to seal them again. */ + object->immutable = TRUE; + json_node_take_object (priv->current_node, object); + if (priv->immutable) + json_node_seal (priv->current_node); json_node_set_parent (priv->current_node, old_current); g_signal_emit (parser, parser_signals[OBJECT_END], 0, object); @@ -876,6 +965,21 @@ json_parser_new (void) return g_object_new (JSON_TYPE_PARSER, NULL); } +/** + * json_parser_new_immutable: + * + * Creates a new #JsonParser instance with its #JsonParser:immutable property + * set to %TRUE to create immutable output trees. + * + * Since: UNRELEASED + * Returns: (transfer full): a new #JsonParser + */ +JsonParser * +json_parser_new_immutable (void) +{ + return g_object_new (JSON_TYPE_PARSER, "immutable", TRUE, NULL); +} + static gboolean json_parser_load (JsonParser *parser, const gchar *data, @@ -1097,6 +1201,10 @@ json_parser_get_root (JsonParser *parser) { g_return_val_if_fail (JSON_IS_PARSER (parser), NULL); + /* Sanity check. */ + g_return_val_if_fail (!parser->priv->immutable || + json_node_is_immutable (parser->priv->root), NULL); + return parser->priv->root; } diff --git a/json-glib/json-parser.h b/json-glib/json-parser.h index a65558e..c7223d2 100644 --- a/json-glib/json-parser.h +++ b/json-glib/json-parser.h @@ -147,6 +147,8 @@ GType json_parser_get_type (void) G_GNUC_CONST; JSON_AVAILABLE_IN_1_0 JsonParser *json_parser_new (void); +JSON_AVAILABLE_IN_1_2 +JsonParser *json_parser_new_immutable (void); JSON_AVAILABLE_IN_1_0 gboolean json_parser_load_from_file (JsonParser *parser, const gchar *filename, diff --git a/json-glib/json-types-private.h b/json-glib/json-types-private.h index f9ac064..34a3160 100644 --- a/json-glib/json-types-private.h +++ b/json-glib/json-types-private.h @@ -43,6 +43,7 @@ struct _JsonNode { /*< private >*/ JsonNodeType type; + gboolean immutable : 1; union { JsonObject *object; @@ -69,6 +70,7 @@ struct _JsonValue JsonValueType type; volatile gint ref_count; + gboolean immutable : 1; union { gint64 v_int; @@ -83,6 +85,7 @@ struct _JsonArray GPtrArray *elements; volatile gint ref_count; + gboolean immutable : 1; }; struct _JsonObject @@ -93,6 +96,7 @@ struct _JsonObject GList *members_ordered; volatile gint ref_count; + gboolean immutable : 1; }; typedef struct @@ -146,6 +150,9 @@ void json_value_set_string (JsonValue *value, G_GNUC_INTERNAL const gchar * json_value_get_string (const JsonValue *value); +G_GNUC_INTERNAL +void json_value_seal (JsonValue *value); + G_END_DECLS #endif /* __JSON_TYPES_PRIVATE_H__ */ diff --git a/json-glib/json-types.h b/json-glib/json-types.h index 1ee84ff..5f2a084 100644 --- a/json-glib/json-types.h +++ b/json-glib/json-types.h @@ -276,6 +276,10 @@ gboolean json_node_get_boolean (JsonNode *node); JSON_AVAILABLE_IN_1_0 gboolean json_node_is_null (JsonNode *node); +JSON_AVAILABLE_IN_1_2 +void json_node_seal (JsonNode *node); +gboolean json_node_is_immutable (JsonNode *node); + /* * JsonObject */ @@ -368,6 +372,11 @@ void json_object_foreach_member (JsonObject *object, JsonObjectForeach func, gpointer data); +JSON_AVAILABLE_IN_1_2 +void json_object_seal (JsonObject *object); +JSON_AVAILABLE_IN_1_2 +gboolean json_object_is_immutable (JsonObject *object); + /** * JsonObjectIter: * @@ -467,6 +476,10 @@ JSON_AVAILABLE_IN_1_0 void json_array_foreach_element (JsonArray *array, JsonArrayForeach func, gpointer data); +JSON_AVAILABLE_IN_1_2 +void json_array_seal (JsonArray *array); +JSON_AVAILABLE_IN_1_2 +gboolean json_array_is_immutable (JsonArray *array); #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC (JsonArray, json_array_unref) diff --git a/json-glib/json-value.c b/json-glib/json-value.c index b6d47e1..148cf8d 100644 --- a/json-glib/json-value.c +++ b/json-glib/json-value.c @@ -2,6 +2,7 @@ * * This file is part of JSON-GLib * Copyright (C) 2012 Emmanuele Bassi <ebassi@gnome.org> + * Copyright (C) 2015 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,6 +19,7 @@ * * Author: * Emmanuele Bassi <ebassi@linux.intel.com> + * Philip Withnall <philip.withnall@collabora.co.uk> */ #include "config.h" @@ -165,12 +167,32 @@ json_value_free (JsonValue *value) } } +/** + * json_value_seal: + * @value: a #JsonValue + * + * Seals the #JsonValue, making it immutable to further changes. + * + * If the @value is already immutable, this is a no-op. + * + * Since: UNRELEASED + */ +void +json_value_seal (JsonValue *value) +{ + g_return_if_fail (JSON_VALUE_IS_VALID (value)); + g_return_if_fail (value->ref_count > 0); + + value->immutable = TRUE; +} + #define _JSON_VALUE_DEFINE_SET(Type,EType,CType,VField) \ void \ json_value_set_##Type (JsonValue *value, CType VField) \ { \ g_return_if_fail (JSON_VALUE_IS_VALID (value)); \ g_return_if_fail (JSON_VALUE_HOLDS (value, JSON_VALUE_##EType)); \ + g_return_if_fail (!value->immutable); \ \ value->data.VField = VField; \ \ @@ -202,6 +224,7 @@ json_value_set_string (JsonValue *value, { g_return_if_fail (JSON_VALUE_IS_VALID (value)); g_return_if_fail (JSON_VALUE_HOLDS_STRING (value)); + g_return_if_fail (!value->immutable); g_free (value->data.v_str); value->data.v_str = g_strdup (v_str); diff --git a/json-glib/tests/node.c b/json-glib/tests/node.c index 6aad5bc..23bda63 100644 --- a/json-glib/tests/node.c +++ b/json-glib/tests/node.c @@ -242,6 +242,311 @@ test_gvalue_autopromotion (void) json_node_free (node); } +/* Test that creating then sealing a node containing an int causes it to be + * immutable. */ +static void +test_seal_int (void) +{ + JsonNode *node = NULL; + + node = json_node_init_int (json_node_alloc (), 1); + + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + json_node_free (node); +} + +/* Test that creating then sealing a node containing a double causes it to be + * immutable. */ +static void +test_seal_double (void) +{ + JsonNode *node = NULL; + + node = json_node_init_double (json_node_alloc (), 15.2); + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + json_node_free (node); +} + +/* Test that creating then sealing a node containing a boolean causes it to be + * immutable. */ +static void +test_seal_boolean (void) +{ + JsonNode *node = NULL; + + node = json_node_init_boolean (json_node_alloc (), TRUE); + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + json_node_free (node); +} + +/* Test that creating then sealing a node containing a string causes it to be + * immutable. */ +static void +test_seal_string (void) +{ + JsonNode *node = NULL; + + node = json_node_init_string (json_node_alloc (), "hi there"); + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + json_node_free (node); +} + +/* Test that creating then sealing a node containing a null causes it to be + * immutable. */ +static void +test_seal_null (void) +{ + JsonNode *node = NULL; + + node = json_node_init_null (json_node_alloc ()); + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + json_node_free (node); +} + +/* Test that creating then sealing a node containing an object causes it to be + * immutable. */ +static void +test_seal_object (void) +{ + JsonNode *node = NULL; + JsonObject *object = NULL; + + object = json_object_new (); + node = json_node_init_object (json_node_alloc (), object); + + g_assert_false (json_object_is_immutable (object)); + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + g_assert_true (json_object_is_immutable (object)); + + json_node_free (node); + json_object_unref (object); +} + +/* Test that creating then sealing a node containing an array causes it to be + * immutable. */ +static void +test_seal_array (void) +{ + JsonNode *node = NULL; + JsonArray *array = NULL; + + array = json_array_new (); + node = json_node_init_array (json_node_alloc (), array); + + g_assert_false (json_array_is_immutable (array)); + g_assert_false (json_node_is_immutable (node)); + json_node_seal (node); + g_assert_true (json_node_is_immutable (node)); + g_assert_true (json_array_is_immutable (array)); + + json_node_free (node); + json_array_unref (array); +} + +/* Test that an immutable node containing an int cannot be modified. */ +static void +test_immutable_int (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + + node = json_node_init_int (json_node_alloc (), 5); + json_node_seal (node); + + /* Boom. */ + json_node_set_int (node, 1); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_int: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node containing a double cannot be modified. */ +static void +test_immutable_double (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + + node = json_node_init_double (json_node_alloc (), 5.6); + json_node_seal (node); + + /* Boom. */ + json_node_set_double (node, 1.1); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_double: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node containing a boolean cannot be modified. */ +static void +test_immutable_boolean (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + + node = json_node_init_boolean (json_node_alloc (), TRUE); + json_node_seal (node); + + /* Boom. */ + json_node_set_boolean (node, FALSE); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_boolean: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node containing a string cannot be modified. */ +static void +test_immutable_string (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + + node = json_node_init_string (json_node_alloc (), "bonghits"); + json_node_seal (node); + + /* Boom. */ + json_node_set_string (node, "asdasd"); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_string: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node containing an object cannot be modified. */ +static void +test_immutable_object (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + + node = json_node_init_object (json_node_alloc (), json_object_new ()); + json_node_seal (node); + + /* Boom. */ + json_node_set_object (node, json_object_new ()); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_object: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node containing an array cannot be modified. */ +static void +test_immutable_array (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + + node = json_node_init_array (json_node_alloc (), json_array_new ()); + json_node_seal (node); + + /* Boom. */ + json_node_set_array (node, json_array_new ()); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_array: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node containing a value cannot be modified. */ +static void +test_immutable_value (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + GValue val = G_VALUE_INIT; + + node = json_node_init_int (json_node_alloc (), 5); + json_node_seal (node); + + /* Boom. */ + g_value_init (&val, G_TYPE_INT); + g_value_set_int (&val, 50); + json_node_set_value (node, &val); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_value: " + "assertion '!node->immutable' failed*"); +} + +/* Test that an immutable node can be reparented but not to an immutable + * parent. */ +static void +test_immutable_parent (void) +{ + if (g_test_subprocess ()) + { + JsonNode *node = NULL; + JsonNode *parent_mutable = NULL; + JsonNode *parent_immutable = NULL; + JsonObject *object_mutable = NULL; + JsonObject *object_immutable = NULL; + + node = json_node_init_int (json_node_alloc (), 5); + json_node_seal (node); + + object_mutable = json_object_new (); + object_immutable = json_object_new (); + + parent_mutable = json_node_init_object (json_node_alloc (), + object_mutable); + parent_immutable = json_node_init_object (json_node_alloc (), + object_immutable); + + json_node_seal (parent_immutable); + + /* Can we reparent the immutable node? */ + json_object_set_member (object_mutable, "test", node); + json_node_set_parent (node, parent_mutable); + + json_object_remove_member (object_mutable, "test"); + json_node_set_parent (node, NULL); + + /* Boom. */ + json_node_set_parent (node, parent_immutable); + } + + g_test_trap_subprocess (NULL, 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_parent: *"); +} + int main (int argc, char *argv[]) @@ -260,6 +565,21 @@ main (int argc, g_test_add_func ("/nodes/get/double", test_get_double); g_test_add_func ("/nodes/gvalue", test_gvalue); g_test_add_func ("/nodes/gvalue/autopromotion", test_gvalue_autopromotion); + g_test_add_func ("/nodes/seal/int", test_seal_int); + g_test_add_func ("/nodes/seal/double", test_seal_double); + g_test_add_func ("/nodes/seal/boolean", test_seal_boolean); + g_test_add_func ("/nodes/seal/string", test_seal_string); + g_test_add_func ("/nodes/seal/null", test_seal_null); + g_test_add_func ("/nodes/seal/object", test_seal_object); + g_test_add_func ("/nodes/seal/array", test_seal_array); + g_test_add_func ("/nodes/immutable/int", test_immutable_int); + g_test_add_func ("/nodes/immutable/double", test_immutable_double); + g_test_add_func ("/nodes/immutable/boolean", test_immutable_boolean); + g_test_add_func ("/nodes/immutable/string", test_immutable_string); + g_test_add_func ("/nodes/immutable/object", test_immutable_object); + g_test_add_func ("/nodes/immutable/array", test_immutable_array); + g_test_add_func ("/nodes/immutable/value", test_immutable_value); + g_test_add_func ("/nodes/immutable/parent", test_immutable_parent); return g_test_run (); } |