Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/base/nsConsoleService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
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
/*
8
 * Maintains a circular buffer of recent messages, and notifies
9
 * listeners when new messages are logged.
10
 */
11
12
/* Threadsafe. */
13
14
#include "nsMemory.h"
15
#include "nsCOMArray.h"
16
#include "nsThreadUtils.h"
17
18
#include "nsConsoleService.h"
19
#include "nsConsoleMessage.h"
20
#include "nsIClassInfoImpl.h"
21
#include "nsIConsoleListener.h"
22
#include "nsIObserverService.h"
23
#include "nsPrintfCString.h"
24
#include "nsProxyRelease.h"
25
#include "nsIScriptError.h"
26
#include "nsISupportsPrimitives.h"
27
28
#include "mozilla/Preferences.h"
29
#include "mozilla/Services.h"
30
#include "mozilla/SystemGroup.h"
31
32
#if defined(ANDROID)
33
#include <android/log.h>
34
#include "mozilla/dom/ContentChild.h"
35
#endif
36
#ifdef XP_WIN
37
#include <windows.h>
38
#endif
39
40
#ifdef MOZ_TASK_TRACER
41
#include "GeckoTaskTracer.h"
42
using namespace mozilla::tasktracer;
43
#endif
44
45
using namespace mozilla;
46
47
NS_IMPL_ADDREF(nsConsoleService)
48
NS_IMPL_RELEASE(nsConsoleService)
49
NS_IMPL_CLASSINFO(nsConsoleService, nullptr,
50
                  nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
51
                  NS_CONSOLESERVICE_CID)
52
NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver)
53
NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver)
54
55
static const bool gLoggingEnabled = true;
56
static const bool gLoggingBuffered = true;
57
#ifdef XP_WIN
58
static bool gLoggingToDebugger = true;
59
#endif // XP_WIN
60
#if defined(ANDROID)
61
static bool gLoggingLogcat = false;
62
#endif // defined(ANDROID)
63
64
nsConsoleService::MessageElement::~MessageElement()
65
8.65k
{
66
8.65k
}
67
68
nsConsoleService::nsConsoleService()
69
  : mCurrentSize(0)
70
  , mDeliveringMessage(false)
71
  , mLock("nsConsoleService.mLock")
