SQLite4
Check-in [cb28262fc8]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Have sqlite4_authorizer_push() invoke any destructor passed to it if an error (i.e. a malloc failure) occurs.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: cb28262fc89c3f585658f0705aac38d5c531f4c9
User & Date: dan 2013-06-14 18:28:43
Context
2013-06-14
20:03
Add a destructor function to the collation_needed() API. check-in: 577089fa36 user: dan tags: trunk
18:28
Have sqlite4_authorizer_push() invoke any destructor passed to it if an error (i.e. a malloc failure) occurs. check-in: cb28262fc8 user: dan tags: trunk
17:16
Update create_collation() documentation. Have the create_collation() function invoke the destructor (if one was specified) before returning if it fails. check-in: bcf7a78f8b user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/auth.c.

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
48
49
50
51
52
53
54

55
56
57
58
59
60
61
struct Authorizer {
  void *pCtx;
  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
  void (*xDestroy)(void*);
  Authorizer *pNext;
};


/*
** Push an authorizer callback onto the stack.
*/
int sqlite4_authorizer_push(
  sqlite4 *db,
  void *pCtx,
  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
................................................................................
  Authorizer *pNew;

  sqlite4_mutex_enter(db->mutex);

  pNew = (Authorizer *)sqlite4DbMallocZero(db, sizeof(Authorizer));
  if( pNew==0 ){
    rc = SQLITE4_NOMEM;

  }else{
    pNew->pCtx = pCtx;
    pNew->xAuth = xAuth;
    pNew->xDestroy = xDestroy;
    pNew->pNext = db->pAuth;
    db->pAuth = pNew;
    sqlite4ExpirePreparedStatements(db);







<







 







>







30
31
32
33
34
35
36

37
38
39
40
41
42
43
..
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
struct Authorizer {
  void *pCtx;
  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
  void (*xDestroy)(void*);
  Authorizer *pNext;
};


/*
** Push an authorizer callback onto the stack.
*/
int sqlite4_authorizer_push(
  sqlite4 *db,
  void *pCtx,
  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
................................................................................
  Authorizer *pNew;

  sqlite4_mutex_enter(db->mutex);

  pNew = (Authorizer *)sqlite4DbMallocZero(db, sizeof(Authorizer));
  if( pNew==0 ){
    rc = SQLITE4_NOMEM;
    if( xDestroy ) xDestroy(pCtx);
  }else{
    pNew->pCtx = pCtx;
    pNew->xAuth = xAuth;
    pNew->xDestroy = xDestroy;
    pNew->pNext = db->pAuth;
    db->pAuth = pNew;
    sqlite4ExpirePreparedStatements(db);

Changes to src/sqlite.h.in.

1170
1171
1172
1173
1174
1175
1176





1177
1178
1179
1180
1181
1182
1183
** removes the topmost authorizer. When an authorization callback is 
** pushed onto the stack, the application may pass a pointer to a destructor 
** function as the fourth argument to sqlite3_authorizer_push(). If the
** destructor function pointer is not NULL, then it is invoked with a copy
** of the second argument to sqlite4_authorizer_push() as the only argument
** when the authorizer function is popped off the stack, or when the database
** connection is closed.





**
** Authorization callbacks are invoked by SQLite4 from within 
** sqlite4_prepare() and, when repreparing statements, from within 
** sqlite3_step(). During SQL statement compilation (or recompilation), 
** SQLite4 invokes the authorization callbacks for each action required
** to execute the statement. "Actions" are things like reading a value
** from a specific table column, modifying, inserting or deleting a row 







>
>
>
>
>







1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
** removes the topmost authorizer. When an authorization callback is 
** pushed onto the stack, the application may pass a pointer to a destructor 
** function as the fourth argument to sqlite3_authorizer_push(). If the
** destructor function pointer is not NULL, then it is invoked with a copy
** of the second argument to sqlite4_authorizer_push() as the only argument
** when the authorizer function is popped off the stack, or when the database
** connection is closed.
**
** If an error occurs within a call to sqlite4_authorizer_push(), then
** an SQLite error code is returned and no authorizer callback is added
** to the stack. If a destructor function was specified, then in this case
** it is invoked before the sqlite4_authorizer_push() call returns.
**
** Authorization callbacks are invoked by SQLite4 from within 
** sqlite4_prepare() and, when repreparing statements, from within 
** sqlite3_step(). During SQL statement compilation (or recompilation), 
** SQLite4 invokes the authorization callbacks for each action required
** to execute the statement. "Actions" are things like reading a value
** from a specific table column, modifying, inserting or deleting a row 

Changes to test/auth4.test.

11
12
13
14
15
16
17

18
19
20
21
22
23
24
..
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

129





































































130
131
#
# This file contains tests for the sqlite4_authorizer_push() and
# sqlite4_authorizer_pop() API functions.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl

set testprefix auth4

ifcapable !auth { finish_test ; return }

#--------------------------------------------------------------------
# Test cases auth4-1.* test that when there are multiple authorizers
# on the stack, they are invoked in order from most to least recently 
................................................................................
}
proc push {id} {
  set ::STACK [concat $id $::STACK]
  sqlite4_authorizer_push db [list auth_callback $id]
}
proc pop {} {
  set ::STACK [lrange $::STACK 1 end]
  sqlite4_authorizer_pop db
}

do_execsql_test 2.0 {
  DROP TABLE IF EXISTS t1;
  CREATE TABLE t1(x, y);
  INSERT INTO t1 VALUES(1, 'one');
}
................................................................................
  do_test 2.$i { test_stack } $::STACK
}

#--------------------------------------------------------------------
# Test that sqlite4_authorizer_pop() returns an error if the stack is
# empty when it is called.
#
db close
sqlite4 db test.db

do_test 3.1 {
  sqlite4_authorizer_pop db
} {SQLITE4_ERROR}
do_test 3.2 {
  sqlite4_authorizer_push db xyz
  sqlite4_authorizer_pop db
} {SQLITE4_OK}







































































finish_test








>







 







|







 







|
<


|
|



<
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
..
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
...
113
114
115
116
117
118
119
120

121
122
123
124
125
126
127

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#
# This file contains tests for the sqlite4_authorizer_push() and
# sqlite4_authorizer_pop() API functions.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set testprefix auth4

ifcapable !auth { finish_test ; return }

#--------------------------------------------------------------------
# Test cases auth4-1.* test that when there are multiple authorizers
# on the stack, they are invoked in order from most to least recently 
................................................................................
}
proc push {id} {
  set ::STACK [concat $id $::STACK]
  sqlite4_authorizer_push db [list auth_callback $id]
}
proc pop {} {
  set ::STACK [lrange $::STACK 1 end]
  catch { sqlite4_authorizer_pop db }
}

do_execsql_test 2.0 {
  DROP TABLE IF EXISTS t1;
  CREATE TABLE t1(x, y);
  INSERT INTO t1 VALUES(1, 'one');
}
................................................................................
  do_test 2.$i { test_stack } $::STACK
}

