/* object.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "oj.h" #include "err.h" #include "parse.h" #include "resolve.h" #include "hash.h" #include "odd.h" #include "encode.h" inline static long read_long(const char *str, size_t len) { long n = 0; for (; 0 < len; str++, len--) { if ('0' <= *str && *str <= '9') { n = n * 10 + (*str - '0'); } else { return -1; } } return n; } static VALUE calc_hash_key(ParseInfo pi, Val kval, char k1) { VALUE rkey; if (':' == k1) { rkey = rb_str_new(kval->key + 1, kval->klen - 1); rkey = oj_encode(rkey); rkey = rb_funcall(rkey, oj_to_sym_id, 0); } else { rkey = rb_str_new(kval->key, kval->klen); rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } } return rkey; } static VALUE str_to_value(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = Qnil; if (':' == *orig && 0 < len) { rstr = rb_str_new(str + 1, len - 1); rstr = oj_encode(rstr); rstr = rb_funcall(rstr, oj_to_sym_id, 0); } else if (pi->circ_array && 3 <= len && '^' == *orig && 'r' == orig[1]) { long i = read_long(str + 2, len - 2); if (0 > i) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a valid ID number"); return Qnil; } rstr = oj_circ_array_get(pi->circ_array, i); } else { rstr = rb_str_new(str, len); rstr = oj_encode(rstr); } return rstr; } static int hat_cstr(ParseInfo pi, Val parent, Val kval, const char *str, size_t len) { const char *key = kval->key; int klen = kval->klen; if (2 == klen) { switch (key[1]) { case 'o': // object { // name2class sets and error if the class is not found or created VALUE clas = oj_name2class(pi, str, len, Yes == pi->options.auto_define); if (Qundef != clas) { parent->val = rb_obj_alloc(clas); } } break; case 'O': // odd object { Odd odd = oj_get_oddc(str, len); if (0 == odd) { return 0; } parent->val = odd->clas; parent->odd_args = oj_odd_alloc_args(odd); } break; case 'm': parent->val = rb_str_new(str + 1, len - 1); parent->val = oj_encode(parent->val); parent->val = rb_funcall(parent->val, oj_to_sym_id, 0); break; case 's': parent->val = rb_str_new(str, len); parent->val = oj_encode(parent->val); break; case 'c': // class parent->val = oj_name2class(pi, str, len, Yes == pi->options.auto_define); break; default: return 0; break; } return 1; // handled } return 0; } static int hat_num(ParseInfo pi, Val parent, Val kval, NumInfo ni) { if (2 == kval->klen) { switch (kval->key[1]) { case 't': // time as a float { int64_t nsec = ni->num * 1000000000LL / ni->div; if (ni->neg) { ni->i = -ni->i; if (0 < nsec) { ni->i--; nsec = 1000000000LL - nsec; } } #if HAS_NANO_TIME parent->val = rb_time_nano_new(ni->i, (long)nsec); #else parent->val = rb_time_new(ni->i, (long)(nsec / 1000)); #endif } break; case 'i': // circular index if (!ni->infinity && !ni->neg && 1 == ni->div && 0 == ni->exp && 0 != pi->circ_array) { // fixnum if (Qnil == parent->val) { parent->val = rb_hash_new(); } oj_circ_array_set(pi->circ_array, parent->val, ni->i); } else { return 0; } break; default: return 0; break; } return 1; // handled } return 0; } static int hat_value(ParseInfo pi, Val parent, const char *key, size_t klen, volatile VALUE value) { if (T_ARRAY == rb_type(value)) { int len = (int)RARRAY_LEN(value); if (2 == klen && 'u' == key[1]) { VALUE sc; if (0 == len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); return 1; } // If struct is not defined then we let this fail and raise an exception. sc = oj_name2struct(pi, *RARRAY_PTR(value)); // Create a properly initialized struct instance without calling the initialize method. parent->val = rb_obj_alloc(sc); // If the JSON array has more entries than the struct class allows, we record an error. #ifdef RSTRUCT_LEN // MRI >= 1.9 if (len - 1 > RSTRUCT_LEN(parent->val)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); } else { MEMCPY(RSTRUCT_PTR(parent->val), RARRAY_PTR(value) + 1, VALUE, len - 1); } #else { // MRI < 1.9 or Rubinius int slen = FIX2INT(rb_funcall2(parent->val, oj_length_id, 0, 0)); int i; if (len - 1 > slen) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); } else { for (i = 0; i < slen; i++) { rb_struct_aset(parent->val, INT2FIX(i), RARRAY_PTR(value)[i + 1]); } } } #endif return 1; } else if (3 <= klen && '#' == key[1]) { volatile VALUE *a; if (2 != len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair"); return 1; } parent->val = rb_hash_new(); a = RARRAY_PTR(value); rb_hash_aset(parent->val, *a, a[1]); return 1; } } return 0; } static void copy_ivars(VALUE target, VALUE src) { volatile VALUE vars = rb_funcall(src, oj_instance_variables_id, 0); volatile VALUE *np = RARRAY_PTR(vars); ID vid; int i, cnt = (int)RARRAY_LEN(vars); const char *attr; for (i = cnt; 0 < i; i--, np++) { vid = rb_to_id(*np); attr = rb_id2name(vid); if ('@' == *attr) { rb_ivar_set(target, vid, rb_ivar_get(src, vid)); } } } static void set_obj_ivar(Val parent, Val kval, VALUE value) { const char *key = kval->key; int klen = kval->klen; ID var_id; ID *slot; if ('~' == *key && Qtrue == rb_obj_is_kind_of(parent->val, rb_eException)) { if (5 == klen && 0 == strncmp("~mesg", key, klen)) { VALUE args[1]; VALUE prev = parent->val; args[0] = value; parent->val = rb_class_new_instance(1, args, rb_class_of(parent->val)); copy_ivars(parent->val, prev); } else if (3 == klen && 0 == strncmp("~bt", key, klen)) { rb_funcall(parent->val, rb_intern("set_backtrace"), 1, value); } } #if USE_PTHREAD_MUTEX pthread_mutex_lock(&oj_cache_mutex); #elif USE_RB_MUTEX rb_mutex_lock(oj_cache_mutex); #endif if (0 == (var_id = oj_attr_hash_get(key, klen, &slot))) { char attr[256]; if ((int)sizeof(attr) <= klen + 2) { char *buf = ALLOC_N(char, klen + 2); if ('~' == *key) { strncpy(buf, key + 1, klen - 1); buf[klen - 1] = '\0'; } else { *buf = '@'; strncpy(buf + 1, key, klen); buf[klen + 1] = '\0'; } var_id = rb_intern(buf); xfree(buf); } else { if ('~' == *key) { strncpy(attr, key + 1, klen - 1); attr[klen - 1] = '\0'; } else { *attr = '@'; strncpy(attr + 1, key, klen); attr[klen + 1] = '\0'; } var_id = rb_intern(attr); } *slot = var_id; } #if USE_PTHREAD_MUTEX pthread_mutex_unlock(&oj_cache_mutex); #elif USE_RB_MUTEX rb_mutex_unlock(oj_cache_mutex); #endif rb_ivar_set(parent->val, var_id, value); } static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { const char *key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = 0; // make sure it is 0 in case not odd if ('^' != *key || !hat_cstr(pi, parent, kval, str, len)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: rb_hash_aset(parent->val, calc_hash_key(pi, kval, parent->k1), str_to_value(pi, str, len, orig)); break; case T_STRING: if (4 == klen && 's' == *key && 'e' == key[1] && 'l' == key[2] && 'f' == key[3]) { rb_funcall(parent->val, oj_replace_id, 1, str_to_value(pi, str, len, orig)); } else { set_obj_ivar(parent, kval, str_to_value(pi, str, len, orig)); } break; case T_OBJECT: set_obj_ivar(parent, kval, str_to_value(pi, str, len, orig)); break; case T_CLASS: if (0 == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, kval->key, kval->klen, str_to_value(pi, str, len, orig))) { char buf[256]; if ((int)sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } } static void hash_set_num(ParseInfo pi, Val kval, NumInfo ni) { const char *key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = 0; // make sure it is 0 in case not odd if ('^' != *key || !hat_num(pi, parent, kval, ni)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: rb_hash_aset(parent->val, calc_hash_key(pi, kval, parent->k1), oj_num_as_value(ni)); break; case T_OBJECT: if (2 == klen && '^' == *key && 'i' == key[1] && !ni->infinity && !ni->neg && 1 == ni->div && 0 == ni->exp && 0 != pi->circ_array) { // fixnum oj_circ_array_set(pi->circ_array, parent->val, ni->i); } else { set_obj_ivar(parent, kval, oj_num_as_value(ni)); } break; case T_CLASS: if (0 == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, oj_num_as_value(ni))) { char buf[256]; if ((int)sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } } static void hash_set_value(ParseInfo pi, Val kval, VALUE value) { const char *key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = 0; // make sure it is 0 in case not odd if ('^' != *key || !hat_value(pi, parent, key, klen, value)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: if (rb_cHash != rb_obj_class(parent->val)) { if (4 == klen && 's' == *key && 'e' == key[1] && 'l' == key[2] && 'f' == key[3]) { rb_funcall(parent->val, oj_replace_id, 1, value); } else { set_obj_ivar(parent, kval, value); } } else { if (3 <= klen && '^' == *key && '#' == key[1] && T_ARRAY == rb_type(value)) { long len = RARRAY_LEN(value); VALUE *a = RARRAY_PTR(value); if (2 != len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair"); return; } rb_hash_aset(parent->val, *a, a[1]); } else { rb_hash_aset(parent->val, calc_hash_key(pi, kval, parent->k1), value); } } break; case T_ARRAY: if (4 == klen && 's' == *key && 'e' == key[1] && 'l' == key[2] && 'f' == key[3]) { rb_funcall(parent->val, oj_replace_id, 1, value); } else { set_obj_ivar(parent, kval, value); } break; case T_STRING: // for subclassed strings case T_OBJECT: set_obj_ivar(parent, kval, value); break; case T_CLASS: if (0 == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, value)) { char buf[256]; if ((int)sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } } static VALUE start_hash(ParseInfo pi) { return Qnil; } static void end_hash(struct _ParseInfo *pi) { Val parent = stack_peek(&pi->stack); if (Qnil == parent->val) { parent->val = rb_hash_new(); } else if (0 != parent->odd_args) { OddArgs oa = parent->odd_args; parent->val = rb_funcall2(oa->odd->create_obj, oa->odd->create_op, oa->odd->attr_cnt, oa->args); oj_odd_free(oa); parent->odd_args = 0; } } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { if (3 <= len && 0 != pi->circ_array) { if ('i' == str[1]) { long i = read_long(str + 2, len - 2); if (0 < i) { oj_circ_array_set(pi->circ_array, stack_peek(&pi->stack)->val, i); return; } } else if ('r' == str[1]) { long i = read_long(str + 2, len - 2); if (0 < i) { rb_ary_push(stack_peek(&pi->stack)->val, oj_circ_array_get(pi->circ_array, i)); return; } } } rb_ary_push(stack_peek(&pi->stack)->val, str_to_value(pi, str, len, orig)); } static void array_append_num(ParseInfo pi, NumInfo ni) { rb_ary_push(stack_peek(&pi->stack)->val, oj_num_as_value(ni)); } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { pi->stack.head->val = str_to_value(pi, str, len, orig); } static void add_num(ParseInfo pi, NumInfo ni) { pi->stack.head->val = oj_num_as_value(ni); } void oj_set_object_callbacks(ParseInfo pi) { oj_set_strict_callbacks(pi); pi->end_hash = end_hash; pi->start_hash = start_hash; pi->hash_set_cstr = hash_set_cstr; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->add_cstr = add_cstr; pi->add_num = add_num; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; } VALUE oj_object_parse(int argc, VALUE *argv, VALUE self) { struct _ParseInfo pi; pi.options = oj_default_options; pi.handler = Qnil; oj_set_object_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, 1); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _ParseInfo pi; pi.options = oj_default_options; pi.handler = Qnil; oj_set_strict_callbacks(&pi); pi.end_hash = end_hash; pi.start_hash = start_hash; pi.hash_set_cstr = hash_set_cstr; pi.hash_set_num = hash_set_num; pi.hash_set_value = hash_set_value; pi.add_cstr = add_cstr; pi.add_num = add_num; pi.array_append_cstr = array_append_cstr; pi.array_append_num = array_append_num; return oj_pi_parse(argc, argv, &pi, json, len, 1); }