72
1
{
73
1
  // XXX grab this from a pref!
74
1
  // hm, but worry about circularity, bc we want to be able to report
75
1
  // prefs errs...
76
1
  mMaximumSize = 250;
77
1
78
#ifdef XP_WIN
79
  // This environment variable controls whether the console service
80
  // should be prevented from putting output to the attached debugger.
81
  // It only affects the Windows platform.
82
  //
83
  // To disable OutputDebugString, set:
84
  //   MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1
85
  //
86
  const char* disableDebugLoggingVar = getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT");
87
  gLoggingToDebugger = !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0');
88
#endif // XP_WIN
89
}
90
91
92
void
93
nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID)
94
0
{
95
0
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
96
0
  MutexAutoLock lock(mLock);
97
0
98
0
  for (MessageElement* e = mMessages.getFirst(); e != nullptr; ) {
99
0
    // Only messages implementing nsIScriptError interface expose the
100
0
    // inner window ID.
101
0
    nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
102
0
    if (!scriptError) {
103
0
      e = e->getNext();
104
0
      continue;
105
0
    }
106
0
    uint64_t innerWindowID;
107
0
    nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
108
0
    if (NS_FAILED(rv) || innerWindowID != innerID) {
109
0
      e = e->getNext();
110
0
      continue;
111
0
    }
112
0
113
0
    MessageElement* next = e->getNext();
114
0
    e->remove();
115
0
    delete e;
116
0
    mCurrentSize--;
117
0
    MOZ_ASSERT(mCurrentSize < mMaximumSize);
118
0
119
0
    e = next;
120
0
  }
121
0
}
122
123
void
124
nsConsoleService::ClearMessages()
125
0
{
126
0
  // NB: A lock is not required here as it's only called from |Reset| which
127
0
  //     locks for us and from the dtor.
128
0
  while (!mMessages.isEmpty()) {
129
0
    MessageElement* e = mMessages.popFirst();
130
0
    delete e;
131
0
  }
132
0
  mCurrentSize = 0;
133
0
}
134
135
nsConsoleService::~nsConsoleService()
136
0
{
137
0
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
138
0
139
0
  ClearMessages();
140
0
}
141
142
class AddConsolePrefWatchers : public Runnable
143
{
144
public:
145
  explicit AddConsolePrefWatchers(nsConsoleService* aConsole)
146
    : mozilla::Runnable("AddConsolePrefWatchers")
147
    , mConsole(aConsole)
148
1
  {
149
1
  }
150
151
  NS_IMETHOD Run() override
152
0
  {
153
#if defined(ANDROID)
154
    Preferences::AddBoolVarCache(&gLoggingLogcat, "consoleservice.logcat",
155
    #ifdef RELEASE_OR_BETA
156
      false
157
    #else
158
      true
159
    #endif
160
    );
161
#endif // defined(ANDROID)
162
163
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
164
0
    MOZ_ASSERT(obs);
165
0
    obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
166
0
    obs->AddObserver(mConsole, "inner-window-destroyed", false);
167
0
168
0
    if (!gLoggingBuffered) {
169
0
      mConsole->Reset();
170
0
    }
171
0
    return NS_OK;
172
0
  }
173
174
private:
175
  RefPtr<nsConsoleService> mConsole;
176
};
177
178
nsresult
179
nsConsoleService::Init()
180
1
{
181
1
  NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
182
1
183
1
  return NS_OK;
184
1
}
185
186
namespace {
187
188
class LogMessageRunnable : public Runnable
189
{
190
public:
191
  LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService)
192
    : mozilla::Runnable("LogMessageRunnable")
193
    , mMessage(aMessage)
194
    , mService(aService)
195
0
  { }
196
197
  NS_DECL_NSIRUNNABLE
198
199
private:
200
  nsCOMPtr<nsIConsoleMessage> mMessage;
201
  RefPtr<nsConsoleService> mService;
202
};
203
204
NS_IMETHODIMP
205
LogMessageRunnable::Run()
206
0
{
207
0
  MOZ_ASSERT(NS_IsMainThread());
208
0
209
0
  // Snapshot of listeners so that we don't reenter this hash during
210
0
  // enumeration.
211
0
  nsCOMArray<nsIConsoleListener> listeners;
212
0
  mService->CollectCurrentListeners(listeners);
213
0
214
0
  mService->SetIsDelivering();
215
0
216
0
  for (int32_t i = 0; i < listeners.Count(); ++i) {
217
0
    listeners[i]->Observe(mMessage);
218
0
  }
219
0
220
0
  mService->SetDoneDelivering();
221
0
222
0
  return NS_OK;
223
0
}
224
225
} // namespace
226
227
// nsIConsoleService methods
228
NS_IMETHODIMP
229
nsConsoleService::LogMessage(nsIConsoleMessage* aMessage)
230
8.90k
{
231
8.90k
  return LogMessageWithMode(aMessage, OutputToLog);
232
8.90k
}
233
234
// This can be called off the main thread.
235
nsresult
236
nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage,
237
                                     nsConsoleService::OutputMode aOutputMode)
238
8.90k
{
239
8.90k
  if (!aMessage) {
240
0
    return NS_ERROR_INVALID_ARG;
241
0
  }
242
8.90k
243
8.90k
  if (!gLoggingEnabled) {
244
0
    return NS_OK;
245
0
  }
246
8.90k
247
8.90k
  if (NS_IsMainThread() && mDeliveringMessage) {
248
0
    nsCString msg;
249
0
    aMessage->ToString(msg);
250
0
    NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted "
251
0
      "to display a message to the console while in a console listener. "
252
0
      "The following message was discarded: \"%s\"", msg.get()).get());
253
0
    return NS_ERROR_FAILURE;
254
0
  }
255
8.90k
256
8.90k
  RefPtr<LogMessageRunnable> r;
257
8.90k
  nsCOMPtr<nsIConsoleMessage> retiredMessage;
258
8.90k
259
8.90k
  /*
260
8.90k
   * Lock while updating buffer, and while taking snapshot of
261
8.90k
   * listeners array.
262
8.90k
   */