#--------------------------------------------------------------------
# Test that sqlite4_authorizer_pop() returns an error if the stack is
# empty when it is called.
#
reset_db


do_test 3.1 {
  list [catch { sqlite4_authorizer_pop db } msg] $msg
} {1 SQLITE4_ERROR}
do_test 3.2 {
  sqlite4_authorizer_push db xyz
  sqlite4_authorizer_pop db

} {}

#--------------------------------------------------------------------
# Test that authorizer destructors are invoked under the following
# circumstances:
#  
#    1. When an authorizer is popped from the stack,
#    2. When the database connection is closed, and
#    3. When an error occurs within the sqlite4_authorizer_push() call.
#
reset_db

do_test 4.1.1 {
  set ::xyz_destroyed 0
  sqlite4_authorizer_push db xyz {incr ::xyz_destroyed}
  set ::xyz_destroyed
} {0}
do_test 4.1.2 {
  sqlite4_authorizer_pop db
  set ::xyz_destroyed
} {1}
do_test 4.1.3 {
  set ::abc_destroyed 0
  set ::xyz_destroyed 0
  sqlite4_authorizer_push db abc {incr ::abc_destroyed}
  sqlite4_authorizer_push db xyz {incr ::xyz_destroyed}
  list $::abc_destroyed $::xyz_destroyed
} {0 0}
do_test 4.1.4 {
  sqlite4_authorizer_pop db
  list $::abc_destroyed $::xyz_destroyed
} {0 1}
do_test 4.1.5 {
  sqlite4_authorizer_pop db
  list $::abc_destroyed $::xyz_destroyed
} {1 1}
do_test 4.1.6 {
  db close
  list $::abc_destroyed $::xyz_destroyed
} {1 1}

