/* sparse.c * Copyright (c) 2013, Peter Ohler * All rights reserved. */ #include #include #include #include #include #include "oj.h" #include "encode.h" #include "parse.h" #include "buf.h" #include "hash.h" // for oj_strndup() #include "val_stack.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0/0.0) #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif #define EXP_MAX 100000 #define DEC_MAX 15 static void skip_comment(ParseInfo pi) { char c = reader_get(&pi->rd); if ('*' == c) { while ('\0' != (c = reader_get(&pi->rd))) { if ('*' == c) { c = reader_get(&pi->rd); if ('/' == c) { return; } } } } else if ('/' == c) { while ('\0' != (c = reader_get(&pi->rd))) { switch (c) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid comment format"); } if ('\0' == c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "comment not terminated"); return; } } static void add_value(ParseInfo pi, VALUE rval) { Val parent = stack_peek(&pi->stack); if (0 == parent) { // simple add pi->add_value(pi, rval); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_value(pi, rval); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_value(pi, parent, rval); if (parent->kalloc) { xfree((char*)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void add_num_value(ParseInfo pi, NumInfo ni) { Val parent = stack_peek(&pi->stack); if (0 == parent) { pi->add_num(pi, ni); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_num(pi, ni); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_num(pi, parent, ni); if (parent->kalloc) { xfree((char*)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void read_true(ParseInfo pi) { if (0 == reader_expect(&pi->rd, "rue")) { add_value(pi, Qtrue); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected true"); } } static void read_false(ParseInfo pi) { if (0 == reader_expect(&pi->rd, "alse")) { add_value(pi, Qfalse); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected false"); } } static uint32_t read_hex(ParseInfo pi) { uint32_t b = 0; int i; char c; for (i = 0; i < 4; i++) { c = reader_get(&pi->rd); b = b << 4; if ('0' <= c && c <= '9') { b += c - '0'; } else if ('A' <= c && c <= 'F') { b += c - 'A' + 10; } else if ('a' <= c && c <= 'f') { b += c - 'a' + 10; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hex character"); return 0; } } return b; } static void unicode_to_chars(ParseInfo pi, Buf buf, uint32_t code) { if (0x0000007F >= code) { buf_append(buf, (char)code); } else if (0x000007FF >= code) { buf_append(buf, 0xC0 | (code >> 6)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x0000FFFF >= code) { buf_append(buf, 0xE0 | (code >> 12)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x001FFFFF >= code) { buf_append(buf, 0xF0 | (code >> 18)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x03FFFFFF >= code) { buf_append(buf, 0xF8 | (code >> 24)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x7FFFFFFF >= code) { buf_append(buf, 0xFC | (code >> 30)); buf_append(buf, 0x80 | ((code >> 24) & 0x3F)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid Unicode character"); } } // entered at backslash static void read_escaped_str(ParseInfo pi) { struct _buf buf; char c; uint32_t code; Val parent = stack_peek(&pi->stack); buf_init(&buf); if (pi->rd.str < pi->rd.tail) { buf_append_string(&buf, pi->rd.str, pi->rd.tail - pi->rd.str); } while ('\"' != (c = reader_get(&pi->rd))) { if ('\0' == c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); buf_cleanup(&buf); return; } else if ('\\' == c) { c = reader_get(&pi->rd); switch (c) { case 'n': buf_append(&buf, '\n'); break; case 'r': buf_append(&buf, '\r'); break; case 't': buf_append(&buf, '\t'); break; case 'f': buf_append(&buf, '\f'); break; case 'b': buf_append(&buf, '\b'); break; case '"': buf_append(&buf, '"'); break; case '/': buf_append(&buf, '/'); break; case '\\': buf_append(&buf, '\\'); break; case '\'': // The json gem claims this is not an error despite the // ECMA-404 indicating it is not valid. if (CompatMode == pi->options.mode) { buf_append(&buf, '\''); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } break; case 'u': if (0 == (code = read_hex(pi)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; char ch2; c = reader_get(&pi->rd); ch2 = reader_get(&pi->rd); if ('\\' != c || 'u' != ch2) { if (Yes == pi->options.allow_invalid) { unicode_to_chars(pi, &buf, code); reader_backup(&pi->rd); reader_backup(&pi->rd); break; } oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } if (0 == (c2 = read_hex(pi)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } unicode_to_chars(pi, &buf, code); if (err_has(&pi->err)) { buf_cleanup(&buf); return; } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } } else { buf_append(&buf, c); } } if (0 == parent) { pi->add_cstr(pi, buf.head, buf_len(&buf), pi->rd.str); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, buf.head, buf_len(&buf), pi->rd.str); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: if (Qundef == (parent->key_val = pi->hash_key(pi, buf.head, buf_len(&buf)))) { parent->klen = buf_len(&buf); parent->key = malloc(parent->klen + 1); memcpy((char*)parent->key, buf.head, parent->klen); *(char*)(parent->key + parent->klen) = '\0'; } else { parent->key = ""; parent->klen = 0; } parent->k1 = *pi->rd.str; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent, buf.head, buf_len(&buf), pi->rd.str); if (parent->kalloc) { xfree((char*)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } buf_cleanup(&buf); } static void read_str(ParseInfo pi) { Val parent = stack_peek(&pi->stack); char c; reader_protect(&pi->rd); while ('\"' != (c = reader_get(&pi->rd))) { if ('\0' == c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); return; } else if ('\\' == c) { reader_backup(&pi->rd); read_escaped_str(pi); reader_release(&pi->rd); return; } } if (0 == parent) { // simple add pi->add_cstr(pi, pi->rd.str, pi->rd.tail - pi->rd.str - 1, pi->rd.str); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, pi->rd.str, pi->rd.tail - pi->rd.str - 1, pi->rd.str); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: parent->klen = pi->rd.tail - pi->rd.str - 1; if (sizeof(parent->karray) <= parent->klen) { parent->key = oj_strndup(pi->rd.str, parent->klen); parent->kalloc = 1; } else { memcpy(parent->karray, pi->rd.str, parent->klen); parent->karray[parent->klen] = '\0'; parent->key = parent->karray; parent->kalloc = 0; } parent->key_val = pi->hash_key(pi, parent->key, parent->klen); parent->k1 = *pi->rd.str; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent, pi->rd.str, pi->rd.tail - pi->rd.str - 1, pi->rd.str); if (parent->kalloc) { xfree((char*)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } reader_release(&pi->rd); } static void read_num(ParseInfo pi) { struct _numInfo ni; char c; reader_protect(&pi->rd); ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 0; ni.neg = 0; ni.hasExp = 0; ni.no_big = (FloatDec == pi->options.bigdec_load); c = reader_get(&pi->rd); if ('-' == c) { c = reader_get(&pi->rd); ni.neg = 1; } else if ('+' == c) { c = reader_get(&pi->rd); } if ('I' == c) { if (No == pi->options.allow_nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } else if (0 != reader_expect(&pi->rd, "nfinity")) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } ni.infinity = 1; } else { int dec_cnt = 0; bool zero1 = false; for (; '0' <= c && c <= '9'; c = reader_get(&pi->rd)) { if (0 == ni.i && '0' == c) { zero1 = true; } if (0 < ni.i) { dec_cnt++; } if (ni.big) { ni.big++; } else { int d = (c - '0'); if (0 < d) { if (zero1 && CompatMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); return; } zero1 = false; } ni.i = ni.i * 10 + d; if (INT64_MAX <= ni.i || DEC_MAX < dec_cnt) { ni.big = 1; } } } if ('.' == c) { c = reader_get(&pi->rd); // A trailing . is not a valid decimal but if encountered allow it // except when mimicing the JSON gem. if (CompatMode == pi->options.mode) { if (c < '0' || '9' < c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); } } for (; '0' <= c && c <= '9'; c = reader_get(&pi->rd)) { int d = (c - '0'); if (0 < ni.num || 0 < ni.i) { dec_cnt++; } ni.num = ni.num * 10 + d; ni.div *= 10; ni.di++; if (INT64_MAX <= ni.div || DEC_MAX < dec_cnt) { ni.big = 1; } } } if ('e' == c || 'E' == c) { int eneg = 0; ni.hasExp = 1; c = reader_get(&pi->rd); if ('-' == c) { c = reader_get(&pi->rd); eneg = 1; } else if ('+' == c) { c = reader_get(&pi->rd); } for (; '0' <= c && c <= '9'; c = reader_get(&pi->rd)) { ni.exp = ni.exp * 10 + (c - '0'); if (EXP_MAX <= ni.exp) { ni.big = 1; } } if (eneg) { ni.exp = -ni.exp; } } ni.len = pi->rd.tail - pi->rd.str; if (0 != c) { reader_backup(&pi->rd); } } ni.str = pi->rd.str; ni.len = pi->rd.tail - pi->rd.str; // Check for special reserved values for Infinity and NaN. if (ni.big) { if (0 == strcasecmp(INF_VAL, ni.str)) { ni.infinity = 1; } else if (0 == strcasecmp(NINF_VAL, ni.str)) { ni.infinity = 1; ni.neg = 1; } else if (0 == strcasecmp(NAN_VAL, ni.str)) { ni.nan = 1; } } if (BigDec == pi->options.bigdec_load) { ni.big = 1; } add_num_value(pi, &ni); reader_release(&pi->rd); } static void read_nan(ParseInfo pi) { struct _numInfo ni; char c; ni.str = pi->rd.str; ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 1; ni.neg = 0; ni.no_big = (FloatDec == pi->options.bigdec_load); if ('a' != reader_get(&pi->rd) || ('N' != (c = reader_get(&pi->rd)) && 'n' != c)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } if (BigDec == pi->options.bigdec_load) { ni.big = 1; } add_num_value(pi, &ni); } static void array_start(ParseInfo pi) { VALUE v = pi->start_array(pi); stack_push(&pi->stack, v, NEXT_ARRAY_NEW); } static void array_end(ParseInfo pi) { Val array = stack_pop(&pi->stack); if (0 == array) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected array close"); } else if (NEXT_ARRAY_COMMA != array->next && NEXT_ARRAY_NEW != array->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not an array close", oj_stack_next_string(array->next)); } else { pi->end_array(pi); add_value(pi, array->val); } } static void hash_start(ParseInfo pi) { volatile VALUE v = pi->start_hash(pi); stack_push(&pi->stack, v, NEXT_HASH_NEW); } static void hash_end(ParseInfo pi) { volatile Val hash = stack_peek(&pi->stack); // leave hash on stack until just before if (0 == hash) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected hash close"); } else if (NEXT_HASH_COMMA != hash->next && NEXT_HASH_NEW != hash->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a hash close", oj_stack_next_string(hash->next)); } else { pi->end_hash(pi); stack_pop(&pi->stack); add_value(pi, hash->val); } } static void comma(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 == parent) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } else if (NEXT_ARRAY_COMMA == parent->next) { parent->next = NEXT_ARRAY_ELEMENT; } else if (NEXT_HASH_COMMA == parent->next) { parent->next = NEXT_HASH_KEY; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } } static void colon(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 != parent && NEXT_HASH_COLON == parent->next) { parent->next = NEXT_HASH_VALUE; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected colon"); } } void oj_sparse2(ParseInfo pi) { int first = 1; char c; long start = 0; err_init(&pi->err); while (1) { if (0 < pi->max_depth && pi->max_depth <= pi->stack.tail - pi->stack.head - 1) { VALUE err_clas = oj_get_json_err_class("NestingError"); oj_set_error_at(pi, err_clas, __FILE__, __LINE__, "Too deeply nested."); pi->err_class = err_clas; return; } c = reader_next_non_white(&pi->rd); if (!first && '\0' != c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected characters after the JSON document"); } switch (c) { case '{': hash_start(pi); break; case '}': hash_end(pi); break; case ':': colon(pi); break; case '[': array_start(pi); break; case ']': array_end(pi); break; case ',': comma(pi); break; case '"': read_str(pi); break; case '+': if (CompatMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } pi->cur--; read_num(pi); break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': reader_backup(&pi->rd); read_num(pi); break; case 'I': if (Yes == pi->options.allow_nan) { reader_backup(&pi->rd); read_num(pi); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } break; case 'N': if (Yes == pi->options.allow_nan) { read_nan(pi); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } break; case 't': read_true(pi); break; case 'f': read_false(pi); break; case 'n': c = reader_get(&pi->rd); if ('u' == c) { if (0 == reader_expect(&pi->rd, "ll")) { add_value(pi, Qnil); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected null"); return; } } else if ('a' == c) { struct _numInfo ni; c = reader_get(&pi->rd); if ('N' != c && 'n' != c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected NaN"); return; } ni.str = pi->rd.str; ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 1; ni.neg = 0; ni.no_big = (FloatDec == pi->options.bigdec_load); add_num_value(pi, &ni); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid token"); return; } break; case '/': skip_comment(pi); break; case '\0': return; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character '%c' [0x%02x]", c, c); return; } if (err_has(&pi->err)) { return; } if (stack_empty(&pi->stack)) { if (Qundef != pi->proc) { VALUE args[3]; long len = pi->rd.pos - start; *args = stack_head_val(&pi->stack); args[1] = LONG2NUM(start); args[2] = LONG2NUM(len); if (Qnil == pi->proc) { rb_yield_values2(3, args); } else { rb_proc_call_with_block(pi->proc, 3, args, Qnil); } } else if (!pi->has_callbacks) { first = 0; } start = pi->rd.pos; // TBD break if option set to allow that } } } static VALUE protect_parse(VALUE pip) { oj_sparse2((ParseInfo)pip); return Qnil; } VALUE oj_pi_sparse(int argc, VALUE *argv, ParseInfo pi, int fd) { volatile VALUE input; volatile VALUE wrapped_stack; VALUE result = Qnil; int line = 0; if (argc < 1) { rb_raise(rb_eArgError, "Wrong number of arguments to parse."); } input = argv[0]; if (2 <= argc) { if (T_HASH == rb_type(argv[1])) { oj_parse_options(argv[1], &pi->options); } else if (3 <= argc && T_HASH == rb_type(argv[2])) { oj_parse_options(argv[2], &pi->options); } } if (Qnil == input) { if (Yes == pi->options.nilnil) { return Qnil; } else { rb_raise(rb_eTypeError, "Nil is not a valid JSON source."); } } else if (CompatMode == pi->options.mode && T_STRING == rb_type(input) && No == pi->options.nilnil && 0 == RSTRING_LEN(input)) { rb_raise(oj_json_parser_error_class, "An empty string is not a valid JSON string."); } if (rb_block_given_p()) { pi->proc = Qnil; } else { pi->proc = Qundef; } oj_reader_init(&pi->rd, input, fd, CompatMode == pi->options.mode); pi->json = 0; // indicates reader is in use if (Yes == pi->options.circular) { pi->circ_array = oj_circ_array_new(); } else { pi->circ_array = 0; } if (No == pi->options.allow_gc) { rb_gc_disable(); } // GC can run at any time. When it runs any Object created by C will be // freed. We protect against this by wrapping the value stack in a ruby // data object and providing a mark function for ruby objects on the // value stack (while it is in scope). wrapped_stack = oj_stack_init(&pi->stack); rb_protect(protect_parse, (VALUE)pi, &line); if (Qundef == pi->stack.head->val && !empty_ok(&pi->options)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Empty input"); } result = stack_head_val(&pi->stack); DATA_PTR(wrapped_stack) = 0; if (No == pi->options.allow_gc) { rb_gc_enable(); } if (!err_has(&pi->err)) { // If the stack is not empty then the JSON terminated early. Val v; VALUE err_class = oj_parse_error_class; if (0 != line) { VALUE ec = rb_obj_class(rb_errinfo()); if (rb_eIOError != ec) { goto CLEANUP; } // Sometimes the class of the error is 0 which seems broken. if (rb_eArgError != ec && 0 != ec) { err_class = ec; } } if (0 != (v = stack_peek(&pi->stack))) { switch (v->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: case NEXT_ARRAY_COMMA: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "Array not terminated"); break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COLON: case NEXT_HASH_VALUE: case NEXT_HASH_COMMA: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "Hash/Object not terminated"); break; default: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "not terminated"); } } } CLEANUP: // proceed with cleanup if (0 != pi->circ_array) { oj_circ_array_free(pi->circ_array); } stack_cleanup(&pi->stack); if (0 != fd) { close(fd); } if (err_has(&pi->err)) { rb_set_errinfo(Qnil); if (Qnil != pi->err_class && 0 != pi->err_class) { pi->err.clas = pi->err_class; } if (CompatMode == pi->options.mode && Yes != pi->options.safe) { // The json gem requires the error message be UTF-8 encoded. In // additional the complete JSON source should be returned but that // is not possible without stored all the bytes read and reading // the remaining bytes on the stream. Both seem like a very bad // idea. VALUE args[] = { oj_encode(rb_str_new2(pi->err.msg)) }; if (pi->err.clas == oj_parse_error_class) { // The error was an Oj::ParseError so change to a JSON::ParserError. pi->err.clas = oj_json_parser_error_class; } rb_exc_raise(rb_class_new_instance(1, args, pi->err.clas)); } else { oj_err_raise(&pi->err); } oj_err_raise(&pi->err); } else if (0 != line) { rb_jump_tag(line); } return result; }