Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "FinalizationWitnessService.h"
6
7
#include "nsString.h"
8
#include "jsapi.h"
9
#include "js/CallNonGenericMethod.h"
10
#include "mozJSComponentLoader.h"
11
#include "nsZipArchive.h"
12
13
#include "mozilla/Scoped.h"
14
#include "mozilla/Services.h"
15
#include "nsIObserverService.h"
16
#include "nsThreadUtils.h"
17
18
19
// Implementation of nsIFinalizationWitnessService
20
21
static bool gShuttingDown = false;
22
23
namespace mozilla {
24
25
namespace {
26
27
/**
28
 * An event meant to be dispatched to the main thread upon finalization
29
 * of a FinalizationWitness, unless method |forget()| has been called.
30
 *
31
 * Held as private data by each instance of FinalizationWitness.
32
 * Important note: we maintain the invariant that these private data
33
 * slots are already addrefed.
34
 */
35
class FinalizationEvent final: public Runnable
36
{
37
public:
38
  FinalizationEvent(const char* aTopic,
39
                  const char16_t* aValue)
40
    : Runnable("FinalizationEvent")
41
    , mTopic(aTopic)
42
    , mValue(aValue)
43
0
  { }
44
45
0
  NS_IMETHOD Run() override {
46
0
    nsCOMPtr<nsIObserverService> observerService =
47
0
      mozilla::services::GetObserverService();
48
0
    if (!observerService) {
49
0
      // This is either too early or, more likely, too late for notifications.
50
0
      // Bail out.
51
0
      return NS_ERROR_NOT_AVAILABLE;
52
0
    }
53
0
    (void)observerService->
54
0
      NotifyObservers(nullptr, mTopic.get(), mValue.get());
55
0
    return NS_OK;
56
0
  }
57
private:
58
  /**
59
   * The topic on which to broadcast the notification of finalization.
60
   *
61
   * Deallocated on the main thread.
62
   */
63
  const nsCString mTopic;
64
65
  /**
66
   * The result of converting the exception to a string.
67
   *
68
   * Deallocated on the main thread.
69
   */
70
  const nsString mValue;
71
};
72
73
enum {
74
  WITNESS_SLOT_EVENT,
75
  WITNESS_INSTANCES_SLOTS
76
};
77
78
/**
79
 * Extract the FinalizationEvent from an instance of FinalizationWitness
80
 * and clear the slot containing the FinalizationEvent.
81
 */
82
already_AddRefed<FinalizationEvent>
83
ExtractFinalizationEvent(JSObject *objSelf)
84
0
{
85
0
  JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT);
86
0
  if (slotEvent.isUndefined()) {
87
0
    // Forget() has been called
88
0
    return nullptr;
89
0
  }
90
0
91
0
  JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue());
92
0
93
0
  return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate()));
94
0
}
95
96
/**
97
 * Finalizer for instances of FinalizationWitness.
98
 *
99
 * Unless method Forget() has been called, the finalizer displays an error
100
 * message.
101
 */
102
void Finalize(JSFreeOp *fop, JSObject *objSelf)
103
0
{
104
0
  RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
105
0
  if (event == nullptr || gShuttingDown) {
106
0
    // NB: event will be null if Forget() has been called
107
0
    return;
108
0
  }
109
0
110
0
  // Notify observers. Since we are executed during garbage-collection,
111
0
  // we need to dispatch the notification to the main thread.
112
0
  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
113
0
  if (mainThread) {
114
0
    mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
115
0
  }
116
0
  // We may fail at dispatching to the main thread if we arrive too late
117
0
  // during shutdown. In that case, there is not much we can do.
118
0
}
119
120
static const JSClassOps sWitnessClassOps = {
121
  nullptr /* addProperty */,
122
  nullptr /* delProperty */,
123
  nullptr /* enumerate */,
124
  nullptr /* newEnumerate */,
125
  nullptr /* resolve */,
126
  nullptr /* mayResolve */,
127
  Finalize /* finalize */
128
};
129
130
static const JSClass sWitnessClass = {
131
  "FinalizationWitness",
132
  JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS) |