reset_db

do_test 4.2.1 {
  set ::abc_destroyed 0
  set ::xyz_destroyed 0
  sqlite4_authorizer_push db abc {incr ::abc_destroyed}
  sqlite4_authorizer_push db xyz {incr ::xyz_destroyed}
  list $::abc_destroyed $::xyz_destroyed
} {0 0}
do_test 4.2.2 {
  db close
  list $::abc_destroyed $::xyz_destroyed
} {1 1}

# Normally we don't like to mix [do_faultsim_test] and friends with
# ordinary (non fault-injection) tests. But this one is OK because
# it runs very fast - sqlite4_authorizer_push() only calls malloc() once.
#
do_faultsim_test 4.3 -faults oom* -prep {
  reset_db
  set ::abc_destroyed 0
} -body {
  sqlite4_authorizer_push db abc {incr ::abc_destroyed}
} -test {
  faultsim_test_result {0 {}} {1 SQLITE4_NOMEM}
  if {$testrc    && $::abc_destroyed!=1} {error "destructor not invoked"}
  if {$testrc==0 && $::abc_destroyed!=0} {error "destructor was invoked"}
}


finish_test

Changes to test/test_auth.c.

16
17
18
19
20
21
22

23
24
25
26
27
28
29
..
59
60
61
62
63
64
65




66
67

68
69
70
71
72
73
74
...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138




139
140
141
142
143
144
145
146

147
148
149
150
151
152
153
...
162
163
164
165
166
167
168

169




170
171
172
173
174
175
176
177
#include "sqlite4.h"
#include "testInt.h"

typedef struct TestAuth TestAuth;
struct TestAuth {
  Tcl_Interp *interp;
  Tcl_Obj *pScript;

  sqlite4 *db;
};

static const char *sqlite4TestAuthCode(int rcauth){
  switch( rcauth ){
    case SQLITE4_CREATE_INDEX: return "SQLITE4_CREATE_INDEX";
    case SQLITE4_CREATE_TABLE: return "SQLITE4_CREATE_TABLE";
................................................................................
    case SQLITE4_SAVEPOINT: return "SQLITE4_SAVEPOINT";
  }
  return "unknown";
}

static void testauth_xDel(void *pCtx){
  TestAuth *p = (TestAuth *)pCtx;




  Tcl_DecrRefCount(p->pScript);
  sqlite4_free(sqlite4_db_env(p->db), p);

}

static int testauth_xAuth(
  void *pCtx,
  int code,
  const char *z1,
  const char *z2,
................................................................................
  }

  Tcl_DecrRefCount(pEval);
  return rc;
}

/*
** sqlite4_authorizer_push DB SCRIPT
*/
static int testauth_push(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  TestAuth *pNew;
  sqlite4 *db;
  int rc;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT");
    return TCL_ERROR;
  }
  rc = sqlite4TestDbHandle(interp, objv[1], &db);
  if( rc!=TCL_OK ) return rc;

  pNew = (TestAuth *)sqlite4_malloc(sqlite4_db_env(db), sizeof(TestAuth));
  if( !pNew ) return sqlite4TestSetResult(interp, SQLITE4_NOMEM);

  pNew->interp = interp;
  pNew->db = db;
  pNew->pScript = Tcl_DuplicateObj(objv[2]);
  Tcl_IncrRefCount(pNew->pScript);





  rc = sqlite4_authorizer_push(db, (void *)pNew, testauth_xAuth, testauth_xDel);
  if( rc!=SQLITE4_OK ){
    /* If sqlite4_authorizer_push() fails, the destructor is not invoked. */
    testauth_xDel((void *)pNew);
  }

  return sqlite4TestSetResult(interp, rc);

}