263
8.90k
  {
264
8.90k
    MutexAutoLock lock(mLock);
265
8.90k
266
#if defined(ANDROID)
267
    if (gLoggingLogcat && aOutputMode == OutputToLog) {
268
      nsCString msg;
269
      aMessage->ToString(msg);
270
271
      /** Attempt to use the process name as the log tag. */
272
      mozilla::dom::ContentChild* child =
273
          mozilla::dom::ContentChild::GetSingleton();
274
      nsCString appName;
275
      if (child) {
276
        child->GetProcessName(appName);
277
      } else {
278
        appName = "GeckoConsole";
279
      }
280
281
      uint32_t logLevel = 0;
282
      aMessage->GetLogLevel(&logLevel);
283
284
      android_LogPriority logPriority = ANDROID_LOG_INFO;
285
      switch (logLevel) {
286
        case nsIConsoleMessage::debug:
287
          logPriority = ANDROID_LOG_DEBUG;
288
          break;
289
        case nsIConsoleMessage::info:
290
          logPriority = ANDROID_LOG_INFO;
291
          break;
292
        case nsIConsoleMessage::warn:
293
          logPriority = ANDROID_LOG_WARN;
294
          break;
295
        case nsIConsoleMessage::error:
296
          logPriority = ANDROID_LOG_ERROR;
297
          break;
298
      }
299
300
      __android_log_print(logPriority, appName.get(), "%s", msg.get());
301
    }
302
#endif
303
#ifdef XP_WIN
304
    if (gLoggingToDebugger && IsDebuggerPresent()) {
305
      nsString msg;
306
      aMessage->GetMessageMoz(msg);
307
      msg.Append('\n');
308
      OutputDebugStringW(msg.get());
309
    }
310
#endif
311
#ifdef MOZ_TASK_TRACER
312
    if (IsStartLogging()) {
313
      nsCString msg;
314
      aMessage->ToString(msg);
315
      int prefixPos = msg.Find(GetJSLabelPrefix());
316
      if (prefixPos >= 0) {
317
        nsDependentCSubstring submsg(msg, prefixPos);
318
        AddLabel("%s", submsg.BeginReading());
319
      }
320
    }
321
#endif
322
323
8.90k
    if (gLoggingBuffered) {
324
8.90k
      MessageElement* e = new MessageElement(aMessage);
325
8.90k
      mMessages.insertBack(e);
326
8.90k
      if (mCurrentSize != mMaximumSize) {
327
250
        mCurrentSize++;
328
8.65k
      } else {
329
8.65k
        MessageElement* p = mMessages.popFirst();
330
8.65k
        MOZ_ASSERT(p);
331
8.65k
        p->swapMessage(retiredMessage);
332
8.65k
        delete p;
333
8.65k
      }
334
8.90k
    }
335
8.90k
336
8.90k
    if (mListeners.Count() > 0) {
337
0
      r = new LogMessageRunnable(aMessage, this);
338
0
    }
339
8.90k
  }
340
8.90k
341
8.90k
  if (retiredMessage) {
342
8.65k
    // Release |retiredMessage| on the main thread in case it is an instance of
343
8.65k
    // a mainthread-only class like nsScriptErrorWithStack and we're off the
344
8.65k
    // main thread.
345
8.65k
    NS_ReleaseOnMainThreadSystemGroup(
346
8.65k
      "nsConsoleService::retiredMessage", retiredMessage.forget());
347
8.65k
  }
348
8.90k
349
8.90k
  if (r) {
350
0
    // avoid failing in XPCShell tests
351
0
    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
352
0
    if (mainThread) {
353
0
      SystemGroup::Dispatch(TaskCategory::Other, r.forget());
354
0
    }
355
0
  }
356
8.90k
357
8.90k
  return NS_OK;
358
8.90k
}
359
360
void
361
nsConsoleService::CollectCurrentListeners(
362
  nsCOMArray<nsIConsoleListener>& aListeners)
363
0
{
364
0
  MutexAutoLock lock(mLock);
365
0
  for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
366
0
    nsIConsoleListener* value = iter.UserData();
367
0
    aListeners.AppendObject(value);
368
0
  }
369
0
}
370
371
NS_IMETHODIMP
372
nsConsoleService::LogStringMessage(const char16_t* aMessage)
373
8.90k
{
374
8.90k
  if (!gLoggingEnabled) {
375
0
    return NS_OK;
376
0
  }
377
8.90k
378
8.90k
  RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage));
379
8.90k
  return this->LogMessage(msg);
