Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -530,11 +530,11 @@ Mem *pOut; assert( pOp->p2>0 ); assert( pOp->p2<=(p->nMem-p->nCursor) ); pOut = &p->aMem[pOp->p2]; memAboutToChange(p, pOut); - if( VdbeMemDynamic(pOut) ){ + if( VdbeMemDynamicOrSubtype(pOut) ){ return out2PrereleaseWithClear(pOut); }else{ pOut->flags = MEM_Int; return pOut; } @@ -2556,11 +2556,11 @@ ** all valid. */ assert( p2nHdrParsed ); assert( rc==SQLITE_OK ); assert( sqlite3VdbeCheckMemInvariants(pDest) ); - if( VdbeMemDynamic(pDest) ) sqlite3VdbeMemSetNull(pDest); + if( VdbeMemDynamicOrSubtype(pDest) ) sqlite3VdbeMemSetNull(pDest); assert( t==pC->aType[p2] ); pDest->enc = encoding; if( pC->szRow>=aOffset[p2+1] ){ /* This is the common case where the desired content fits on the original ** page - where the content is not on an overflow page */ Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -250,14 +250,28 @@ #define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */ #define MEM_Static 0x0800 /* Mem.z points to a static string */ #define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */ #define MEM_Agg 0x2000 /* Mem.z points to an agg function context */ #define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x8000 /* Mem.eSubtype might be non-zero */ #ifdef SQLITE_OMIT_INCRBLOB #undef MEM_Zero #define MEM_Zero 0x0000 #endif + +/* Return TRUE if Mem X contains dynamically allocated content - anything +** that needs to be deallocated to avoid a leak. +*/ +#define VdbeMemDynamic(X) \ + (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame))!=0) + +/* Return TRUE if MEM x contains dynamically allocated content, or if +** x might have a non-zero subtype +*/ +#define VdbeMemDynamicOrSubtype(X) \ + (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame|MEM_Subtype))!=0) + /* ** Clear any existing type flags from a Mem and replace them with f */ #define MemSetTypeFlag(p, f) \ @@ -470,12 +484,10 @@ int sqlite3VdbeMemRealify(Mem*); int sqlite3VdbeMemNumerify(Mem*); void sqlite3VdbeMemCast(Mem*,u8,u8); int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,int,Mem*); void sqlite3VdbeMemRelease(Mem *p); -#define VdbeMemDynamic(X) \ - (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame))!=0) int sqlite3VdbeMemFinalize(Mem*, FuncDef*); const char *sqlite3OpcodeName(int); int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); int sqlite3VdbeMemClearAndResize(Mem *pMem, int n); int sqlite3VdbeCloseStatement(Vdbe *, int); Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -367,12 +367,14 @@ void sqlite3_result_null(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); } void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - pCtx->pOut->eSubtype = eSubtype & 0xff; + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pOut->db->mutex) ); + pOut->eSubtype = eSubtype & 0xff; + pOut->flags |= MEM_Subtype; } void sqlite3_result_text( sqlite3_context *pCtx, const char *z, int n, Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -356,11 +356,11 @@ ** for sqlite3VdbeMemRelease(). Use those other routines as the ** entry point for releasing Mem resources. */ static SQLITE_NOINLINE void vdbeMemClearExternAndSetNull(Mem *p){ assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) ); - assert( VdbeMemDynamic(p) ); + assert( VdbeMemDynamicOrSubtype(p) ); if( p->flags&MEM_Agg ){ sqlite3VdbeMemFinalize(p, p->u.pDef); assert( (p->flags & MEM_Agg)==0 ); testcase( p->flags & MEM_Dyn ); } @@ -374,10 +374,11 @@ VdbeFrame *pFrame = p->u.pFrame; pFrame->pParent = pFrame->v->pDelFrame; pFrame->v->pDelFrame = pFrame; } p->flags = MEM_Null; + p->eSubtype = 0; } /* ** Release memory held by the Mem p, both external memory cleared ** by p->xDel and memory in p->zMalloc. @@ -385,11 +386,11 @@ ** This is a helper routine invoked by sqlite3VdbeMemRelease() in ** the unusual case where there really is memory in p that needs ** to be freed. */ static SQLITE_NOINLINE void vdbeMemClear(Mem *p){ - if( VdbeMemDynamic(p) ){ + if( VdbeMemDynamicOrSubtype(p) ){ vdbeMemClearExternAndSetNull(p); } if( p->szMalloc ){ sqlite3DbFree(p->db, p->zMalloc); p->szMalloc = 0; @@ -407,11 +408,11 @@ ** Use sqlite3VdbeMemSetNull() to release just the Mem.xDel space ** prior to inserting new content into the Mem. */ void sqlite3VdbeMemRelease(Mem *p){ assert( sqlite3VdbeCheckMemInvariants(p) ); - if( VdbeMemDynamic(p) || p->szMalloc ){ + if( VdbeMemDynamicOrSubtype(p) || p->szMalloc ){ vdbeMemClear(p); } } /* @@ -646,11 +647,11 @@ ** Use this routine to reset the Mem prior to insert a new value. ** ** Use sqlite3VdbeMemRelease() to complete erase the Mem prior to abandoning it. */ void sqlite3VdbeMemSetNull(Mem *pMem){ - if( VdbeMemDynamic(pMem) ){ + if( VdbeMemDynamicOrSubtype(pMem) ){ vdbeMemClearExternAndSetNull(pMem); }else{ pMem->flags = MEM_Null; } } @@ -686,11 +687,11 @@ /* ** Delete any previous value and set the value stored in *pMem to val, ** manifest type INTEGER. */ void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){ - if( VdbeMemDynamic(pMem) ){ + if( VdbeMemDynamicOrSubtype(pMem) ){ vdbeReleaseAndSetInt64(pMem, val); }else{ pMem->u.i = val; pMem->flags = MEM_Int; } Index: test/json103.test ================================================================== --- test/json103.test +++ test/json103.test @@ -58,8 +58,21 @@ do_execsql_test json103-220 { SELECT b, json_group_object(c,a) FROM t1 WHERE rowid<7 GROUP BY b ORDER BY b; } {0 {{"n3":3,"n6":6}} 1 {{"n1":1,"n4":4}} 2 {{"n2":2,"n5":5}}} - +# ticket https://www.sqlite.org/src/info/f45ac567eaa9f93c 2016-01-30 +# Invalid JSON generated by json_group_array() +# +# The underlying problem is a failure to reset Mem.eSubtype +# +do_execsql_test json103-300 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1),('abc'); + SELECT + json_group_array(x), + json_group_array(json_object('x',x)) + FROM t1; +} {{[1,"abc"]} {[{"x":1},{"x":"abc"}]}} finish_test