aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/insert.c4
-rw-r--r--src/prepare.c49
-rw-r--r--src/select.c14
-rw-r--r--src/sqliteInt.h20
-rw-r--r--src/tokenize.c12
-rw-r--r--src/trigger.c1
6 files changed, 78 insertions, 22 deletions
diff --git a/src/insert.c b/src/insert.c
index 0e52ee7a0..c0ab0cf37 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -370,7 +370,9 @@ static int autoIncBegin(
while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; }
if( pInfo==0 ){
pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo));
- if( pInfo==0 ) return 0;
+ sqlite3ParserAddCleanup(pToplevel, sqlite3DbFree, pInfo);
+ testcase( pParse->earlyCleanup );
+ if( pParse->db->mallocFailed ) return 0;
pInfo->pNext = pToplevel->pAinc;
pToplevel->pAinc = pInfo;
pInfo->pTab = pTab;
diff --git a/src/prepare.c b/src/prepare.c
index 3a2943f10..f93a6f07a 100644
--- a/src/prepare.c
+++ b/src/prepare.c
@@ -570,8 +570,16 @@ void sqlite3ParserReset(Parse *pParse){
agginfoFree(db, pThis);
pThis = pNext;
}
+ while( pParse->pCleanup ){
+ ParseCleanup *pThis = pParse->pCleanup;
+ pParse->pCleanup = pThis->pNext;
+ pThis->xCleanup(db, pThis->pPtr);
+ sqlite3DbFree(db, pThis);
+ }
sqlite3DbFree(db, pParse->aLabel);
- sqlite3ExprListDelete(db, pParse->pConstExpr);
+ if( pParse->pConstExpr ){
+ sqlite3ExprListDelete(db, pParse->pConstExpr);
+ }
if( db ){
assert( db->lookaside.bDisable >= pParse->disableLookaside );
db->lookaside.bDisable -= pParse->disableLookaside;
@@ -581,6 +589,45 @@ void sqlite3ParserReset(Parse *pParse){
}
/*
+** Add a new cleanup operation to a Parser. The cleanup should happen when
+** the parser object is destroyed. But, beware: the cleanup might happen
+** immediately.
+**
+** Use this mechanism for uncommon cleanups. There is a higher setup
+** cost for this mechansim (an extra malloc), so it should not be used
+** for common cleanups that happen on most calls. But for less
+** common cleanups, we save a single NULL-pointer comparison in
+** sqlite3ParserReset(), which reduces the total CPU cycle count.
+**
+** If a memory allocation error occurs, then the cleanup happens immediately.
+** When eithr SQLITE_DEBUG or SQLITE_COVERAGE_TEST are defined, the
+** pParse->earlyCleanup flag is set in that case. Calling code show verify
+** that test cases exist for which this happens, to guard against possible
+** use-after-free errors following an OOM. The preferred way to do this is
+** to immediately follow the call to this routine with:
+**
+** testcase( pParse->earlyCleanup );
+*/
+void sqlite3ParserAddCleanup(
+ Parse *pParse, /* Destroy when this Parser finishes */
+ void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */
+ void *pPtr /* Pointer to object to be cleaned up */
+){
+ ParseCleanup *pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup));
+ if( pCleanup ){
+ pCleanup->pNext = pParse->pCleanup;
+ pParse->pCleanup = pCleanup;
+ pCleanup->pPtr = pPtr;
+ pCleanup->xCleanup = xCleanup;
+ }else{
+ xCleanup(pParse->db, pPtr);
+#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
+ pParse->earlyCleanup = 1;
+#endif
+ }
+}
+
+/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
static int sqlite3Prepare(
diff --git a/src/select.c b/src/select.c
index 407a6a4b2..c4d17810e 100644
--- a/src/select.c
+++ b/src/select.c
@@ -4140,8 +4140,10 @@ static int flattenSubquery(
Table *pTabToDel = pSubitem->pTab;
if( pTabToDel->nTabRef==1 ){
Parse *pToplevel = sqlite3ParseToplevel(pParse);
- pTabToDel->pNextZombie = pToplevel->pZombieTab;
- pToplevel->pZombieTab = pTabToDel;
+ sqlite3ParserAddCleanup(pToplevel,
+ (void(*)(sqlite3*,void*))sqlite3DeleteTable,
+ pTabToDel);
+ testcase( pToplevel->earlyCleanup );
}else{
pTabToDel->nTabRef--;
}
@@ -4856,12 +4858,16 @@ static struct Cte *searchWith(
** statement with which it is associated.
*/
void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
- assert( bFree==0 || (pParse->pWith==0 && pParse->pWithToFree==0) );
if( pWith ){
assert( pParse->pWith!=pWith );
pWith->pOuter = pParse->pWith;
pParse->pWith = pWith;
- if( bFree ) pParse->pWithToFree = pWith;
+ if( bFree ){
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3WithDelete,
+ pWith);
+ testcase( pParse->earlyCleanup );
+ }
}
}
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 980ae6d58..6d1e598d9 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -1154,6 +1154,7 @@ typedef struct LookasideSlot LookasideSlot;
typedef struct Module Module;
typedef struct NameContext NameContext;
typedef struct Parse Parse;
+typedef struct ParseCleanup ParseCleanup;
typedef struct PreUpdate PreUpdate;
typedef struct PrintfArguments PrintfArguments;
typedef struct RenameToken RenameToken;
@@ -2186,7 +2187,6 @@ struct Table {
#endif
Trigger *pTrigger; /* List of triggers stored in pSchema */
Schema *pSchema; /* Schema that contains this table */
- Table *pNextZombie; /* Next on the Parse.pZombieTab list */
};
/*
@@ -3349,6 +3349,17 @@ struct TriggerPrg {
#endif
/*
+** An instance of the ParseCleanup object specifies an operation that
+** should be performed after parsing to deallocation resources obtained
+** during the parse and which are no longer needed.
+*/
+struct ParseCleanup {
+ ParseCleanup *pNext; /* Next cleanup task */
+ void *pPtr; /* Pointer to object to deallocate */
+ void (*xCleanup)(sqlite3*,void*); /* Deallocation routine */
+};
+
+/*
** An SQL parser context. A copy of this structure is passed through
** the parser and down into all the parser action routine in order to
** carry around information that is global to the entire parse.
@@ -3379,6 +3390,9 @@ struct Parse {
u8 okConstFactor; /* OK to factor out constants */
u8 disableLookaside; /* Number of times lookaside has been disabled */
u8 disableVtab; /* Disable all virtual tables for this parse */
+#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
+ u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */
+#endif
int nRangeReg; /* Size of the temporary register block */
int iRangeReg; /* First register in temporary register block */
int nErr; /* Number of errors seen */
@@ -3457,10 +3471,9 @@ struct Parse {
Token sArg; /* Complete text of a module argument */
Table **apVtabLock; /* Pointer to virtual tables needing locking */
#endif
- Table *pZombieTab; /* List of Table objects to delete after code gen */
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
With *pWith; /* Current WITH clause, or NULL */
- With *pWithToFree; /* Free this WITH object at the end of the parse */
+ ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */
#ifndef SQLITE_OMIT_ALTERTABLE
RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */
#endif
@@ -4827,6 +4840,7 @@ sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
void sqlite3ParserReset(Parse*);
+void sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
#ifdef SQLITE_ENABLE_NORMALIZE
char *sqlite3Normalize(Vdbe*, const char*);
#endif
diff --git a/src/tokenize.c b/src/tokenize.c
index bafda0322..712447c4c 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -713,19 +713,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
if( !IN_RENAME_OBJECT ){
sqlite3DeleteTrigger(db, pParse->pNewTrigger);
}
-
- if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree);
sqlite3DbFree(db, pParse->pVList);
- while( pParse->pAinc ){
- AutoincInfo *p = pParse->pAinc;
- pParse->pAinc = p->pNext;
- sqlite3DbFreeNN(db, p);
- }
- while( pParse->pZombieTab ){
- Table *p = pParse->pZombieTab;
- pParse->pZombieTab = p->pNextZombie;
- sqlite3DeleteTable(db, p);
- }
db->pParse = pParse->pParentParse;
pParse->pParentParse = 0;
assert( nErr==0 || pParse->rc!=SQLITE_OK );
diff --git a/src/trigger.c b/src/trigger.c
index dd4ed8c2e..0c8cf5334 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -1000,7 +1000,6 @@ static TriggerPrg *codeRowTrigger(
sqlite3VdbeDelete(v);
}
- assert( !pSubParse->pAinc && !pSubParse->pZombieTab );
assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg );
sqlite3ParserReset(pSubParse);
sqlite3StackFree(db, pSubParse);