From 4747d382ff642ac19ea9fceeeb7eca76b4321679 Mon Sep 17 00:00:00 2001 From: Pali Date: Mon, 31 Jul 2017 14:35:17 +0200 Subject: [PATCH] Implement JSON type specification via second param in encode_json() --- XS.xs | 563 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 491 insertions(+), 72 deletions(-) diff --git a/XS.xs b/XS.xs index b644718..2ef5cae 100644 --- a/XS.xs +++ b/XS.xs @@ -133,6 +133,12 @@ mingw_modfl(long double x, long double *ip) # endif #endif /* compatibility with perl <5.14 */ +#ifndef SvTRUE_nomg +#define SvTRUE_nomg SvTRUE +#endif +#ifndef SvNV_nomg +#define SvNV_nomg SvNV +#endif #ifndef PERL_UNICODE_MAX #define PERL_UNICODE_MAX 0x10FFFF #endif @@ -203,6 +209,26 @@ mingw_modfl(long double x, long double *ip) # endif #endif +/* types */ +#define JSON_TYPE_SCALAR 0x0000 +#define JSON_TYPE_BOOL 0x0001 +#define JSON_TYPE_INT 0x0002 +#define JSON_TYPE_FLOAT 0x0003 +#define JSON_TYPE_STRING 0x0004 + +/* flags */ +#define JSON_TYPE_CAN_BE_NULL 0x0100 + +/* classes */ +#define JSON_TYPE_CLASS "Cpanel::JSON::XS::Type" +#define JSON_TYPE_ARRAYOF_CLASS "Cpanel::JSON::XS::Type::ArrayOf" +#define JSON_TYPE_HASHOF_CLASS "Cpanel::JSON::XS::Type::HashOf" +#define JSON_TYPE_ANYOF_CLASS "Cpanel::JSON::XS::Type::AnyOf" + +#define JSON_TYPE_ANYOF_SCALAR_INDEX 0 +#define JSON_TYPE_ANYOF_ARRAY_INDEX 1 +#define JSON_TYPE_ANYOF_HASH_INDEX 2 + /* three extra for rounding, sign, and end of string */ #define IVUV_MAXCHARS (sizeof (UV) * CHAR_BIT * 28 / 93 + 3) @@ -919,16 +945,74 @@ encode_comma (pTHX_ enc_t *enc) encode_space (aTHX_ enc); } -static void encode_sv (pTHX_ enc_t *enc, SV *sv); +static void encode_sv (pTHX_ enc_t *enc, SV *sv, SV *typesv); static void -encode_av (pTHX_ enc_t *enc, AV *av) +encode_av (pTHX_ enc_t *enc, AV *av, SV *typesv) { + AV *typeav = NULL; HVMAX_T i, len = av_len (av); if (enc->indent >= enc->json.max_depth) croak (ERR_NESTING_EXCEEDED); + SvGETMAGIC (typesv); + + if (SvOK (typesv)) + { + if (SvROK (typesv) && + SvOBJECT (SvRV (typesv)) && + SvTYPE (SvRV (typesv)) == SVt_PVAV) + { + HV *stash = SvSTASH (SvRV (typesv)); + char *name = LIKELY (!!stash) ? HvNAME (stash) : NULL; + if (LIKELY (name && strEQ (name, JSON_TYPE_ANYOF_CLASS))) + { + AV *type_any = (AV *)SvRV (typesv); + SV **typesv_ref = av_fetch (type_any, JSON_TYPE_ANYOF_ARRAY_INDEX, 0); + if (UNLIKELY (!typesv_ref)) + croak ("incorrectly constructed anyof type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)av)))); + typesv = *typesv_ref; + SvGETMAGIC (typesv); + if (!SvOK (typesv)) + croak ("no array alternative in anyof was specified for '%s'", + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)av)))); + } + } + + if (UNLIKELY(!SvROK (typesv))) + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)av)))); + + if (!SvOBJECT (SvRV (typesv)) && SvTYPE (SvRV (typesv)) == SVt_PVAV) + { + typeav = (AV *)SvRV (typesv); + if (len != av_len (typeav)) + croak ("array '%s' has different number of elements as in specified type '%s'", + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)av))), + SvPV_nolen (typesv)); + } + else if (SvOBJECT (SvRV (typesv)) && + SvTYPE (SvRV (typesv)) < SVt_PVAV) + { + HV *stash = SvSTASH (SvRV (typesv)); + char *name = LIKELY (!!stash) ? HvNAME (stash) : NULL; + if (LIKELY (name && strEQ (name, JSON_TYPE_ARRAYOF_CLASS))) + typesv = (SV *)SvRV (typesv); + else + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)av)))); + } + else + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)av)))); + } + encode_ch (aTHX_ enc, '['); if (len >= 0) @@ -939,10 +1023,13 @@ encode_av (pTHX_ enc_t *enc, AV *av) { SV **svp = av_fetch (av, i, 0); + if (typeav) + typesv = *(av_fetch (typeav, i, 0)); + encode_indent (aTHX_ enc); if (svp) - encode_sv (aTHX_ enc, *svp); + encode_sv (aTHX_ enc, *svp, typesv); else encode_const_str (aTHX_ enc, "null", 4, 0); @@ -956,24 +1043,34 @@ encode_av (pTHX_ enc_t *enc, AV *av) encode_ch (aTHX_ enc, ']'); } -static void -encode_hk (pTHX_ enc_t *enc, HE *he) +INLINE void +retrieve_hk (pTHX_ HE *he, char **key, I32 *klen) { - encode_ch (aTHX_ enc, '"'); + int utf8; if (HeKLEN (he) == HEf_SVKEY) { - SV *sv = HeSVKEY (he); STRLEN len; - char *str; - - str = SvPV (sv, len); - - encode_str (aTHX_ enc, str, len, SvUTF8 (sv)); + SV *sv = HeSVKEY (he); + *key = SvPV (sv, len); + *klen = (I32)len; + utf8 = SvUTF8 (sv); } else - encode_str (aTHX_ enc, HeKEY (he), (STRLEN)HeKLEN (he), HeKUTF8 (he)); + { + *key = HeKEY (he); + *klen = HeKLEN (he); + utf8 = HeKUTF8 (he); + } + + if (utf8) *klen = -(*klen); +} +static void +encode_hk (pTHX_ enc_t *enc, char *key, I32 klen) +{ + encode_ch (aTHX_ enc, '"'); + encode_str (aTHX_ enc, key, klen < 0 ? -klen : klen, klen < 0); encode_ch (aTHX_ enc, '"'); if (enc->json.flags & F_SPACE_BEFORE) encode_space (aTHX_ enc); @@ -1008,13 +1105,66 @@ he_cmp_slow (const void *a, const void *b) } static void -encode_hv (pTHX_ enc_t *enc, HV *hv) +encode_hv (pTHX_ enc_t *enc, HV *hv, SV *typesv) { + HV *typehv = NULL; HE *he; if (enc->indent >= enc->json.max_depth) croak (ERR_NESTING_EXCEEDED); + SvGETMAGIC (typesv); + + if (SvOK (typesv)) + { + if (SvROK (typesv) && + SvOBJECT (SvRV (typesv)) && + SvTYPE (SvRV (typesv)) == SVt_PVAV) + { + HV *stash = SvSTASH (SvRV (typesv)); + char *name = LIKELY (!!stash) ? HvNAME (stash) : NULL; + if (LIKELY (name && strEQ (name, JSON_TYPE_ANYOF_CLASS))) + { + AV *type_any = (AV *)SvRV (typesv); + SV **typesv_ref = av_fetch (type_any, JSON_TYPE_ANYOF_HASH_INDEX, 0); + if (UNLIKELY (!typesv_ref)) + croak ("incorrectly constructed anyof type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)hv)))); + typesv = *typesv_ref; + SvGETMAGIC (typesv); + if (!SvOK (typesv)) + croak ("no hash alternative in anyof was specified for '%s'", + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)hv)))); + } + } + + if (UNLIKELY(!SvROK (typesv))) + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)hv)))); + + if (!SvOBJECT (SvRV (typesv)) && SvTYPE (SvRV (typesv)) == SVt_PVHV) + typehv = (HV *)SvRV (typesv); + else if (SvOBJECT (SvRV (typesv)) && + SvTYPE (SvRV (typesv)) < SVt_PVAV) + { + HV *stash = SvSTASH (SvRV (typesv)); + char *name = LIKELY (!!stash) ? HvNAME (stash) : NULL; + if (LIKELY (name && strEQ (name, JSON_TYPE_HASHOF_CLASS))) + typesv = (SV *)SvRV (typesv); + else + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)hv)))); + } + else + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv_2mortal (newRV_inc ((SV *)hv)))); + } + + encode_ch (aTHX_ enc, '{'); /* for canonical output we have to sort by keys first */ @@ -1084,10 +1234,24 @@ encode_hv (pTHX_ enc_t *enc, HV *hv) while (count--) { + char *key; + I32 klen; + encode_indent (aTHX_ enc); he = hes [count]; - encode_hk (aTHX_ enc, he); - encode_sv (aTHX_ enc, UNLIKELY(SvMAGICAL (hv)) ? hv_iterval (hv, he) : HeVAL (he)); + retrieve_hk (aTHX_ he, &key, &klen); + encode_hk (aTHX_ enc, key, klen); + + if (typehv) + { + SV **typesv_ref = hv_fetch (typehv, key, klen, 0); + if (UNLIKELY (!typesv_ref)) + croak ("no type was specified for hash key '%s'", key); + + typesv = *typesv_ref; + } + + encode_sv (aTHX_ enc, UNLIKELY(SvMAGICAL (hv)) ? hv_iterval (hv, he) : HeVAL (he), typesv); if (count) encode_comma (aTHX_ enc); @@ -1105,9 +1269,23 @@ encode_hv (pTHX_ enc_t *enc, HV *hv) for (;;) { + char *key; + I32 klen; + encode_indent (aTHX_ enc); - encode_hk (aTHX_ enc, he); - encode_sv (aTHX_ enc, UNLIKELY(SvMAGICAL (hv)) ? hv_iterval (hv, he) : HeVAL (he)); + retrieve_hk (aTHX_ he, &key, &klen); + encode_hk (aTHX_ enc, key, klen); + + if (typehv) + { + SV **typesv_ref = hv_fetch (typehv, key, klen, 0); + if (UNLIKELY (!typesv_ref)) + croak ("no type was specified for hash key '%s'", key); + + typesv = *typesv_ref; + } + + encode_sv (aTHX_ enc, UNLIKELY(SvMAGICAL (hv)) ? hv_iterval (hv, he) : HeVAL (he), typesv); if (!(he = hv_iternext (hv))) break; @@ -1235,6 +1413,59 @@ encode_stringify(pTHX_ enc_t *enc, SV *sv, int isref) } +INLINE int +encode_bool_obj (pTHX_ enc_t *enc, SV *sv, int force_conversion, int as_string) +{ + dMY_CXT; + + HV *bstash = MY_CXT.json_boolean_stash; /* JSON-XS-3.x interop (Types::Serialiser/JSON::PP::Boolean) */ + HV *oldstash = MY_CXT.jsonold_boolean_stash; /* JSON-XS-2.x interop (JSON::XS::Boolean) */ + HV *mstash = MY_CXT.mojo_boolean_stash; /* Mojo::JSON::_Bool interop */ + HV *stash = SvSTASH (sv); + + if (stash == bstash || stash == mstash || stash == oldstash) + { + if (as_string) + encode_ch (aTHX_ enc, '"'); + if (SvIV_nomg (sv)) + encode_const_str (aTHX_ enc, "true", 4, 0); + else + encode_const_str (aTHX_ enc, "false", 5, 0); + if (as_string) + encode_ch (aTHX_ enc, '"'); + } + else if (force_conversion && enc->json.flags & F_CONV_BLESSED) + { + if (as_string) + encode_ch (aTHX_ enc, '"'); + if (SvTRUE_nomg (sv)) + encode_const_str (aTHX_ enc, "true", 4, 0); + else + encode_const_str (aTHX_ enc, "false", 5, 0); + if (as_string) + encode_ch (aTHX_ enc, '"'); + } + else + return 0; + + return 1; +} + +INLINE int +encode_bool_ref (pTHX_ enc_t *enc, SV *sv) +{ + int bool_type = ref_bool_type (aTHX_ sv); + + if (bool_type == 1) + encode_const_str (aTHX_ enc, "true", 4, 0); + else if (bool_type == 0) + encode_const_str (aTHX_ enc, "false", 5, 0); + else + return 0; + + return 1; +} + /* encode objects, arrays and special \0=false and \1=true values and other representations of booleans: JSON::PP::Boolean, Mojo::JSON::_Bool */ @@ -1249,20 +1480,10 @@ encode_rv (pTHX_ enc_t *enc, SV *rv) if (UNLIKELY(SvOBJECT (sv))) { - dMY_CXT; - HV *bstash = MY_CXT.json_boolean_stash; /* JSON-XS-3.x interop (Types::Serialiser/JSON::PP::Boolean) */ - HV *oldstash = MY_CXT.jsonold_boolean_stash; /* JSON-XS-2.x interop (JSON::XS::Boolean) */ - HV *mstash = MY_CXT.mojo_boolean_stash; /* Mojo::JSON::_Bool interop */ - HV *stash = SvSTASH (sv); - - if (stash == bstash || stash == mstash || stash == oldstash) - { - if (SvIV_nomg (sv)) - encode_const_str (aTHX_ enc, "true", 4, 0); - else - encode_const_str (aTHX_ enc, "false", 5, 0); - } - else if ((enc->json.flags & F_ALLOW_TAGS) + if (!encode_bool_obj (aTHX_ enc, sv, 0, 0)) + { + HV *stash = SvSTASH (sv); + if ((enc->json.flags & F_ALLOW_TAGS) && (method = gv_fetchmethod_autoload (stash, "FREEZE", 0))) { dMY_CXT; @@ -1293,7 +1514,7 @@ encode_rv (pTHX_ enc_t *enc, SV *rv) while (count) { - encode_sv (aTHX_ enc, SP[1 - count--]); + encode_sv (aTHX_ enc, SP[1 - count--], &PL_sv_undef); SPAGAIN; if (count) @@ -1328,7 +1549,7 @@ encode_rv (pTHX_ enc_t *enc, SV *rv) sv = POPs; PUTBACK; - encode_sv (aTHX_ enc, sv); + encode_sv (aTHX_ enc, sv, &PL_sv_undef); FREETMPS; LEAVE; } @@ -1345,25 +1566,19 @@ encode_rv (pTHX_ enc_t *enc, SV *rv) croak ("encountered object '%s', but neither allow_blessed, convert_blessed nor allow_tags settings are enabled (or TO_JSON/FREEZE method missing)", SvPV_nolen (sv_2mortal (newRV_inc (sv)))); } - else if (svt == SVt_PVHV) - encode_hv (aTHX_ enc, (HV *)sv); - else if (svt == SVt_PVAV) - encode_av (aTHX_ enc, (AV *)sv); - else if (svt < SVt_PVAV && svt != SVt_PVGV) + } + else if (svt < SVt_PVAV && svt != SVt_PVGV && svt != SVt_PVHV && svt != SVt_PVAV) { - int bool_type = ref_bool_type (aTHX_ sv); - - if (bool_type == 1) - encode_const_str (aTHX_ enc, "true", 4, 0); - else if (bool_type == 0) - encode_const_str (aTHX_ enc, "false", 5, 0); - else if (enc->json.flags & F_ALLOW_STRINGIFY) + if (!encode_bool_ref (aTHX_ enc, sv)) + { + if (enc->json.flags & F_ALLOW_STRINGIFY) encode_stringify(aTHX_ enc, sv, SvROK(sv)); else if (enc->json.flags & F_ALLOW_UNKNOWN) encode_const_str (aTHX_ enc, "null", 4, 0); else croak ("cannot encode reference to scalar '%s' unless the scalar is 0 or 1", SvPV_nolen (sv_2mortal (newRV_inc (sv)))); + } } else if (enc->json.flags & F_ALLOW_UNKNOWN) encode_const_str (aTHX_ enc, "null", 4, 0); @@ -1373,26 +1588,137 @@ encode_rv (pTHX_ enc_t *enc, SV *rv) } static void -encode_sv (pTHX_ enc_t *enc, SV *sv) +encode_bool (pTHX_ enc_t *enc, SV *sv) +{ + svtype svt; + + if (!SvROK (sv)) + { + if (UNLIKELY (sv == &PL_sv_yes)) + encode_const_str (aTHX_ enc, "true", 4, 0); + else if (UNLIKELY (sv == &PL_sv_no)) + encode_const_str (aTHX_ enc, "false", 5, 0); + else if (!SvOK (sv)) + encode_const_str (aTHX_ enc, "false", 5, 0); + else if (SvTRUE_nomg (sv)) + encode_const_str (aTHX_ enc, "true", 4, 0); + else + encode_const_str (aTHX_ enc, "false", 5, 0); + } + else + { + sv = SvRV (sv); + svt = SvTYPE (sv); + + if (UNLIKELY (SvOBJECT (sv))) + { + if (!encode_bool_obj (aTHX_ enc, sv, 1, 0)) + croak ("encountered object '%s', but convert_blessed is not enabled", + SvPV_nolen (sv_2mortal (newRV_inc (sv)))); + } + else if (svt < SVt_PVAV && svt != SVt_PVGV) + { + if (!encode_bool_ref (aTHX_ enc, sv)) + croak ("cannot encode reference to scalar '%s' unless the scalar is 0 or 1", + SvPV_nolen (sv_2mortal (newRV_inc (sv)))); + } + else + croak ("encountered %s, but does not represent boolean", + SvPV_nolen (sv_2mortal (newRV_inc (sv)))); + } +} + +static void +encode_sv (pTHX_ enc_t *enc, SV *sv, SV *typesv) { + IV type = 0; + int can_be_null = 0; + int process_ref = 0; + int force_conversion = 0; + SvGETMAGIC (sv); - if (UNLIKELY(sv == &PL_sv_yes )) + if (SvROK (sv) && !SvOBJECT (SvRV (sv))) + { + svtype svt = SvTYPE (SvRV (sv)); + if (svt == SVt_PVHV) + { + encode_hv (aTHX_ enc, (HV *)SvRV (sv), typesv); + return; + } + else if (svt == SVt_PVAV) + { + encode_av (aTHX_ enc, (AV *)SvRV (sv), typesv); + return; + } + } + + SvGETMAGIC (typesv); + + if (SvOK (typesv)) + { + if (!SvIOKp (typesv)) + { + if (SvROK (typesv) && + SvOBJECT (SvRV (typesv)) && + SvTYPE (SvRV (typesv)) == SVt_PVAV) + { + HV *stash = SvSTASH (SvRV (typesv)); + char *name = LIKELY (!!stash) ? HvNAME (stash) : NULL; + if (LIKELY (name && strEQ (name, JSON_TYPE_ANYOF_CLASS))) + { + AV *type_any = (AV *)SvRV (typesv); + SV **typesv_ref = av_fetch (type_any, JSON_TYPE_ANYOF_SCALAR_INDEX, 0); + if (UNLIKELY (!typesv_ref)) + croak ("incorrectly constructed anyof type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv)); + typesv = *typesv_ref; + SvGETMAGIC (typesv); + if (!SvIOKp (typesv)) + croak ("no scalar alternative in anyof was specified for '%s'", SvPV_nolen (sv)); + } + else + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv)); + } + else + croak ("encountered type (%s, 0x%x) was specified for '%s'", + SvPV_nolen (typesv), (unsigned int)SvFLAGS (typesv), + SvPV_nolen (sv)); + } + type = SvIVX (typesv); + } + + if (type) { - encode_const_str (aTHX_ enc, "true", 4, 0); + force_conversion = 1; + can_be_null = (type & JSON_TYPE_CAN_BE_NULL); + type &= ~JSON_TYPE_CAN_BE_NULL; } - else if (UNLIKELY(sv == &PL_sv_no )) + else { - encode_const_str (aTHX_ enc, "false", 5, 0); + if (UNLIKELY (sv == &PL_sv_yes || sv == &PL_sv_no)) type = JSON_TYPE_BOOL; + else if (SvNOKp (sv)) type = JSON_TYPE_FLOAT; + else if (SvIOKp (sv)) type = JSON_TYPE_INT; + else if (SvPOKp (sv)) type = JSON_TYPE_STRING; + else if (SvROK (sv)) process_ref = 1; + else if (!SvOK (sv)) can_be_null = 1; } - else if (SvNOKp (sv)) + + if (can_be_null && !SvOK (sv)) + encode_const_str (aTHX_ enc, "null", 4, 0); + else if (type == JSON_TYPE_BOOL) + encode_bool (aTHX_ enc, sv); + else if (type == JSON_TYPE_FLOAT) { char *savecur, *saveend; char inf_or_nan = 0; #ifdef NEED_NUMERIC_LOCALE_C char *locale = NULL; #endif - NV nv = SvNVX(sv); + NV nv = SvNOKp (sv) ? SvNVX (sv) : SvOK (sv) ? SvNV_nomg (sv) : 0; /* trust that perl will do the right thing w.r.t. JSON syntax. */ need (aTHX_ enc, NV_DIG + 32); savecur = enc->cur; @@ -1517,7 +1843,7 @@ encode_sv (pTHX_ enc_t *enc, SV *sv) enc->json.infnan_mode); } } - if (SvPOKp (sv) && !strEQ(enc->cur, SvPVX (sv))) { + if (!force_conversion && SvPOKp (sv) && !strEQ(enc->cur, SvPVX (sv))) { char *str = SvPVX (sv); STRLEN len = SvCUR (sv); enc->cur = savecur; @@ -1529,7 +1855,7 @@ encode_sv (pTHX_ enc_t *enc, SV *sv) } else { NV intpart; - if (!( inf_or_nan || Perl_modf(SvNVX(sv), &intpart) || SvIOK(sv) + if (!( inf_or_nan || (SvNOKp(sv) && Perl_modf(SvNVX(sv), &intpart)) || (!force_conversion && SvIOK(sv)) || strchr(enc->cur,'e') || strchr(enc->cur,'E') #if PERL_VERSION < 10 /* !!1 with 5.8 */ @@ -1544,19 +1870,73 @@ encode_sv (pTHX_ enc_t *enc, SV *sv) enc->cur += strlen (enc->cur); } } - else if (SvIOKp (sv)) + else if (type == JSON_TYPE_INT) { char *savecur, *saveend; /* we assume we can always read an IV as a UV and vice versa */ /* we assume two's complement */ /* we assume no aliasing issues in the union */ - if (SvIsUV (sv) ? SvUVX (sv) <= 59000 - : SvIVX (sv) <= 59000 && SvIVX (sv) >= -59000) + UV uv = 0; + IV iv = 0; + int is_neg = 0; + if (SvIOKp (sv)) + { + is_neg = !SvIsUV (sv); + iv = SvIVX (sv); + uv = SvUVX (sv); + } + else if (SvNOKp (sv)) + { + NV nv = SvNVX (sv); + is_neg = (nv < 0); + if (is_neg) + { + iv = (IV)nv; + uv = (UV)iv; + } + else + { + uv = (UV)nv; + iv = (IV)uv; + } + } + else if (SvPOKp (sv)) + { + int numtype = grok_number (SvPVX (sv), SvCUR (sv), &uv); + if (numtype & IS_NUMBER_IN_UV) + { + if (numtype & IS_NUMBER_NEG) + { + is_neg = 1; + if (LIKELY(uv <= (UV)(IV_MAX) + 1)) + iv = -(IV)uv; + else + iv = IV_MIN; /* underflow */ + uv = (UV)iv; + } + else + iv = (IV)uv; + } + else + { + is_neg = 1; + iv = SvIV_nomg (sv); + uv = (UV)iv; + } + } + else if (SvOK (sv)) + { + is_neg = 1; + iv = SvIV_nomg (sv); + uv = (UV)iv; + } + if (is_neg ? iv <= 59000 && iv >= -59000 + : uv <= 59000) { /* optimise the "small number case" */ /* code will likely be branchless and use only a single multiplication */ /* works for numbers up to 59074 */ - I32 i = (I32)SvIVX (sv); + I32 i = iv; U32 u; char digit, nz = 0; @@ -1589,12 +1969,12 @@ encode_sv (pTHX_ enc_t *enc, SV *sv) savecur = enc->cur; saveend = enc->end; enc->cur += - SvIsUV(sv) - ? snprintf (enc->cur, IVUV_MAXCHARS, "%" UVuf, (UV)SvUVX (sv)) - : snprintf (enc->cur, IVUV_MAXCHARS, "%" IVdf, (IV)SvIVX (sv)); + !is_neg + ? snprintf (enc->cur, IVUV_MAXCHARS, "%" UVuf, uv) + : snprintf (enc->cur, IVUV_MAXCHARS, "%" IVdf, iv); } - if (SvPOKp (sv) && !strEQ(savecur, SvPVX (sv))) { + if (!force_conversion && SvPOKp (sv) && !strEQ(savecur, SvPVX (sv))) { char *str = SvPVX (sv); STRLEN len = SvCUR (sv); enc->cur = savecur; @@ -1605,17 +1985,41 @@ encode_sv (pTHX_ enc_t *enc, SV *sv) *enc->cur = 0; } } - else if (SvPOKp (sv)) + else if (type == JSON_TYPE_STRING) { - char *str = SvPVX (sv); - STRLEN len = SvCUR (sv); - encode_ch (aTHX_ enc, '"'); - encode_str (aTHX_ enc, str, len, SvUTF8 (sv)); - encode_ch (aTHX_ enc, '"'); + if (UNLIKELY (sv == &PL_sv_yes)) + { + encode_ch (aTHX_ enc, '"'); + encode_const_str (aTHX_ enc, "true", 4, 0); + encode_ch (aTHX_ enc, '"'); + } + else if (UNLIKELY (sv == &PL_sv_no)) + { + encode_ch (aTHX_ enc, '"'); + encode_const_str (aTHX_ enc, "false", 5, 0); + encode_ch (aTHX_ enc, '"'); + } + else if (!UNLIKELY (SvROK(sv) && SvOBJECT (SvRV(sv))) || !encode_bool_obj (aTHX_ enc, SvRV(sv), 0, 1)) + { + char *str; + STRLEN len; + if (SvPOKp (sv)) + { + str = SvPVX (sv); + len = SvCUR (sv); + } + else + { + str = SvPV_nomg (sv, len); + } + encode_ch (aTHX_ enc, '"'); + encode_str (aTHX_ enc, str, len, SvUTF8 (sv)); + encode_ch (aTHX_ enc, '"'); + } } - else if (SvROK (sv)) + else if (process_ref) encode_rv (aTHX_ enc, sv); - else if (!SvOK (sv) || enc->json.flags & F_ALLOW_UNKNOWN) + else if (enc->json.flags & F_ALLOW_UNKNOWN) encode_const_str (aTHX_ enc, "null", 4, 0); else croak ("encountered perl type (%s,0x%x) that JSON cannot handle, check your input data", @@ -1641,7 +2045,7 @@ encode_json (pTHX_ SV *scalar, JSON *json, SV *typesv) : 0x110000UL; SvPOK_only (enc.sv); - encode_sv (aTHX_ &enc, scalar); + encode_sv (aTHX_ &enc, scalar, typesv); encode_nl (aTHX_ &enc); SvCUR_set (enc.sv, enc.cur - SvPVX (enc.sv)); @@ -3473,9 +3877,24 @@ MODULE = Cpanel::JSON::XS PACKAGE = Cpanel::JSON::XS BOOT: { + HV *stash; MY_CXT_INIT; init_MY_CXT(aTHX_ &MY_CXT); + stash = gv_stashpvs(JSON_TYPE_CLASS, GV_ADD); + newCONSTSUB(stash, "JSON_TYPE_BOOL", newSViv(JSON_TYPE_BOOL)); + newCONSTSUB(stash, "JSON_TYPE_INT", newSViv(JSON_TYPE_INT)); + newCONSTSUB(stash, "JSON_TYPE_FLOAT", newSViv(JSON_TYPE_FLOAT)); + newCONSTSUB(stash, "JSON_TYPE_STRING", newSViv(JSON_TYPE_STRING)); + newCONSTSUB(stash, "JSON_TYPE_INT_OR_NULL", newSViv(JSON_TYPE_INT | JSON_TYPE_CAN_BE_NULL)); + newCONSTSUB(stash, "JSON_TYPE_BOOL_OR_NULL", newSViv(JSON_TYPE_BOOL | JSON_TYPE_CAN_BE_NULL)); + newCONSTSUB(stash, "JSON_TYPE_FLOAT_OR_NULL", newSViv(JSON_TYPE_FLOAT | JSON_TYPE_CAN_BE_NULL)); + newCONSTSUB(stash, "JSON_TYPE_STRING_OR_NULL", newSViv(JSON_TYPE_STRING | JSON_TYPE_CAN_BE_NULL)); + newCONSTSUB(stash, "JSON_TYPE_CAN_BE_NULL", newSViv(JSON_TYPE_CAN_BE_NULL)); + newCONSTSUB(stash, "JSON_TYPE_ARRAYOF_CLASS", newSVpvs(JSON_TYPE_ARRAYOF_CLASS)); + newCONSTSUB(stash, "JSON_TYPE_HASHOF_CLASS", newSVpvs(JSON_TYPE_HASHOF_CLASS)); + newCONSTSUB(stash, "JSON_TYPE_ANYOF_CLASS", newSVpvs(JSON_TYPE_ANYOF_CLASS)); + NODEBUG_ON; /* the debugger completely breaks lvalue subs */ }