/src/mozilla-central/storage/mozStorageAsyncStatement.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
2 | | * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include <limits.h> |
8 | | #include <stdio.h> |
9 | | |
10 | | #include "nsError.h" |
11 | | #include "nsMemory.h" |
12 | | #include "nsProxyRelease.h" |
13 | | #include "nsThreadUtils.h" |
14 | | #include "nsIClassInfoImpl.h" |
15 | | #include "Variant.h" |
16 | | |
17 | | #include "mozIStorageError.h" |
18 | | |
19 | | #include "mozStorageBindingParams.h" |
20 | | #include "mozStorageConnection.h" |
21 | | #include "mozStorageAsyncStatementJSHelper.h" |
22 | | #include "mozStorageAsyncStatementParams.h" |
23 | | #include "mozStoragePrivateHelpers.h" |
24 | | #include "mozStorageStatementRow.h" |
25 | | #include "mozStorageStatement.h" |
26 | | |
27 | | #include "mozilla/Logging.h" |
28 | | |
29 | | extern mozilla::LazyLogModule gStorageLog; |
30 | | |
31 | | namespace mozilla { |
32 | | namespace storage { |
33 | | |
34 | | //////////////////////////////////////////////////////////////////////////////// |
35 | | //// nsIClassInfo |
36 | | |
37 | | NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, |
38 | | mozIStorageAsyncStatement, |
39 | | mozIStorageBaseStatement, |
40 | | mozIStorageBindingParams, |
41 | | mozilla::storage::StorageBaseStatementInternal) |
42 | | |
43 | | class AsyncStatementClassInfo : public nsIClassInfo |
44 | | { |
45 | | public: |
46 | 0 | constexpr AsyncStatementClassInfo() {} |
47 | | |
48 | | NS_DECL_ISUPPORTS_INHERITED |
49 | | |
50 | | NS_IMETHOD |
51 | | GetInterfaces(uint32_t *_count, nsIID ***_array) override |
52 | 0 | { |
53 | 0 | return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array); |
54 | 0 | } |
55 | | |
56 | | NS_IMETHOD |
57 | | GetScriptableHelper(nsIXPCScriptable **_helper) override |
58 | 0 | { |
59 | 0 | static AsyncStatementJSHelper sJSHelper; |
60 | 0 | *_helper = &sJSHelper; |
61 | 0 | return NS_OK; |
62 | 0 | } |
63 | | |
64 | | NS_IMETHOD |
65 | | GetContractID(nsACString& aContractID) override |
66 | 0 | { |
67 | 0 | aContractID.SetIsVoid(true); |
68 | 0 | return NS_OK; |
69 | 0 | } |
70 | | |
71 | | NS_IMETHOD |
72 | | GetClassDescription(nsACString& aDesc) override |
73 | 0 | { |
74 | 0 | aDesc.SetIsVoid(true); |
75 | 0 | return NS_OK; |
76 | 0 | } |
77 | | |
78 | | NS_IMETHOD |
79 | | GetClassID(nsCID **_id) override |
80 | 0 | { |
81 | 0 | *_id = nullptr; |
82 | 0 | return NS_OK; |
83 | 0 | } |
84 | | |
85 | | NS_IMETHOD |
86 | | GetFlags(uint32_t *_flags) override |
87 | 0 | { |
88 | 0 | *_flags = 0; |
89 | 0 | return NS_OK; |
90 | 0 | } |
91 | | |
92 | | NS_IMETHOD |
93 | | GetClassIDNoAlloc(nsCID *_cid) override |
94 | 0 | { |
95 | 0 | return NS_ERROR_NOT_AVAILABLE; |
96 | 0 | } |
97 | | }; |
98 | | |
99 | 0 | NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; } |
100 | 0 | NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; } |
101 | | NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo) |
102 | | |
103 | | static AsyncStatementClassInfo sAsyncStatementClassInfo; |
104 | | |
105 | | //////////////////////////////////////////////////////////////////////////////// |
106 | | //// AsyncStatement |
107 | | |
108 | | AsyncStatement::AsyncStatement() |
109 | | : StorageBaseStatementInternal() |
110 | | , mFinalized(false) |
111 | 0 | { |
112 | 0 | } |
113 | | |
114 | | nsresult |
115 | | AsyncStatement::initialize(Connection *aDBConnection, |
116 | | sqlite3 *aNativeConnection, |
117 | | const nsACString &aSQLStatement) |
118 | 0 | { |
119 | 0 | MOZ_ASSERT(aDBConnection, "No database connection given!"); |
120 | 0 | MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(), "Database connection should be valid"); |
121 | 0 | MOZ_ASSERT(aNativeConnection, "No native connection given!"); |
122 | 0 |
|
123 | 0 | mDBConnection = aDBConnection; |
124 | 0 | mNativeConnection = aNativeConnection; |
125 | 0 | mSQLString = aSQLStatement; |
126 | 0 |
|
127 | 0 | MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)", |
128 | 0 | mSQLString.get(), this)); |
129 | 0 |
|
130 | | #ifdef DEBUG |
131 | | // We want to try and test for LIKE and that consumers are using |
132 | | // escapeStringForLIKE instead of just trusting user input. The idea to |
133 | | // check to see if they are binding a parameter after like instead of just |
134 | | // using a string. We only do this in debug builds because it's expensive! |
135 | | const nsCaseInsensitiveCStringComparator c; |
136 | | nsACString::const_iterator start, end, e; |
137 | | aSQLStatement.BeginReading(start); |
138 | | aSQLStatement.EndReading(end); |
139 | | e = end; |
140 | | while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) { |
141 | | // We have a LIKE in here, so we perform our tests |
142 | | // FindInReadable moves the iterator, so we have to get a new one for |
143 | | // each test we perform. |
144 | | nsACString::const_iterator s1, s2, s3; |
145 | | s1 = s2 = s3 = start; |
146 | | |
147 | | if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) || |
148 | | ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) || |
149 | | ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) { |
150 | | // At this point, we didn't find a LIKE statement followed by ?, :, |
151 | | // or @, all of which are valid characters for binding a parameter. |
152 | | // We will warn the consumer that they may not be safely using LIKE. |
153 | | NS_WARNING("Unsafe use of LIKE detected! Please ensure that you " |
154 | | "are using mozIStorageAsyncStatement::escapeStringForLIKE " |
155 | | "and that you are binding that result to the statement " |
156 | | "to prevent SQL injection attacks."); |
157 | | } |
158 | | |
159 | | // resetting start and e |
160 | | start = e; |
161 | | e = end; |
162 | | } |
163 | | #endif |
164 | |
|
165 | 0 | return NS_OK; |
166 | 0 | } |
167 | | |
168 | | mozIStorageBindingParams * |
169 | | AsyncStatement::getParams() |
170 | 0 | { |
171 | 0 | nsresult rv; |
172 | 0 |
|
173 | 0 | // If we do not have an array object yet, make it. |
174 | 0 | if (!mParamsArray) { |
175 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> array; |
176 | 0 | rv = NewBindingParamsArray(getter_AddRefs(array)); |
177 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
178 | 0 |
|
179 | 0 | mParamsArray = static_cast<BindingParamsArray *>(array.get()); |
180 | 0 | } |
181 | 0 |
|
182 | 0 | // If there isn't already any rows added, we'll have to add one to use. |
183 | 0 | if (mParamsArray->length() == 0) { |
184 | 0 | RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray)); |
185 | 0 | NS_ENSURE_TRUE(params, nullptr); |
186 | 0 |
|
187 | 0 | rv = mParamsArray->AddParams(params); |
188 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
189 | 0 |
|
190 | 0 | // We have to unlock our params because AddParams locks them. This is safe |
191 | 0 | // because no reference to the params object was, or ever will be given out. |
192 | 0 | params->unlock(nullptr); |
193 | 0 |
|
194 | 0 | // We also want to lock our array at this point - we don't want anything to |
195 | 0 | // be added to it. |
196 | 0 | mParamsArray->lock(); |
197 | 0 | } |
198 | 0 |
|
199 | 0 | return *mParamsArray->begin(); |
200 | 0 | } |
201 | | |
202 | | /** |
203 | | * If we are here then we know there are no pending async executions relying on |
204 | | * us (StatementData holds a reference to us; this also goes for our own |
205 | | * AsyncStatementFinalizer which proxies its release to the calling thread) and |
206 | | * so it is always safe to destroy our sqlite3_stmt if one exists. We can be |
207 | | * destroyed on the caller thread by garbage-collection/reference counting or on |
208 | | * the async thread by the last execution of a statement that already lost its |
209 | | * main-thread refs. |
210 | | */ |
211 | | AsyncStatement::~AsyncStatement() |
212 | 0 | { |
213 | 0 | destructorAsyncFinalize(); |
214 | 0 |
|
215 | 0 | // If we are getting destroyed on the wrong thread, proxy the connection |
216 | 0 | // release to the right thread. I'm not sure why we do this. |
217 | 0 | bool onCallingThread = false; |
218 | 0 | (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread); |
219 | 0 | if (!onCallingThread) { |
220 | 0 | // NS_ProxyRelase only magic forgets for us if mDBConnection is an |
221 | 0 | // nsCOMPtr. Which it is not; it's an nsRefPtr. |
222 | 0 | nsCOMPtr<nsIThread> targetThread(mDBConnection->threadOpenedOn); |
223 | 0 | NS_ProxyRelease( |
224 | 0 | "AsyncStatement::mDBConnection", |
225 | 0 | targetThread, mDBConnection.forget()); |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | //////////////////////////////////////////////////////////////////////////////// |
230 | | //// nsISupports |
231 | | |
232 | | NS_IMPL_ADDREF(AsyncStatement) |
233 | | NS_IMPL_RELEASE(AsyncStatement) |
234 | | |
235 | 0 | NS_INTERFACE_MAP_BEGIN(AsyncStatement) |
236 | 0 | NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement) |
237 | 0 | NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) |
238 | 0 | NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) |
239 | 0 | NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) |
240 | 0 | if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { |
241 | 0 | foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo); |
242 | 0 | } |
243 | 0 | else |
244 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement) |
245 | 0 | NS_INTERFACE_MAP_END |
246 | | |
247 | | |
248 | | //////////////////////////////////////////////////////////////////////////////// |
249 | | //// StorageBaseStatementInternal |
250 | | |
251 | | Connection * |
252 | | AsyncStatement::getOwner() |
253 | 0 | { |
254 | 0 | return mDBConnection; |
255 | 0 | } |
256 | | |
257 | | int |
258 | | AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt) |
259 | 0 | { |
260 | | #ifdef DEBUG |
261 | | // Make sure we are never called on the connection's owning thread. |
262 | | bool onOpenedThread = false; |
263 | | (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread); |
264 | | NS_ASSERTION(!onOpenedThread, |
265 | | "We should only be called on the async thread!"); |
266 | | #endif |
267 | |
|
268 | 0 | if (!mAsyncStatement) { |
269 | 0 | int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString, |
270 | 0 | &mAsyncStatement); |
271 | 0 | if (rc != SQLITE_OK) { |
272 | 0 | MOZ_LOG(gStorageLog, LogLevel::Error, |
273 | 0 | ("Sqlite statement prepare error: %d '%s'", rc, |
274 | 0 | ::sqlite3_errmsg(mNativeConnection))); |
275 | 0 | MOZ_LOG(gStorageLog, LogLevel::Error, |
276 | 0 | ("Statement was: '%s'", mSQLString.get())); |
277 | 0 | *_stmt = nullptr; |
278 | 0 | return rc; |
279 | 0 | } |
280 | 0 | MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)", |
281 | 0 | mSQLString.get(), |
282 | 0 | mAsyncStatement)); |
283 | 0 | } |
284 | 0 |
|
285 | 0 | *_stmt = mAsyncStatement; |
286 | 0 | return SQLITE_OK; |
287 | 0 | } |
288 | | |
289 | | nsresult |
290 | | AsyncStatement::getAsynchronousStatementData(StatementData &_data) |
291 | 0 | { |
292 | 0 | if (mFinalized) |
293 | 0 | return NS_ERROR_UNEXPECTED; |
294 | 0 | |
295 | 0 | // Pass null for the sqlite3_stmt; it will be requested on demand from the |
296 | 0 | // async thread. |
297 | 0 | _data = StatementData(nullptr, bindingParamsArray(), this); |
298 | 0 |
|
299 | 0 | return NS_OK; |
300 | 0 | } |
301 | | |
302 | | already_AddRefed<mozIStorageBindingParams> |
303 | | AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner) |
304 | 0 | { |
305 | 0 | if (mFinalized) |
306 | 0 | return nullptr; |
307 | 0 | |
308 | 0 | nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner)); |
309 | 0 | return params.forget(); |
310 | 0 | } |
311 | | |
312 | | |
313 | | //////////////////////////////////////////////////////////////////////////////// |
314 | | //// mozIStorageAsyncStatement |
315 | | |
316 | | // (nothing is specific to mozIStorageAsyncStatement) |
317 | | |
318 | | //////////////////////////////////////////////////////////////////////////////// |
319 | | //// StorageBaseStatementInternal |
320 | | |
321 | | // proxy to StorageBaseStatementInternal using its define helper. |
322 | | MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL( |
323 | | AsyncStatement, |
324 | | if (mFinalized) return NS_ERROR_UNEXPECTED;) |
325 | | |
326 | | NS_IMETHODIMP |
327 | | AsyncStatement::Finalize() |
328 | 0 | { |
329 | 0 | if (mFinalized) |
330 | 0 | return NS_OK; |
331 | 0 | |
332 | 0 | mFinalized = true; |
333 | 0 |
|
334 | 0 | MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s'", |
335 | 0 | mSQLString.get())); |
336 | 0 |
|
337 | 0 | asyncFinalize(); |
338 | 0 |
|
339 | 0 | // Release the params holder, so it can release the reference to us. |
340 | 0 | mStatementParamsHolder = nullptr; |
341 | 0 |
|
342 | 0 | return NS_OK; |
343 | 0 | } |
344 | | |
345 | | NS_IMETHODIMP |
346 | | AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters) |
347 | 0 | { |
348 | 0 | if (mFinalized) |
349 | 0 | return NS_ERROR_UNEXPECTED; |
350 | 0 | |
351 | 0 | BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters); |
352 | 0 | if (array->getOwner() != this) |
353 | 0 | return NS_ERROR_UNEXPECTED; |
354 | 0 | |
355 | 0 | if (array->length() == 0) |
356 | 0 | return NS_ERROR_UNEXPECTED; |
357 | 0 | |
358 | 0 | mParamsArray = array; |
359 | 0 | mParamsArray->lock(); |
360 | 0 |
|
361 | 0 | return NS_OK; |
362 | 0 | } |
363 | | |
364 | | NS_IMETHODIMP |
365 | | AsyncStatement::GetState(int32_t *_state) |
366 | 0 | { |
367 | 0 | if (mFinalized) |
368 | 0 | *_state = MOZ_STORAGE_STATEMENT_INVALID; |
369 | 0 | else |
370 | 0 | *_state = MOZ_STORAGE_STATEMENT_READY; |
371 | 0 |
|
372 | 0 | return NS_OK; |
373 | 0 | } |
374 | | |
375 | | //////////////////////////////////////////////////////////////////////////////// |
376 | | //// mozIStorageBindingParams |
377 | | |
378 | | BOILERPLATE_BIND_PROXIES( |
379 | | AsyncStatement, |
380 | | if (mFinalized) return NS_ERROR_UNEXPECTED; |
381 | | ) |
382 | | |
383 | | } // namespace storage |
384 | | } // namespace mozilla |