Index: src/cson_amalgamation.c ================================================================== --- src/cson_amalgamation.c +++ src/cson_amalgamation.c @@ -1427,11 +1427,11 @@ /** Type IDs corresponding to JavaScript/JSON types. */ enum cson_type_id { /** - The special "null" value constant. + The special "undefined" value constant. Its value must be 0 for internal reasons. */ CSON_TYPE_UNDEF = 0, /** @@ -1597,10 +1597,18 @@ static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 }; static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 }; static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 }; static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 }; +/** + Strings are allocated as an instances of this class with N+1 + trailing bytes, where N is the length of the string being + allocated. To convert a cson_string to c-string we simply increment + the cson_string pointer. To do the opposite we use (cstr - + sizeof(cson_string)). Zero-length strings are a special case + handled by a couple of the cson_string functions. +*/ struct cson_string { unsigned int length; }; #define cson_string_empty_m {0/*length*/} @@ -1668,11 +1676,11 @@ { &cson_value_api_bool, &CSON_EMPTY_HOLDER.trueValue, 0 }, /* TRUE */ { &cson_value_api_bool, NULL, 0 }, /* FALSE */ { &cson_value_api_integer, NULL, 0 }, /* INT_0 */ { &cson_value_api_double, NULL, 0 }, /* DBL_0 */ { &cson_value_api_string, &CSON_EMPTY_HOLDER.stringValue, 0 }, /* STR_EMPTY */ -{ 0, NULL, 0 } +{ NULL, NULL, 0 } }; /** Returns non-0 (true) if m is one of our special @@ -1916,11 +1924,11 @@ cson_strings are supposed to be immutable, but this form provides access to the immutable bits, which are v->length bytes long. A length-0 string is returned as NULL from here, as opposed to "". (This is a side-effect of the string allocation mechanism.) - Returns NULL if !v. + Returns NULL if !v or if v is the internal empty-string singleton. */ static char * cson_string_str(cson_string *v) { /* See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a @@ -1948,11 +1956,14 @@ See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a */ #if 1 if( ! v ) return NULL; else if( v == &CSON_EMPTY_HOLDER.stringValue ) return ""; - else return (char *)((unsigned char *)(v+1)); + else { + assert((0 < v->length) && "How do we have a non-singleton empty string?"); + return (char *)((unsigned char *)(v+1)); + } #else return (NULL == v) ? NULL : (v->length ? (char const *) ((unsigned char const *)(v+1)) @@ -2222,13 +2233,11 @@ @see cson_value_new_integer() @see cson_value_new_double() @see cson_value_new_bool() @see cson_value_free() */ -static cson_value * cson_value_new(cson_type_id t, size_t extra); - -cson_value * cson_value_new(cson_type_id t, size_t extra) +static cson_value * cson_value_new(cson_type_id t, size_t extra) { static const size_t vsz = sizeof(cson_value); const size_t sz = vsz + extra; size_t tx = 0; cson_value def = cson_value_undef; @@ -3142,11 +3151,11 @@ kvp->value = v; } return 0; } if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1)) - { + { /* reserve space */ unsigned int const n = obj->kvp.count ? (obj->kvp.count*2) : 6; if( n > cson_kvp_list_reserve( &obj->kvp, n ) ) { return cson_rc.AllocError; } @@ -3281,10 +3290,12 @@ { cson_value_free(val); return cson_rc.AllocError; } kvp->key = cson_string_value(p->ckey)/*transfer ownership*/; + assert(0 == kvp->key->refcount); + cson_refcount_incr(kvp->key); p->ckey = NULL; kvp->value = val; cson_refcount_incr( val ); rc = cson_kvp_list_append( &obj->kvp, kvp ); if( 0 != rc ) @@ -3361,11 +3372,11 @@ ? cson_value_new_array() : cson_value_new_object(); if( ! obja ) { p->errNo = cson_rc.AllocError; - return 0; + break; } if( 0 != rc ) break; if( ! p->root ) { p->root = p->node = obja; @@ -3384,12 +3395,16 @@ } } else { rc = cson_array_append( &p->stack, obja ); - if( 0 == rc ) rc = cson_parser_push_value( p, obja ); - if( 0 == rc ) p->node = obja; + if(rc) cson_value_free( obja ); + else + { + rc = cson_parser_push_value( p, obja ); + if( 0 == rc ) p->node = obja; + } } break; } case JSON_T_ARRAY_END: case JSON_T_OBJECT_END: { @@ -4578,10 +4593,39 @@ cson_value * v = NULL; cson_object_fetch_sub2( obj, &v, path ); return v; } + +/** + If v is-a Object or Array then this function returns a deep + clone, otherwise it returns v. In either case, the refcount + of the returned value is increased by 1. +*/ +static cson_value * cson_value_clone_shared( cson_value * v ) +{ + cson_value * rc = NULL; +#define TRY_SHARING 1 +#if TRY_SHARING + if(!v ) return rc; + else if( cson_value_is_object(v) + || cson_value_is_array(v)) + { + rc = cson_value_clone( v ); + } + else + { + rc = v; + } +#else + rc = cson_value_clone(v); +#endif +#undef TRY_SHARING + cson_value_add_reference(rc); + return rc; +} + static cson_value * cson_value_clone_array( cson_value const * orig ) { unsigned int i = 0; cson_array const * asrc = cson_value_get_array( orig ); unsigned int alen = cson_array_length_get( asrc ); @@ -4600,11 +4644,11 @@ for( ; i < alen; ++i ) { cson_value * ch = cson_array_get( asrc, i ); if( NULL != ch ) { - cson_value * cl = cson_value_clone( ch ); + cson_value * cl = cson_value_clone_shared( ch ); if( NULL == cl ) { cson_value_free( destV ); return NULL; } @@ -4612,15 +4656,16 @@ { cson_value_free( cl ); cson_value_free( destV ); return NULL; } + cson_value_free(cl)/*remove our artificial reference */; } } return destV; } - + static cson_value * cson_value_clone_object( cson_value const * orig ) { cson_object const * src = cson_value_get_object( orig ); cson_value * destV = NULL; cson_object * dest = NULL; @@ -4639,32 +4684,33 @@ cson_value_free( destV ); return NULL; } while( (kvp = cson_object_iter_next( &iter )) ) { - /* - FIXME: refcount the keys! We first need a setter which takes - a cson_string or cson_value key type. - */ cson_value * key = NULL; cson_value * val = NULL; - key = cson_value_clone(kvp->key); - val = key ? cson_value_clone( kvp->value ) : NULL; + assert( kvp->key && (kvp->key->refcount>0) ); + key = cson_value_clone_shared(kvp->key); + val = key ? cson_value_clone_shared(kvp->value) : NULL; if( ! key || !val ){ - cson_value_free(key); - cson_value_free(val); - cson_value_free(destV); - return NULL; + goto error; } assert( CSON_STR(key) ); if( 0 != cson_object_set_s( dest, CSON_STR(key), val ) ) { - cson_value_free(key); - cson_value_free(val); - cson_value_free(destV); - return NULL; + goto error; } + /* remove our references */ + cson_value_free(key); + cson_value_free(val); + continue; + error: + cson_value_free(key); + cson_value_free(val); + cson_value_free(destV); + destV = NULL; + break; } return destV; } cson_value * cson_value_clone( cson_value const * orig ) @@ -4733,17 +4779,17 @@ void cson_free_array(cson_array *x) { if(x) cson_value_free(cson_array_value(x)); } -void cson_free_string(cson_string const *x) +void cson_free_string(cson_string *x) { if(x) cson_value_free(cson_string_value(x)); } void cson_free_value(cson_value *x) { - cson_value_free(x); + if(x) cson_value_free(x); } #if 0 /* i'm not happy with this... */ @@ -4907,11 +4953,11 @@ assert( 0 && "Should have been caught by is-builtin check!" ); break; default: assert(0 && "Invalid typeID!"); return 0; - +#undef RCCHECK } return rc; } } Index: src/cson_amalgamation.h ================================================================== --- src/cson_amalgamation.h +++ src/cson_amalgamation.h @@ -928,11 +928,12 @@ /** Returns a pointer to the NULL-terminated string bytes of str. The bytes are owned by string and will be invalided when it is cleaned up. - If str is NULL then NULL is returned. + If str is NULL then NULL is returned. If the string has a length + of 0 then "" is returned. @see cson_string_length_bytes() @see cson_value_get_string() */ char const * cson_string_cstr( cson_string const * str ); @@ -1261,11 +1262,11 @@ void cson_free_array(cson_array *x); /** Equivalent to cson_value_free(cson_string_value(x)). */ -void cson_free_string(cson_string const *x); +void cson_free_string(cson_string *x); /** Allocates a new "array" value and transfers ownership of it to the caller. It must eventually be destroyed, by the caller or its @@ -2037,14 +2038,14 @@ /** Deeply copies a JSON value, be it an object/array or a "plain" value (e.g. number/string/boolean). If cv is not NULL then this function makes a deep clone of it and returns that clone. Ownership - of the clone is transfered to the caller, who must eventually free - the value using cson_value_free() or add it to a container - object/array to transfer ownership to the container. The returned - object will be of the same logical type as orig. + of the clone is identical t transfered to the caller, who must + eventually free the value using cson_value_free() or add it to a + container object/array to transfer ownership to the container. The + returned object will be of the same logical type as orig. ACHTUNG: if orig contains any cyclic references at any depth level this function will endlessly recurse. (Having _any_ cyclic references violates this library's requirements.) @@ -2051,10 +2052,36 @@ Returns NULL if orig is NULL or if cloning fails. Assuming that orig is in a valid state, the only "likely" error case is that an allocation fails while constructing the clone. In other words, if cloning fails due to something other than an allocation error then either orig is in an invalid state or there is a bug. + + When this function clones Objects or Arrays it shares any immutable + values (including object keys) between the parent and the + clone. Mutable values (Objects and Arrays) are copied, however. + For example, if we clone: + + @code + { a: 1, b: 2, c:["hi"] } + @endcode + + The cloned object and the array "c" would be a new Object/Array + instances but the object keys (a,b,b) and the values of (a,b), as + well as the string value within the "c" array, would be shared + between the original and the clone. The "c" array itself would be + deeply cloned, such that future changes to the clone are not + visible to the parent, and vice versa, but immutable values within + the array are shared (in this case the string "hi"). The + justification for this heuristic is that immutable values can never + be changed, so there is no harm in sharing them across + clones. Additionally, such types can never contribute to cycles in + a JSON tree, so they are safe to share this way. Objects and + Arrays, on the other hand, can be modified later and can contribute + to cycles, and thus the clone needs to be an independent instance. + Note, however, that if this function directly passed a + non-Object/Array, that value is deeply cloned. The sharing + behaviour only applies when traversing Objects/Arrays. */ cson_value * cson_value_clone( cson_value const * orig ); /** Returns the value handle associated with s. The handle itself owns @@ -2062,11 +2089,12 @@ function. If the returned handle is part of a container, calling cson_value_free() on the returned handle invoked undefined behaviour (quite possibly downstream when the container tries to use it). - This function only returns NULL if s. is NULL. + This function only returns NULL if s is NULL. The length of the + returned string is cson_string_length_bytes(). */ cson_value * cson_string_value(cson_string const * s); /** The Object form of cson_string_value(). See that function for full details. @@ -2079,19 +2107,20 @@ */ cson_value * cson_array_value(cson_array const * s); /** - Calculates the in-memory-allocated size of v, recursively if it is - a container type, with the following caveats and limitations: + Calculates the approximate in-memory-allocated size of v, + recursively if it is a container type, with the following caveats + and limitations: - If a given value is reference counted and multiple times within a - traversed container, each reference is counted at full cost. We - have no what of knowing if a given reference has been visited - already and whether it should or should not be counted, so we - pessimistically count them even though the _might_ not really count - for the given object tree (it depends on where the other open + If a given value is reference counted then it is only and multiple + times within a traversed container, each reference is counted at + full cost. We have no way of knowing if a given reference has been + visited already and whether it should or should not be counted, so + we pessimistically count them even though the _might_ not really + count for the given object tree (it depends on where the other open references live). This function returns 0 if any of the following are true: - v is NULL