/*
** sqlite4_authorizer_pop DB
*/
static int testauth_pop(
  void *clientData,
................................................................................
    Tcl_WrongNumArgs(interp, 1, objv, "DB");
    return TCL_ERROR;
  }
  rc = sqlite4TestDbHandle(interp, objv[1], &db);
  if( rc!=TCL_OK ) return rc;

  rc = sqlite4_authorizer_pop(db);

  return sqlite4TestSetResult(interp, rc);




}

int Sqlitetest_auth_init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "sqlite4_authorizer_push", testauth_push, 0, 0);
  Tcl_CreateObjCommand(interp, "sqlite4_authorizer_pop", testauth_pop, 0, 0);
  return TCL_OK;
}








>







 







>
>
>
>

<
>







 







|











|
|





|
|





>
>
>
>



|
|

<
|
>







 







>
|
>
>
>
>








16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
..
60
61
62
63
64
65
66
67
68
69
70
71

72
73
74
75
76
77
78
79
...
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

154
155
156
157
158
159
160
161
162
...
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include "sqlite4.h"
#include "testInt.h"

typedef struct TestAuth TestAuth;
struct TestAuth {
  Tcl_Interp *interp;
  Tcl_Obj *pScript;
  Tcl_Obj *pDestroy;
  sqlite4 *db;
};

static const char *sqlite4TestAuthCode(int rcauth){
  switch( rcauth ){
    case SQLITE4_CREATE_INDEX: return "SQLITE4_CREATE_INDEX";
    case SQLITE4_CREATE_TABLE: return "SQLITE4_CREATE_TABLE";
................................................................................
    case SQLITE4_SAVEPOINT: return "SQLITE4_SAVEPOINT";
  }
  return "unknown";
}

static void testauth_xDel(void *pCtx){
  TestAuth *p = (TestAuth *)pCtx;
  if( p->pDestroy ){
    Tcl_EvalObjEx(p->interp, p->pDestroy, TCL_EVAL_GLOBAL);
    Tcl_DecrRefCount(p->pDestroy);
  }
  Tcl_DecrRefCount(p->pScript);

  ckfree(p);
}

static int testauth_xAuth(
  void *pCtx,
  int code,
  const char *z1,
  const char *z2,
................................................................................
  }

  Tcl_DecrRefCount(pEval);
  return rc;
}

/*
** sqlite4_authorizer_push DB SCRIPT ?DESTRUCTOR-SCRIPT?
*/
static int testauth_push(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  TestAuth *pNew;
  sqlite4 *db;
  int rc;

  if( objc!=3 && objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT ?DESTRUCTOR-SCRIPT?");
    return TCL_ERROR;
  }
  rc = sqlite4TestDbHandle(interp, objv[1], &db);
  if( rc!=TCL_OK ) return rc;

  pNew = (TestAuth *)ckalloc(sizeof(TestAuth));
  memset(pNew, 0, sizeof(TestAuth));

  pNew->interp = interp;
  pNew->db = db;
  pNew->pScript = Tcl_DuplicateObj(objv[2]);
  Tcl_IncrRefCount(pNew->pScript);
  if( objc==4 ){
    pNew->pDestroy = Tcl_DuplicateObj(objv[3]);
    Tcl_IncrRefCount(pNew->pDestroy);
  }

  rc = sqlite4_authorizer_push(db, (void *)pNew, testauth_xAuth, testauth_xDel);
  if( rc!=SQLITE4_OK ){
    sqlite4TestSetResult(interp, rc);
    return TCL_ERROR;
  }

  Tcl_ResetResult(interp);
  return TCL_OK;
}

/*
** sqlite4_authorizer_pop DB
*/
static int testauth_pop(
  void *clientData,
................................................................................
    Tcl_WrongNumArgs(interp, 1, objv, "DB");
    return TCL_ERROR;
  }
  rc = sqlite4TestDbHandle(interp, objv[1], &db);
  if( rc!=TCL_OK ) return rc;

  rc = sqlite4_authorizer_pop(db);
  if( rc ){
    sqlite4TestSetResult(interp, rc);
    return TCL_ERROR;
  }
  Tcl_ResetResult(interp);
  return TCL_OK;
}

int Sqlitetest_auth_init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "sqlite4_authorizer_push", testauth_push, 0, 0);
  Tcl_CreateObjCommand(interp, "sqlite4_authorizer_pop", testauth_pop, 0, 0);
  return TCL_OK;
}