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