380
8.90k
}
381
382
NS_IMETHODIMP
383
nsConsoleService::GetMessageArray(uint32_t* aCount,
384
                                  nsIConsoleMessage*** aMessages)
385
0
{
386
0
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
387
0
388
0
  MutexAutoLock lock(mLock);
389
0
390
0
  if (mMessages.isEmpty()) {
391
0
    /*
392
0
     * Make a 1-length output array so that nobody gets confused,
393
0
     * and return a count of 0.  This should result in a 0-length
394
0
     * array object when called from script.
395
0
     */
396
0
    nsIConsoleMessage** messageArray = (nsIConsoleMessage**)
397
0
      moz_xmalloc(sizeof(nsIConsoleMessage*));
398
0
    *messageArray = nullptr;
399
0
    *aMessages = messageArray;
400
0
    *aCount = 0;
401
0
402
0
    return NS_OK;
403
0
  }
404
0
405
0
  MOZ_ASSERT(mCurrentSize <= mMaximumSize);
406
0
  nsIConsoleMessage** messageArray =
407
0
    static_cast<nsIConsoleMessage**>(moz_xmalloc(sizeof(nsIConsoleMessage*)
408
0
                                                 * mCurrentSize));
409
0
410
0
  uint32_t i = 0;
411
0
  for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) {
412
0
    nsCOMPtr<nsIConsoleMessage> m = e->Get();
413
0
    m.forget(&messageArray[i]);
414
0
    i++;
415
0
  }
416
0
417
0
  MOZ_ASSERT(i == mCurrentSize);
418
0
419
0
  *aCount = i;
420
0
  *aMessages = messageArray;
421
0
422
0
  return NS_OK;
423
0
}
424
425
NS_IMETHODIMP
426
nsConsoleService::RegisterListener(nsIConsoleListener* aListener)
427
0
{
428
0
  if (!NS_IsMainThread()) {
429
0
    NS_ERROR("nsConsoleService::RegisterListener is main thread only.");
430
0
    return NS_ERROR_NOT_SAME_THREAD;
431
0
  }
432
0
433
0
  nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
434
0
435
0
  MutexAutoLock lock(mLock);
436
0
  if (mListeners.GetWeak(canonical)) {
437
0
    // Reregistering a listener isn't good
438
0
    return NS_ERROR_FAILURE;
439
0
  }
440
0
  mListeners.Put(canonical, aListener);
441
0
  return NS_OK;
442
0
}
443
444
NS_IMETHODIMP
445
nsConsoleService::UnregisterListener(nsIConsoleListener* aListener)
446
0
{
447
0
  if (!NS_IsMainThread()) {
448
0
    NS_ERROR("nsConsoleService::UnregisterListener is main thread only.");
449
0
    return NS_ERROR_NOT_SAME_THREAD;
450
0
  }
451
0
452
0
  nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
453
0
454
0
  MutexAutoLock lock(mLock);
455
0
456
0
  if (!mListeners.GetWeak(canonical)) {
457
0
    // Unregistering a listener that was never registered?
458
0
    return NS_ERROR_FAILURE;
459
0
  }
460
0
  mListeners.Remove(canonical);
461
0
  return NS_OK;
462
0
}
463
464
NS_IMETHODIMP
465
nsConsoleService::Reset()
466
0
{
467
0
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
468
0
469
0
  /*
470
0
   * Make sure nobody trips into the buffer while it's being reset
471
0
   */
472
0
  MutexAutoLock lock(mLock);
473
0
474
0
  ClearMessages();
475
0
  return NS_OK;
476
0
}
477
478
NS_IMETHODIMP
479
nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic,
480
                          const char16_t* aData)
481
0
{
482
0
  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
483
0
    // Dump all our messages, in case any are cycle collected.
484
0
    Reset();
485
0
    // We could remove ourselves from the observer service, but it is about to
486
0
    // drop all observers anyways, so why bother.
487
0
  } else if (!strcmp(aTopic, "inner-window-destroyed")) {
488
0
    nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject);
489
0
    MOZ_ASSERT(supportsInt);
490
0
491
0
    uint64_t windowId;
492
0
    MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId));
493
0
494
0
    ClearMessagesForWindowID(windowId);
495
0
  } else {
496
0
    MOZ_CRASH();
497
0
  }
498
0
  return NS_OK;
499
0
}