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 #ifndef storage_test_harness_h__
8 #define storage_test_harness_h__
9 
10 #include "gtest/gtest.h"
11 
12 #include "prthread.h"
13 #include "nsAppDirectoryServiceDefs.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsDirectoryServiceDefs.h"
16 #include "nsDirectoryServiceUtils.h"
17 #include "nsMemory.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsIThread.h"
20 #include "nsThreadUtils.h"
21 #include "mozilla/ReentrantMonitor.h"
22 
23 #include "mozIStorageService.h"
24 #include "mozIStorageConnection.h"
25 #include "mozIStorageStatementCallback.h"
26 #include "mozIStorageCompletionCallback.h"
27 #include "mozIStorageBindingParamsArray.h"
28 #include "mozIStorageBindingParams.h"
29 #include "mozIStorageAsyncStatement.h"
30 #include "mozIStorageStatement.h"
31 #include "mozIStoragePendingStatement.h"
32 #include "mozIStorageError.h"
33 #include "nsIInterfaceRequestorUtils.h"
34 #include "nsIEventTarget.h"
35 
36 #include "sqlite3.h"
37 
38 #define do_check_true(aCondition) EXPECT_TRUE(aCondition)
39 
40 #define do_check_false(aCondition) EXPECT_FALSE(aCondition)
41 
42 #define do_check_success(aResult) do_check_true(NS_SUCCEEDED(aResult))
43 
44 #define do_check_eq(aExpected, aActual) do_check_true(aExpected == aActual)
45 
46 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
47 
getService()48 already_AddRefed<mozIStorageService> getService() {
49   nsCOMPtr<mozIStorageService> ss =
50       do_CreateInstance("@mozilla.org/storage/service;1");
51   do_check_true(ss);
52   return ss.forget();
53 }
54 
getMemoryDatabase()55 already_AddRefed<mozIStorageConnection> getMemoryDatabase() {
56   nsCOMPtr<mozIStorageService> ss = getService();
57   nsCOMPtr<mozIStorageConnection> conn;
58   nsresult rv = ss->OpenSpecialDatabase(
59       kMozStorageMemoryStorageKey, VoidCString(),
60       mozIStorageService::CONNECTION_DEFAULT, getter_AddRefs(conn));
61   do_check_success(rv);
62   return conn.forget();
63 }
64 
65 already_AddRefed<mozIStorageConnection> getDatabase(
66     nsIFile* aDBFile = nullptr,
67     uint32_t aConnectionFlags = mozIStorageService::CONNECTION_DEFAULT) {
68   nsCOMPtr<nsIFile> dbFile;
69   nsresult rv;
70   if (!aDBFile) {
71     MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Can't get tmp dir off mainthread.");
72     (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
73                                  getter_AddRefs(dbFile));
74     NS_ASSERTION(dbFile, "The directory doesn't exists?!");
75 
76     rv = dbFile->Append(u"storage_test_db.sqlite"_ns);
77     do_check_success(rv);
78   } else {
79     dbFile = aDBFile;
80   }
81 
82   nsCOMPtr<mozIStorageService> ss = getService();
83   nsCOMPtr<mozIStorageConnection> conn;
84   rv = ss->OpenDatabase(dbFile, aConnectionFlags, getter_AddRefs(conn));
85   do_check_success(rv);
86   return conn.forget();
87 }
88 
89 class AsyncStatementSpinner : public mozIStorageStatementCallback,
90                               public mozIStorageCompletionCallback {
91  public:
92   NS_DECL_ISUPPORTS
93   NS_DECL_MOZISTORAGESTATEMENTCALLBACK
94   NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
95 
96   AsyncStatementSpinner();
97 
98   void SpinUntilCompleted();
99 
100   uint16_t completionReason;
101 
102  protected:
~AsyncStatementSpinner()103   virtual ~AsyncStatementSpinner() {}
104   volatile bool mCompleted;
105 };
106 
NS_IMPL_ISUPPORTS(AsyncStatementSpinner,mozIStorageStatementCallback,mozIStorageCompletionCallback)107 NS_IMPL_ISUPPORTS(AsyncStatementSpinner, mozIStorageStatementCallback,
108                   mozIStorageCompletionCallback)
109 
110 AsyncStatementSpinner::AsyncStatementSpinner()
111     : completionReason(0), mCompleted(false) {}
112 
113 NS_IMETHODIMP
HandleResult(mozIStorageResultSet * aResultSet)114 AsyncStatementSpinner::HandleResult(mozIStorageResultSet* aResultSet) {
115   return NS_OK;
116 }
117 
118 NS_IMETHODIMP
HandleError(mozIStorageError * aError)119 AsyncStatementSpinner::HandleError(mozIStorageError* aError) {
120   int32_t result;
121   nsresult rv = aError->GetResult(&result);
122   NS_ENSURE_SUCCESS(rv, rv);
123   nsAutoCString message;
124   rv = aError->GetMessage(message);
125   NS_ENSURE_SUCCESS(rv, rv);
126 
127   nsAutoCString warnMsg;
128   warnMsg.AppendLiteral(
129       "An error occurred while executing an async statement: ");
130   warnMsg.AppendInt(result);
131   warnMsg.Append(' ');
132   warnMsg.Append(message);
133   NS_WARNING(warnMsg.get());
134 
135   return NS_OK;
136 }
137 
138 NS_IMETHODIMP
HandleCompletion(uint16_t aReason)139 AsyncStatementSpinner::HandleCompletion(uint16_t aReason) {
140   completionReason = aReason;
141   mCompleted = true;
142   return NS_OK;
143 }
144 
145 NS_IMETHODIMP
Complete(nsresult,nsISupports *)146 AsyncStatementSpinner::Complete(nsresult, nsISupports*) {
147   mCompleted = true;
148   return NS_OK;
149 }
150 
SpinUntilCompleted()151 void AsyncStatementSpinner::SpinUntilCompleted() {
152   nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
153   nsresult rv = NS_OK;
154   bool processed = true;
155   while (!mCompleted && NS_SUCCEEDED(rv)) {
156     rv = thread->ProcessNextEvent(true, &processed);
157   }
158 }
159 
160 #define NS_DECL_ASYNCSTATEMENTSPINNER \
161   NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override;
162 
163 ////////////////////////////////////////////////////////////////////////////////
164 //// Async Helpers
165 
166 /**
167  * Execute an async statement, blocking the main thread until we get the
168  * callback completion notification.
169  */
blocking_async_execute(mozIStorageBaseStatement * stmt)170 void blocking_async_execute(mozIStorageBaseStatement* stmt) {
171   RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
172 
173   nsCOMPtr<mozIStoragePendingStatement> pendy;
174   (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy));
175   spinner->SpinUntilCompleted();
176 }
177 
178 /**
179  * Invoke AsyncClose on the given connection, blocking the main thread until we
180  * get the completion notification.
181  */
blocking_async_close(mozIStorageConnection * db)182 void blocking_async_close(mozIStorageConnection* db) {
183   RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
184 
185   db->AsyncClose(spinner);
186   spinner->SpinUntilCompleted();
187 }
188 
189 ////////////////////////////////////////////////////////////////////////////////
190 //// Mutex Watching
191 
192 /**
193  * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
194  * the caller (generally main) thread.  We do this by decorating the sqlite
195  * mutex logic with our own code that checks what thread it is being invoked on
196  * and sets a flag if it is invoked on the main thread.  We are able to easily
197  * decorate the SQLite mutex logic because SQLite allows us to retrieve the
198  * current function pointers being used and then provide a new set.
199  */
200 
201 sqlite3_mutex_methods orig_mutex_methods;
202 sqlite3_mutex_methods wrapped_mutex_methods;
203 
204 bool mutex_used_on_watched_thread = false;
205 PRThread* watched_thread = nullptr;
206 /**
207  * Ugly hack to let us figure out what a connection's async thread is.  If we
208  * were MOZILLA_INTERNAL_API and linked as such we could just include
209  * mozStorageConnection.h and just ask Connection directly.  But that turns out
210  * poorly.
211  *
212  * When the thread a mutex is invoked on isn't watched_thread we save it to this
213  * variable.
214  */
215 nsIThread* last_non_watched_thread = nullptr;
216 
217 /**
218  * Set a flag if the mutex is used on the thread we are watching, but always
219  * call the real mutex function.
220  */
wrapped_MutexEnter(sqlite3_mutex * mutex)221 extern "C" void wrapped_MutexEnter(sqlite3_mutex* mutex) {
222   if (PR_GetCurrentThread() == watched_thread)
223     mutex_used_on_watched_thread = true;
224   else
225     last_non_watched_thread = NS_GetCurrentThread();
226   orig_mutex_methods.xMutexEnter(mutex);
227 }
228 
wrapped_MutexTry(sqlite3_mutex * mutex)229 extern "C" int wrapped_MutexTry(sqlite3_mutex* mutex) {
230   if (::PR_GetCurrentThread() == watched_thread)
231     mutex_used_on_watched_thread = true;
232   return orig_mutex_methods.xMutexTry(mutex);
233 }
234 
235 class HookSqliteMutex {
236  public:
HookSqliteMutex()237   HookSqliteMutex() {
238     // We need to initialize and teardown SQLite to get it to set up the
239     // default mutex handlers for us so we can steal them and wrap them.
240     do_check_ok(sqlite3_initialize());
241     do_check_ok(sqlite3_shutdown());
242     do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
243     do_check_ok(
244         ::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
245     wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
246     wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
247     do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
248   }
249 
~HookSqliteMutex()250   ~HookSqliteMutex() {
251     do_check_ok(sqlite3_shutdown());
252     do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &orig_mutex_methods));
253     do_check_ok(sqlite3_initialize());
254   }
255 };
256 
257 /**
258  * Call to clear the watch state and to set the watching against this thread.
259  *
260  * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
261  * this method was last called.  Since we're talking about the current thread,
262  * there are no race issues to be concerned about
263  */
watch_for_mutex_use_on_this_thread()264 void watch_for_mutex_use_on_this_thread() {
265   watched_thread = ::PR_GetCurrentThread();
266   mutex_used_on_watched_thread = false;
267 }
268 
269 ////////////////////////////////////////////////////////////////////////////////
270 //// Thread Wedgers
271 
272 /**
273  * A runnable that blocks until code on another thread invokes its unwedge
274  * method.  By dispatching this to a thread you can ensure that no subsequent
275  * runnables dispatched to the thread will execute until you invoke unwedge.
276  *
277  * The wedger is self-dispatching, just construct it with its target.
278  */
279 class ThreadWedger : public mozilla::Runnable {
280  public:
ThreadWedger(nsIEventTarget * aTarget)281   explicit ThreadWedger(nsIEventTarget* aTarget)
282       : mozilla::Runnable("ThreadWedger"),
283         mReentrantMonitor("thread wedger"),
284         unwedged(false) {
285     aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
286   }
287 
Run()288   NS_IMETHOD Run() override {
289     mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
290 
291     if (!unwedged) automon.Wait();
292 
293     return NS_OK;
294   }
295 
unwedge()296   void unwedge() {
297     mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
298     unwedged = true;
299     automon.Notify();
300   }
301 
302  private:
303   mozilla::ReentrantMonitor mReentrantMonitor;
304   bool unwedged;
305 };
306 
307 ////////////////////////////////////////////////////////////////////////////////
308 //// Async Helpers
309 
310 /**
311  * A horrible hack to figure out what the connection's async thread is.  By
312  * creating a statement and async dispatching we can tell from the mutex who
313  * is the async thread, PRThread style.  Then we map that to an nsIThread.
314  */
get_conn_async_thread(mozIStorageConnection * db)315 already_AddRefed<nsIThread> get_conn_async_thread(mozIStorageConnection* db) {
316   // Make sure we are tracking the current thread as the watched thread
317   watch_for_mutex_use_on_this_thread();
318 
319   // - statement with nothing to bind
320   nsCOMPtr<mozIStorageAsyncStatement> stmt;
321   db->CreateAsyncStatement("SELECT 1"_ns, getter_AddRefs(stmt));
322   blocking_async_execute(stmt);
323   stmt->Finalize();
324 
325   nsCOMPtr<nsIThread> asyncThread = last_non_watched_thread;
326 
327   // Additionally, check that the thread we get as the background thread is the
328   // same one as the one we report from getInterface.
329   nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
330   nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
331   do_check_eq(allegedAsyncThread, asyncThread);
332   return asyncThread.forget();
333 }
334 
335 #endif  // storage_test_harness_h__
336