Coverage Report

Created: 2018-09-25 14:53

/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