133
  JSCLASS_FOREGROUND_FINALIZE,
134
  &sWitnessClassOps
135
};
136
137
bool IsWitness(JS::Handle<JS::Value> v)
138
0
{
139
0
  return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass;
140
0
}
141
142
143
/**
144
 * JS method |forget()|
145
 *
146
 * === JS documentation
147
 *
148
 *  Neutralize the witness. Once this method is called, the witness will
149
 *  never report any error.
150
 */
151
bool ForgetImpl(JSContext* cx, const JS::CallArgs& args)
152
0
{
153
0
  if (args.length() != 0) {
154
0
    JS_ReportErrorASCII(cx, "forget() takes no arguments");
155
0
    return false;
156
0
  }
157
0
  JS::Rooted<JS::Value> valSelf(cx, args.thisv());
158
0
  JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject());
159
0
160
0
  RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
161
0
  if (event == nullptr) {
162
0
    JS_ReportErrorASCII(cx, "forget() called twice");
163
0
    return false;
164
0
  }
165
0
166
0
  args.rval().setUndefined();
167
0
  return true;
168
0
}
169
170
bool Forget(JSContext *cx, unsigned argc, JS::Value *vp)
171
0
{
172
0
  JS::CallArgs args = CallArgsFromVp(argc, vp);
173
0
  return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args);
174
0
}
175
176
static const JSFunctionSpec sWitnessClassFunctions[] = {
177
  JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT),
178
  JS_FS_END
179
};
180
181
} // namespace
182
183
NS_IMPL_ISUPPORTS(FinalizationWitnessService, nsIFinalizationWitnessService, nsIObserver)
184
185
/**
186
 * Create a new Finalization Witness.
187
 *
188
 * A finalization witness is an object whose sole role is to notify
189
 * observers when it is gc-ed. Once the witness is created, call its
190
 * method |forget()| to prevent the observers from being notified.
191
 *
192
 * @param aTopic The notification topic.
193
 * @param aValue The notification value. Converted to a string.
194
 *
195
 * @constructor
196
 */
197
NS_IMETHODIMP
198
FinalizationWitnessService::Make(const char* aTopic,
199
                                 const char16_t* aValue,
200
                                 JSContext* aCx,
201
                                 JS::MutableHandle<JS::Value> aRetval)
202
0
{
203
0
  // Finalize witnesses are not created when recording or replaying, as
204
0
  // finalizations occur non-deterministically in the recording.
205
0
  if (recordreplay::IsRecordingOrReplaying()) {
206
0
    return NS_ERROR_NOT_AVAILABLE;
207
0
  }
208
0
209
0
  JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass));
210
0
  if (!objResult) {
211
0
    return NS_ERROR_OUT_OF_MEMORY;
212
0
  }
213
0
  if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) {
214
0
    return NS_ERROR_FAILURE;
215
0
  }
216
0
217
0
  RefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue);
218
0
219
0
  // Transfer ownership of the addrefed |event| to |objResult|.
220
0
  JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT,
221
0
                     JS::PrivateValue(event.forget().take()));
222
0
223
0
  aRetval.setObject(*objResult);
224
0
  return NS_OK;
225
0
}
226
227
NS_IMETHODIMP
228
FinalizationWitnessService::Observe(nsISupports* aSubject,
229
                                    const char* aTopic,
230
                                    const char16_t* aValue)
231
0
{
232
0
  MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
233
0
  gShuttingDown = true;
234
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
235
0
  if (obs) {
236
0
    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
237
0
  }
238
0
239
0
  return NS_OK;
240
0
}
241
242
nsresult
243
FinalizationWitnessService::Init()
244
0
{
245
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
246
0
  if (!obs) {
247
0
    return NS_ERROR_FAILURE;
248
0
  }
249
0
250
0
  return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
251
0
}
252
253
} // namespace mozilla