diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/global.c | 3 | ||||
-rw-r--r-- | src/json.c | 288 | ||||
-rw-r--r-- | src/main.c | 13 | ||||
-rw-r--r-- | src/shell.c.in | 7 | ||||
-rw-r--r-- | src/sqlite.h.in | 1 | ||||
-rw-r--r-- | src/sqliteInt.h | 3 |
6 files changed, 193 insertions, 122 deletions
diff --git a/src/global.c b/src/global.c index 60cd13e2a..5db5565bf 100644 --- a/src/global.c +++ b/src/global.c @@ -244,6 +244,9 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* bSmallMalloc */ 1, /* bExtraSchemaChecks */ sizeof(LONGDOUBLE_TYPE)>8, /* bUseLongDouble */ +#ifdef SQLITE_DEBUG + 0, /* bJsonbValidate */ +#endif 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ diff --git a/src/json.c b/src/json.c index ab69f33ce..f800dc931 100644 --- a/src/json.c +++ b/src/json.c @@ -336,6 +336,7 @@ static u32 jsonXlateBlobToText(const JsonParse*,u32,JsonString*); static void jsonReturnParse(sqlite3_context*,JsonParse*); static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); static void jsonParseFree(JsonParse*); +static u32 jsonbPayloadSize(const JsonParse*, u32, u32*); /************************************************************************** ** Utility routines for dealing with JsonCache objects @@ -1228,6 +1229,166 @@ static int jsonIs4HexB(const char *z, int *pOp){ return 1; } + +/* +** Check a single element of the JSONB in pParse for validity. +** +** The element to be checked starts at offset i and must end at on the +** last byte before iEnd. +** +** Return 0 if everything is correct. Return the 1-based byte offset of the +** error if a problem is detected. (In other words, if the error is at offset +** 0, return 1). +*/ +static u32 jsonbValidityCheck(JsonParse *pParse, u32 i, u32 iEnd, u32 iDepth){ + u32 n, sz, j, k; + const u8 *z; + u8 x; + if( iDepth>JSON_MAX_DEPTH ) return i+1; + sz = 0; + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ) return i+1; + if( i+n+sz!=iEnd ) return i+1; + z = pParse->aBlob; + x = z[i] & 0x0f; + switch( x ){ + case JSONB_NULL: + case JSONB_TRUE: + case JSONB_FALSE: { + return n+sz==1 ? 0 : i+1; + } + default: { + return i+1; + } + case JSONB_INT: { + if( sz<1 ) return i+1; + j = i+n; + if( z[j]=='-' ){ + j++; + if( sz<2 ) return j; + } + k = i+n+sz; + while( j<k ){ + if( sqlite3Isdigit(z[j]) ){ + j++; + }else{ + return j+1; + } + } + return 0; + } + case JSONB_INT5: { + if( sz<3 ) return i+1; + j = i+n; + if( z[j]!='0' ) return j+1; + if( z[j+1]!='x' && z[j+1]!='X' ) return j+2; + j += 2; + k = i+n+sz; + while( j<k ){ + if( sqlite3Isxdigit(z[j]) ){ + j++; + }else{ + return j+1; + } + } + return 0; + } + case JSONB_FLOAT: + case JSONB_FLOAT5: { + u8 seen = 0; /* 0: initial. 1: '.' seen 2: 'e' seen */ + if( sz<2 ) return i+1; + j = i+n; + k = j+sz; + if( z[j]=='-' ){ + j++; + if( sz<3 ) return i+1; + } + if( z[j]=='.' ){ + if( !sqlite3Isdigit(z[j+1]) ) return i+1; + j += 2; + seen = 1; + }else if( z[j]=='0' && x==JSONB_FLOAT ){ + if( j+3>k ) return i+1; + if( z[j+1]!='.' ) return i+1; + j += 2; + seen = 1; + } + for(; j<k; j++){ + if( sqlite3Isdigit(z[j]) ) continue; + if( z[j]=='.' ){ + if( seen>0 ) return i+1; + if( x==JSONB_FLOAT && (j==k-1 || !sqlite3Isdigit(z[j+1])) ){ + return i+1; + } + seen = 1; + continue; + } + if( z[j]=='e' || z[j]=='E' ){ + if( seen==2 ) return i+1; + if( j==k-1 ) return i+1; + if( z[j+1]=='+' || z[j+1]=='-' ){ + j++; + if( j==k-1 ) return i+1; + } + seen = 2; + continue; + } + return i+1; + } + return 0; + } + case JSONB_TEXT: { + return 0; + } + case JSONB_TEXTJ: + case JSONB_TEXT5: { + return 0; + } + case JSONB_TEXTRAW: { + return 0; + } + case JSONB_ARRAY: { + u32 sub; + j = i+n; + k = j+sz; + while( j<k ){ + sz = 0; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return j+1; + if( j+n+sz>k ) return j+1; + sub = jsonbValidityCheck(pParse, j, j+n+sz, iDepth+1); + if( sub ) return sub; + j += n + sz; + } + assert( j==k ); + return 0; + } + case JSONB_OBJECT: { + u32 cnt = 0; + u32 sub; + j = i+n; + k = j+sz; + while( j<k ){ + sz = 0; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return j+1; + if( j+n+sz>k ) return j+1; + if( (cnt & 1)==0 ){ + x = z[j] & 0x0f; + if( x<JSONB_TEXT || x>JSONB_TEXTRAW ) return j+1; + } + sub = jsonbValidityCheck(pParse, j, j+n+sz, iDepth+1); + if( sub ) return sub; + cnt++; + j += n + sz; + } + assert( j==k ); + if( (cnt & 1)!=0 ) return j+1; + return 0; + } + } +} + /* ** Translate a single element of JSON text at pParse->zJson[i] into ** its equivalent binary JSONB representation. Append the translation into @@ -1713,7 +1874,12 @@ static int jsonConvertTextToBlob( i = jsonXlateTextToBlob(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ +#ifdef SQLITE_DEBUG assert( pParse->iDepth==0 ); + if( sqlite3Config.bJsonbValidate ){ + assert( jsonbValidityCheck(pParse, 0, pParse->nBlob, 0)==0 ); + } +#endif while( jsonIsspace(zJson[i]) ) i++; if( zJson[i] ){ i += json5Whitespace(&zJson[i]); @@ -3870,128 +4036,6 @@ json_type_done: } /* -** Check a single element of the JSONB in pParse for validity. -** -** The element to be checked starts at offset i and must end at on the -** last byte before iEnd. -** -** Return 0 if everything is correct. Return the 1-based byte offset of the -** error if a problem is detected. (In other words, if the error is at offset -** 0, return 1). -*/ -static u32 jsonbValidityCheck(JsonParse *pParse, u32 i, u32 iEnd, u32 iDepth){ - u32 n, sz, j, k; - const u8 *z; - u8 x; - if( iDepth>JSON_MAX_DEPTH ) return i+1; - sz = 0; - n = jsonbPayloadSize(pParse, i, &sz); - if( n==0 ) return i+1; - if( i+n+sz!=iEnd ) return i+1; - z = pParse->aBlob; - x = z[i] & 0x0f; - switch( x ){ - case JSONB_NULL: - case JSONB_TRUE: - case JSONB_FALSE: { - return n+sz==1 ? 0 : i+1; - } - default: { - return i+1; - } - case JSONB_INT: { - if( sz<1 ) return i+1; - j = i+n; - if( z[j]=='-' ){ - j++; - if( sz<2 ) return j; - } - k = i+n+sz; - while( j<k ){ - if( sqlite3Isdigit(z[j]) ){ - j++; - }else{ - return j+1; - } - } - return 0; - } - case JSONB_INT5: { - if( sz<3 ) return i+1; - j = i+n; - if( z[j]!='0' ) return j+1; - if( z[j+1]!='x' && z[j+1]!='X' ) return j+2; - j += 2; - k = i+n+sz; - while( j<k ){ - if( sqlite3Isxdigit(z[j]) ){ - j++; - }else{ - return j+1; - } - } - return 0; - } - case JSONB_FLOAT: - case JSONB_FLOAT5: { - if( sz<2 ) return i+1; - j = i+n; - k = j+sz; - if( z[j]=='-' ){ - j++; - if( sz<3 ) return i+1; - } - return 0; - } - case JSONB_TEXT: - case JSONB_TEXTJ: - case JSONB_TEXT5: - case JSONB_TEXTRAW: { - return 0; - } - case JSONB_ARRAY: { - u32 sub; - j = i+n; - k = j+sz; - while( j<k ){ - sz = 0; - n = jsonbPayloadSize(pParse, j, &sz); - if( n==0 ) return j+1; - if( j+n+sz>k ) return j+1; - sub = jsonbValidityCheck(pParse, j, k, iDepth+1); - if( sub ) return sub; - j += n + sz; - } - assert( j==k ); - return 0; - } - case JSONB_OBJECT: { - u32 cnt = 0; - u32 sub; - j = i+n; - k = j+sz; - while( j<k ){ - sz = 0; - n = jsonbPayloadSize(pParse, j, &sz); - if( n==0 ) return j+1; - if( j+n+sz>k ) return j+1; - if( (cnt & 1)==0 ){ - x = z[j] & 0x0f; - if( x<JSONB_TEXT || x>JSONB_TEXTRAW ) return j+1; - } - sub = jsonbValidityCheck(pParse, j, k, iDepth+1); - if( sub ) return sub; - cnt++; - j += n + sz; - } - assert( j==k ); - if( (cnt & 1)!=0 ) return j+1; - return 0; - } - } -} - -/* ** json_valid(JSON) ** json_valid(JSON, FLAGS) ** diff --git a/src/main.c b/src/main.c index 6acfdc325..553e3dc2d 100644 --- a/src/main.c +++ b/src/main.c @@ -4659,6 +4659,19 @@ int sqlite3_test_control(int op, ...){ break; } #endif + + /* sqlite3_test_control(SQLITE_TESTCTRL_VALIDATE_JSONB, (u8)trueFalse); + ** + ** Activate or deactivate validation of JSONB that is generated from + ** text. Off by default, as the validation is slow. Validation is + ** only available if compiled using SQLITE_DEBUG. + */ + case SQLITE_TESTCTRL_VALIDATE_JSONB: { +#if defined(SQLITE_DEBUG) + sqlite3Config.bJsonbValidate = (u8)(va_arg(ap, int)&0xff); +#endif + break; + } } va_end(ap); #endif /* SQLITE_UNTESTABLE */ diff --git a/src/shell.c.in b/src/shell.c.in index 6d1312d0f..af7b1b6cf 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -10770,6 +10770,7 @@ static int do_meta_command(char *zLine, ShellState *p){ {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, + {"validate_jsonb", SQLITE_TESTCTRL_VALIDATE_JSONB,0, "BOOLEAN" }, }; int testctrl = -1; int iCtrl = -1; @@ -10974,6 +10975,12 @@ static int do_meta_command(char *zLine, ShellState *p){ isOk = 3; } break; + case SQLITE_TESTCTRL_VALIDATE_JSONB: + if( nArg==3 ){ + sqlite3_test_control(testctrl, booleanValue(azArg[2])); + isOk = 3; + } + break; } } if( isOk==0 && iCtrl>=0 ){ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index ce32b85b1..84af1c54e 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -8300,6 +8300,7 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 #define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ +#define SQLITE_TESTCTRL_VALIDATE_JSONB 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index a0d596200..f2846ca6d 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -4177,6 +4177,9 @@ struct Sqlite3Config { u8 bSmallMalloc; /* Avoid large memory allocations if true */ u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */ u8 bUseLongDouble; /* Make use of long double */ +#ifdef SQLITE_DEBUG + u8 bJsonbValidate; /* Double-check JSONB parsing */ +#endif int mxStrlen; /* Maximum string length */ int neverCorrupt; /* Database is always well-formed */ int szLookaside; /* Default lookaside buffer size */ |