--- JSON-XS/XS.xs 2007/07/26 11:33:35 1.56 +++ JSON-XS/XS.xs 2008/03/19 03:17:38 1.71 @@ -29,6 +29,8 @@ #define F_SHRINK 0x00000200UL #define F_ALLOW_BLESSED 0x00000400UL #define F_CONV_BLESSED 0x00000800UL +#define F_RELAXED 0x00001000UL + #define F_MAXDEPTH 0xf8000000UL #define S_MAXDEPTH 27 #define F_MAXSIZE 0x01f00000UL @@ -50,16 +52,24 @@ #define SE } while (0) #if __GNUC__ >= 3 -# define expect(expr,value) __builtin_expect ((expr),(value)) -# define inline inline +# define expect(expr,value) __builtin_expect ((expr), (value)) +# define INLINE static inline #else # define expect(expr,value) (expr) -# define inline static +# define INLINE static #endif #define expect_false(expr) expect ((expr) != 0, 0) #define expect_true(expr) expect ((expr) != 0, 1) +#ifdef USE_ITHREADS +# define JSON_SLOW 1 +# define JSON_STASH (json_stash ? json_stash : gv_stashpv ("JSON::XS", 1)) +#else +# define JSON_SLOW 0 +# define JSON_STASH json_stash +#endif + static HV *json_stash, *json_boolean_stash; // JSON::XS:: static SV *json_true, *json_false; @@ -72,7 +82,7 @@ ///////////////////////////////////////////////////////////////////////////// // utility functions -inline void +INLINE void shrink (SV *sv) { sv_utf8_downgrade (sv, 1); @@ -91,7 +101,7 @@ // we special-case "safe" characters from U+80 .. U+7FF, // but use the very good perl function to parse anything else. // note that we never call this function for a ascii codepoints -inline UV +INLINE UV decode_utf8 (unsigned char *s, STRLEN len, STRLEN *clen) { if (expect_false (s[0] > 0xdf || s[0] < 0xc2)) @@ -120,9 +130,10 @@ JSON json; U32 indent; // indentation level U32 maxdepth; // max. indentation/recursion level + UV limit; // escape character values >= this value when encoding } enc_t; -inline void +INLINE void need (enc_t *enc, STRLEN len) { if (expect_false (enc->cur + len >= enc->end)) @@ -134,7 +145,7 @@ } } -inline void +INLINE void encode_ch (enc_t *enc, char ch) { need (enc, 1); @@ -198,13 +209,13 @@ clen = 1; } - if (uch > 0x10FFFFUL) - croak ("out of range codepoint (0x%lx) encountered, unrepresentable in JSON", (unsigned long)uch); - - if (uch < 0x80 || enc->json.flags & F_ASCII || (enc->json.flags & F_LATIN1 && uch > 0xFF)) + if (uch < 0x20 || uch >= enc->limit) { if (uch > 0xFFFFUL) { + if (uch > 0x10FFFFUL) + croak ("out of range codepoint (0x%lx) encountered, unrepresentable in JSON", (unsigned long)uch); + need (enc, len += 11); sprintf (enc->cur, "\\u%04x\\u%04x", (int)((uch - 0x10000) / 0x400 + 0xD800), @@ -253,7 +264,7 @@ } } -inline void +INLINE void encode_indent (enc_t *enc) { if (enc->json.flags & F_INDENT) @@ -266,14 +277,14 @@ } } -inline void +INLINE void encode_space (enc_t *enc) { need (enc, 1); encode_ch (enc, ' '); } -inline void +INLINE void encode_nl (enc_t *enc) { if (enc->json.flags & F_INDENT) @@ -283,7 +294,7 @@ } } -inline void +INLINE void encode_comma (enc_t *enc) { encode_ch (enc, ','); @@ -304,32 +315,35 @@ if (enc->indent >= enc->maxdepth) croak ("data structure too deep (hit recursion limit)"); - encode_ch (enc, '['); encode_nl (enc); - ++enc->indent; - - for (i = 0; i <= len; ++i) + encode_ch (enc, '['); + + if (len >= 0) { - SV **svp = av_fetch (av, i, 0); + encode_nl (enc); ++enc->indent; - encode_indent (enc); + for (i = 0; i <= len; ++i) + { + SV **svp = av_fetch (av, i, 0); - if (svp) - encode_sv (enc, *svp); - else - encode_str (enc, "null", 4, 0); + encode_indent (enc); - if (i < len) - encode_comma (enc); - } + if (svp) + encode_sv (enc, *svp); + else + encode_str (enc, "null", 4, 0); - encode_nl (enc); + if (i < len) + encode_comma (enc); + } - --enc->indent; - encode_indent (enc); encode_ch (enc, ']'); + encode_nl (enc); --enc->indent; encode_indent (enc); + } + + encode_ch (enc, ']'); } static void -encode_he (enc_t *enc, HE *he) +encode_hk (enc_t *enc, HE *he) { encode_ch (enc, '"'); @@ -352,7 +366,6 @@ if (enc->json.flags & F_SPACE_BEFORE) encode_space (enc); encode_ch (enc, ':'); if (enc->json.flags & F_SPACE_AFTER ) encode_space (enc); - encode_sv (enc, HeVAL (he)); } // compare hash entries, used when all keys are bytestrings @@ -367,8 +380,8 @@ STRLEN la = HeKLEN (a); STRLEN lb = HeKLEN (b); - if (!(cmp = memcmp (HeKEY (a), HeKEY (b), la < lb ? la : lb))) - cmp = la - lb; + if (!(cmp = memcmp (HeKEY (b), HeKEY (a), lb < la ? lb : la))) + cmp = lb - la; return cmp; } @@ -377,29 +390,45 @@ static int he_cmp_slow (const void *a, const void *b) { - return sv_cmp (HeSVKEY_force (*(HE **)a), HeSVKEY_force (*(HE **)b)); + return sv_cmp (HeSVKEY_force (*(HE **)b), HeSVKEY_force (*(HE **)a)); } static void encode_hv (enc_t *enc, HV *hv) { - int count, i; + HE *he; + int count; if (enc->indent >= enc->maxdepth) croak ("data structure too deep (hit recursion limit)"); - encode_ch (enc, '{'); encode_nl (enc); ++enc->indent; + encode_ch (enc, '{'); - if ((count = hv_iterinit (hv))) + // for canonical output we have to sort by keys first + // actually, this is mostly due to the stupid so-called + // security workaround added somewhere in 5.8.x. + // that randomises hash orderings + if (enc->json.flags & F_CANONICAL) { - // for canonical output we have to sort by keys first - // actually, this is mostly due to the stupid so-called - // security workaround added somewhere in 5.8.x. - // that randomises hash orderings - if (enc->json.flags & F_CANONICAL) + int count = hv_iterinit (hv); + + if (SvMAGICAL (hv)) + { + // need to count by iterating. could improve by dynamically building the vector below + // but I don't care for the speed of this special case. + // note also that we will run into undefined behaviour when the two iterations + // do not result in the same count, something I might care for in some later release. + + count = 0; + while (hv_iternext (hv)) + ++count; + + hv_iterinit (hv); + } + + if (count) { - int fast = 1; - HE *he; + int i, fast = 1; #if defined(__BORLANDC__) || defined(_MSC_VER) HE **hes = _alloca (count * sizeof (HE)); #else @@ -436,37 +465,46 @@ LEAVE; } - for (i = 0; i < count; ++i) + encode_nl (enc); ++enc->indent; + + while (count--) { encode_indent (enc); - encode_he (enc, hes [i]); + he = hes [count]; + encode_hk (enc, he); + encode_sv (enc, expect_false (SvMAGICAL (hv)) ? hv_iterval (hv, he) : HeVAL (he)); - if (i < count - 1) + if (count) encode_comma (enc); } - encode_nl (enc); + encode_nl (enc); --enc->indent; encode_indent (enc); } - else - { - HE *he = hv_iternext (hv); + } + else + { + if (hv_iterinit (hv) || SvMAGICAL (hv)) + if ((he = hv_iternext (hv))) + { + encode_nl (enc); ++enc->indent; - for (;;) - { - encode_indent (enc); - encode_he (enc, he); + for (;;) + { + encode_indent (enc); + encode_hk (enc, he); + encode_sv (enc, expect_false (SvMAGICAL (hv)) ? hv_iterval (hv, he) : HeVAL (he)); - if (!(he = hv_iternext (hv))) - break; + if (!(he = hv_iternext (hv))) + break; - encode_comma (enc); - } + encode_comma (enc); + } - encode_nl (enc); - } + encode_nl (enc); --enc->indent; encode_indent (enc); + } } - --enc->indent; encode_indent (enc); encode_ch (enc, '}'); + encode_ch (enc, '}'); } // encode objects, arrays and special \0=false and \1=true values. @@ -480,7 +518,11 @@ if (expect_false (SvOBJECT (sv))) { - if (SvSTASH (sv) == json_boolean_stash) + HV *stash = !JSON_SLOW || json_boolean_stash + ? json_boolean_stash + : gv_stashpv ("JSON::XS::Boolean", 1); + + if (SvSTASH (sv) == stash) { if (SvIV (sv)) encode_str (enc, "true", 4, 0); @@ -502,7 +544,6 @@ if (to_json) { - int count; dSP; ENTER; SAVETMPS; PUSHMARK (SP); @@ -642,6 +683,9 @@ enc.end = SvEND (enc.sv); enc.indent = 0; enc.maxdepth = DEC_DEPTH (enc.json.flags); + enc.limit = enc.json.flags & F_ASCII ? 0x000080UL + : enc.json.flags & F_LATIN1 ? 0x000100UL + : 0x10FFFFUL; SvPOK_only (enc.sv); encode_sv (&enc, scalar); @@ -672,16 +716,36 @@ U32 maxdepth; // recursion depth limit } dec_t; -inline void +INLINE void +decode_comment (dec_t *dec) +{ + // only '#'-style comments allowed a.t.m. + + while (*dec->cur && *dec->cur != 0x0a && *dec->cur != 0x0d) + ++dec->cur; +} + +INLINE void decode_ws (dec_t *dec) { for (;;) { char ch = *dec->cur; - if (ch > 0x20 - || (ch != 0x20 && ch != 0x0a && ch != 0x0d && ch != 0x09)) - break; + if (ch > 0x20) + { + if (expect_false (ch == '#')) + { + if (dec->json.flags & F_RELAXED) + decode_comment (dec); + else + break; + } + else + break; + } + else if (ch != 0x20 && ch != 0x0a && ch != 0x0d && ch != 0x09) + break; // parse error, but let higher level handle it, gives better error messages ++dec->cur; } @@ -942,22 +1006,24 @@ { int len = dec->cur - start; - // special case the rather common 1..4-digit-int case, assumes 32 bit ints or so + // special case the rather common 1..5-digit-int case if (*start == '-') switch (len) { - case 2: return newSViv (-( start [1] - '0' * 1)); - case 3: return newSViv (-( start [1] * 10 + start [2] - '0' * 11)); - case 4: return newSViv (-( start [1] * 100 + start [2] * 10 + start [3] - '0' * 111)); - case 5: return newSViv (-(start [1] * 1000 + start [2] * 100 + start [3] * 10 + start [4] - '0' * 1111)); + case 2: return newSViv (-( start [1] - '0' * 1)); + case 3: return newSViv (-( start [1] * 10 + start [2] - '0' * 11)); + case 4: return newSViv (-( start [1] * 100 + start [2] * 10 + start [3] - '0' * 111)); + case 5: return newSViv (-( start [1] * 1000 + start [2] * 100 + start [3] * 10 + start [4] - '0' * 1111)); + case 6: return newSViv (-(start [1] * 10000 + start [2] * 1000 + start [3] * 100 + start [4] * 10 + start [5] - '0' * 11111)); } else switch (len) { - case 1: return newSViv ( start [0] - '0' * 1); - case 2: return newSViv ( start [0] * 10 + start [1] - '0' * 11); - case 3: return newSViv ( start [0] * 100 + start [1] * 10 + start [2] - '0' * 111); - case 4: return newSViv ( start [0] * 1000 + start [1] * 100 + start [2] * 10 + start [3] - '0' * 1111); + case 1: return newSViv ( start [0] - '0' * 1); + case 2: return newSViv ( start [0] * 10 + start [1] - '0' * 11); + case 3: return newSViv ( start [0] * 100 + start [1] * 10 + start [2] - '0' * 111); + case 4: return newSViv ( start [0] * 1000 + start [1] * 100 + start [2] * 10 + start [3] - '0' * 1111); + case 5: return newSViv ( start [0] * 10000 + start [1] * 1000 + start [2] * 100 + start [3] * 10 + start [4] - '0' * 11111); } { @@ -1028,6 +1094,14 @@ ERR (", or ] expected while parsing array"); ++dec->cur; + + decode_ws (dec); + + if (*dec->cur == ']' && dec->json.flags & F_RELAXED) + { + ++dec->cur; + break; + } } DEC_DEC_DEPTH; @@ -1053,7 +1127,7 @@ else for (;;) { - decode_ws (dec); EXPECT_CH ('"'); + EXPECT_CH ('"'); // heuristic: assume that // a) decode_str + hv_store_ent are abysmally slow. @@ -1077,6 +1151,7 @@ decode_ws (dec); EXPECT_CH (':'); + decode_ws (dec); value = decode_sv (dec); if (!value) { @@ -1098,6 +1173,7 @@ decode_ws (dec); EXPECT_CH (':'); + decode_ws (dec); value = decode_sv (dec); if (!value) goto fail; @@ -1123,6 +1199,14 @@ ERR (", or } expected while parsing object/hash"); ++dec->cur; + + decode_ws (dec); + + if (*dec->cur == '}' && dec->json.flags & F_RELAXED) + { + ++dec->cur; + break; + } } DEC_DEC_DEPTH; @@ -1197,8 +1281,6 @@ static SV * decode_sv (dec_t *dec) { - decode_ws (dec); - // the beauty of JSON: you need exactly one character lookahead // to parse anything. switch (*dec->cur) @@ -1216,6 +1298,9 @@ if (dec->end - dec->cur >= 4 && !memcmp (dec->cur, "true", 4)) { dec->cur += 4; +#if JSON_SLOW + json_true = get_sv ("JSON::XS::true", 1); SvREADONLY_on (json_true); +#endif return SvREFCNT_inc (json_true); } else @@ -1227,6 +1312,9 @@ if (dec->end - dec->cur >= 5 && !memcmp (dec->cur, "false", 5)) { dec->cur += 5; +#if JSON_SLOW + json_false = get_sv ("JSON::XS::false", 1); SvREADONLY_on (json_false); +#endif return SvREFCNT_inc (json_false); } else @@ -1286,6 +1374,8 @@ dec.json.flags |= F_HOOK; *dec.end = 0; // this should basically be a nop, too, but make sure it's there + + decode_ws (&dec); sv = decode_sv (&dec); if (!(offset_return || !sv)) @@ -1363,6 +1453,11 @@ PROTOTYPES: DISABLE +void CLONE (...) + CODE: + json_stash = 0; + json_boolean_stash = 0; + void new (char *klass) PPCODE: { @@ -1370,7 +1465,10 @@ SvPOK_only (pv); Zero (SvPVX (pv), 1, JSON); ((JSON *)SvPVX (pv))->flags = F_DEFAULT; - XPUSHs (sv_2mortal (sv_bless (newRV_noinc (pv), json_stash))); + XPUSHs (sv_2mortal (sv_bless ( + newRV_noinc (pv), + strEQ (klass, "JSON::XS") ? JSON_STASH : gv_stashpv (klass, 1) + ))); } void ascii (JSON *self, int enable = 1) @@ -1387,6 +1485,7 @@ shrink = F_SHRINK allow_blessed = F_ALLOW_BLESSED convert_blessed = F_CONV_BLESSED + relaxed = F_RELAXED PPCODE: { if (enable) @@ -1397,6 +1496,23 @@ XPUSHs (ST (0)); } +void get_ascii (JSON *self) + ALIAS: + get_ascii = F_ASCII + get_latin1 = F_LATIN1 + get_utf8 = F_UTF8 + get_indent = F_INDENT + get_canonical = F_CANONICAL + get_space_before = F_SPACE_BEFORE + get_space_after = F_SPACE_AFTER + get_allow_nonref = F_ALLOW_NONREF + get_shrink = F_SHRINK + get_allow_blessed = F_ALLOW_BLESSED + get_convert_blessed = F_CONV_BLESSED + get_relaxed = F_RELAXED + PPCODE: + XPUSHs (boolSV (self->flags & ix)); + void max_depth (JSON *self, UV max_depth = 0x80000000UL) PPCODE: { @@ -1412,6 +1528,12 @@ XPUSHs (ST (0)); } +U32 get_max_depth (JSON *self) + CODE: + RETVAL = DEC_DEPTH (self->flags); + OUTPUT: + RETVAL + void max_size (JSON *self, UV max_size = 0) PPCODE: { @@ -1428,6 +1550,12 @@ XPUSHs (ST (0)); } +int get_max_size (JSON *self) + CODE: + RETVAL = DEC_SIZE (self->flags); + OUTPUT: + RETVAL + void filter_json_object (JSON *self, SV *cb = &PL_sv_undef) PPCODE: { @@ -1483,14 +1611,14 @@ PROTOTYPES: ENABLE -void to_json (SV *scalar) +void encode_json (SV *scalar) PPCODE: { JSON json = { F_DEFAULT | F_UTF8 }; XPUSHs (encode_json (scalar, &json)); } -void from_json (SV *jsonstr) +void decode_json (SV *jsonstr) PPCODE: { JSON json = { F_DEFAULT | F_UTF8 };