Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/mfbt/RecordReplay.h
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
/* Public API for Web Replay. */
8
9
#ifndef mozilla_RecordReplay_h
10
#define mozilla_RecordReplay_h
11
12
#include "mozilla/Attributes.h"
13
#include "mozilla/GuardObjects.h"
14
#include "mozilla/TemplateLib.h"
15
#include "mozilla/Types.h"
16
17
#include <functional>
18
#include <stdarg.h>
19
20
struct PLDHashTableOps;
21
struct JSContext;
22
class JSObject;
23
24
namespace mozilla {
25
namespace recordreplay {
26
27
// Record/Replay Overview.
28
//
29
// Firefox content processes can be specified to record or replay their
30
// behavior. Whether a process is recording or replaying is initialized at the
31
// start of the main() routine, and is afterward invariant for the process.
32
//
33
// Recording and replaying works by controlling non-determinism in the browser:
34
// non-deterministic behaviors are initially recorded, then later replayed
35
// exactly to force the browser to behave deterministically. Two types of
36
// non-deterministic behaviors are captured: intra-thread and inter-thread.
37
// Intra-thread non-deterministic behaviors are non-deterministic even in the
38
// absence of actions by other threads, and inter-thread non-deterministic
39
// behaviors are those affected by interleaving execution with other threads.
40
//
41
// Intra-thread non-determinism is recorded and replayed as a stream of events
42
// for each thread. Most events originate from calls to system library
43
// functions (for i/o and such); the record/replay system handles these
44
// internally by redirecting these library functions so that code can be
45
// injected and the event recorded/replayed. Events can also be manually
46
// performed using the RecordReplayValue and RecordReplayBytes APIs below.
47
//
48
// Inter-thread non-determinism is recorded and replayed by keeping track of
49
// the order in which threads acquire locks or perform atomic accesses. If the
50
// program is data race free, then reproducing the order of these operations
51
// will give an interleaving that is functionally (if not exactly) the same
52
// as during the recording. As for intra-thread non-determinism, system library
53
// redirections are used to capture most inter-thread non-determinism, but the
54
// {Begin,End}OrderedAtomicAccess APIs below can be used to add new ordering
55
// constraints.
56
//
57
// Some behaviors can differ between recording and replay. Mainly, pointer
58
// values can differ, and JS GCs can occur at different points (a more complete
59
// list is at the URL below). Some of the APIs below are used to accommodate
60
// these behaviors and keep the replaying process on track.
61
//
62
// A third process type, middleman processes, are normal content processes
63
// which facilitate communication with recording and replaying processes,
64
// managing the graphics data they generate, and running devtools code that
65
// interacts with them.
66
//
67
// This file contains the main public API for places where mozilla code needs
68
// to interact with the record/replay system. There are a few additional public
69
// APIs in toolkit/recordreplay/ipc, for the IPC performed by
70
// recording/replaying processes and middleman processes.
71
//
72
// A more complete description of Web Replay can be found at this URL:
73
// https://developer.mozilla.org/en-US/docs/WebReplay
74
75
///////////////////////////////////////////////////////////////////////////////
76
// Public API
77
///////////////////////////////////////////////////////////////////////////////
78
79
// Recording and replaying is only enabled on Mac nightlies.
80
#if defined(XP_MACOSX) && defined(NIGHTLY_BUILD)
81
82
extern MFBT_DATA bool gIsRecordingOrReplaying;
83
extern MFBT_DATA bool gIsRecording;
84
extern MFBT_DATA bool gIsReplaying;
85
extern MFBT_DATA bool gIsMiddleman;
86
87
// Get the kind of recording/replaying process this is, if any.
88
static inline bool IsRecordingOrReplaying() { return gIsRecordingOrReplaying; }
89
static inline bool IsRecording() { return gIsRecording; }
90
static inline bool IsReplaying() { return gIsReplaying; }
91
static inline bool IsMiddleman() { return gIsMiddleman; }
92
93
#else // XP_MACOSX && NIGHTLY_BUILD
94
95
// On unsupported platforms, getting the kind of process is a no-op.
96
0
static inline bool IsRecordingOrReplaying() { return false; }
97
0
static inline bool IsRecording() { return false; }
98
0
static inline bool IsReplaying() { return false; }
99
0
static inline bool IsMiddleman() { return false; }
100
101
#endif // XP_MACOSX && NIGHTLY_BUILD
102
103
// Mark a region which occurs atomically wrt the recording. No two threads can
104
// be in an atomic region at once, and the order in which atomic sections are
105
// executed by the various threads will be the same in the replay as in the
106
// recording. These calls have no effect when not recording/replaying.
107
static inline void BeginOrderedAtomicAccess();
108
static inline void EndOrderedAtomicAccess();
109
110
// RAII class for an atomic access.
111
struct MOZ_RAII AutoOrderedAtomicAccess
112
{
113
  AutoOrderedAtomicAccess() { BeginOrderedAtomicAccess(); }
114
  ~AutoOrderedAtomicAccess() { EndOrderedAtomicAccess(); }
115
};
116
117
// Mark a region where thread events are passed through the record/replay
118
// system. While recording, no information from system calls or other events
119
// will be recorded for the thread. While replaying, system calls and other
120
// events are performed normally.
121
static inline void BeginPassThroughThreadEvents();
122
static inline void EndPassThroughThreadEvents();
123
124
// Whether events in this thread are passed through.
125
static inline bool AreThreadEventsPassedThrough();
126
127
// RAII class for regions where thread events are passed through.
128
struct MOZ_RAII AutoPassThroughThreadEvents
129
{
130
  AutoPassThroughThreadEvents() { BeginPassThroughThreadEvents(); }
131
  ~AutoPassThroughThreadEvents() { EndPassThroughThreadEvents(); }
132
};
133
134
// As for AutoPassThroughThreadEvents, but may be used when events are already
135
// passed through.
136
struct MOZ_RAII AutoEnsurePassThroughThreadEvents
137
{
138
  AutoEnsurePassThroughThreadEvents()
139
    : mPassedThrough(AreThreadEventsPassedThrough())
140
  {
141
    if (!mPassedThrough)
142
      BeginPassThroughThreadEvents();
143
  }
144
145
  ~AutoEnsurePassThroughThreadEvents()
146
  {
147
    if (!mPassedThrough)
148
      EndPassThroughThreadEvents();
149
  }
150
151
private:
152
  bool mPassedThrough;
153
};
154
155
// Mark a region where thread events are not allowed to occur. The process will
156
// crash immediately if an event does happen.
157
static inline void BeginDisallowThreadEvents();
158
static inline void EndDisallowThreadEvents();
159
160
// Whether events in this thread are disallowed.
161
static inline bool AreThreadEventsDisallowed();
162
163
// RAII class for a region where thread events are disallowed.
164
struct MOZ_RAII AutoDisallowThreadEvents
165
{
166
  AutoDisallowThreadEvents() { BeginDisallowThreadEvents(); }
167
  ~AutoDisallowThreadEvents() { EndDisallowThreadEvents(); }
168
};
169
170
// Record or replay a value in the current thread's event stream.
171
static inline size_t RecordReplayValue(size_t aValue);
172
173
// Record or replay the contents of a range of memory in the current thread's
174
// event stream.
175
static inline void RecordReplayBytes(void* aData, size_t aSize);
176
177
// During recording or replay, mark the recording as unusable. There are some
178
// behaviors that can't be reliably recorded or replayed. For more information,
179
// see 'Unrecordable Executions' in the URL above.
180
static inline void InvalidateRecording(const char* aWhy);
181
182
// API for ensuring deterministic recording and replaying of PLDHashTables.
183
// This allows PLDHashTables to behave deterministically by generating a custom
184
// set of operations for each table and requiring no other instrumentation.
185
// (PLHashTables have a similar mechanism, though it is not exposed here.)
186
static inline const PLDHashTableOps* GeneratePLDHashTableCallbacks(const PLDHashTableOps* aOps);
187
static inline const PLDHashTableOps* UnwrapPLDHashTableCallbacks(const PLDHashTableOps* aOps);
188
static inline void DestroyPLDHashTableCallbacks(const PLDHashTableOps* aOps);
189
static inline void MovePLDHashTableContents(const PLDHashTableOps* aFirstOps,
190
                                            const PLDHashTableOps* aSecondOps);
191
192
// Associate an arbitrary pointer with a JS object root while replaying. This
193
// is useful for replaying the behavior of weak pointers.
194
MFBT_API void SetWeakPointerJSRoot(const void* aPtr, JSObject* aJSObj);
195
196
// API for ensuring that a function executes at a consistent point when
197
// recording or replaying. This is primarily needed for finalizers and other
198
// activity during a GC that can perform recorded events (because GCs can
199
// occur at different times and behave differently between recording and
200
// replay, thread events are disallowed during a GC). Triggers can be
201
// registered at a point where thread events are allowed, then activated at
202
// a point where thread events are not allowed. When recording, the trigger's
203
// callback will execute at the next point when ExecuteTriggers is called on
204
// the thread which originally registered the trigger (typically at the top of
205
// the thread's event loop), and when replaying the callback will execute at
206
// the same point, even if it was never activated.
207
//
208
// Below is an example of how this API can be used.
209
//
210
// // This structure's lifetime is managed by the GC.
211
// struct GarbageCollectedHolder {
212
//   GarbageCollectedHolder() {
213
//     RegisterTrigger(this, [=]() { this->DestroyContents(); });
214
//   }
215
//   ~GarbageCollectedHolder() {
216
//     UnregisterTrigger(this);
217
//   }
218
//
219
//   void Finalize() {
220
//     // During finalization, thread events are disallowed.
221
//     if (IsRecordingOrReplaying()) {
222
//       ActivateTrigger(this);
223
//     } else {
224
//       DestroyContents();
225
//     }
226
//   }
227
//
228
//   // This is free to release resources held by the system, communicate with
229
//   // other threads or processes, and so forth. When replaying, this may
230
//   // be called before the GC has actually collected this object, but since
231
//   // the GC will have already collected this object at this point in the
232
//   // recording, this object will never be accessed again.
233
//   void DestroyContents();
234
// };
235
MFBT_API void RegisterTrigger(void* aObj, const std::function<void()>& aCallback);
236
MFBT_API void UnregisterTrigger(void* aObj);
237
MFBT_API void ActivateTrigger(void* aObj);
238
MFBT_API void ExecuteTriggers();
239
240
// Some devtools operations which execute in a replaying process can cause code
241
// to run which did not run while recording. For example, the JS debugger can
242
// run arbitrary JS while paused at a breakpoint, by doing an eval(). In such
243
// cases we say that execution has diverged from the recording, and if recorded
244
// events are encountered the associated devtools operation fails. This API can
245
// be used to test for such cases and avoid causing the operation to fail.
246
static inline bool HasDivergedFromRecording();
247
248
// API for handling unrecorded waits. During replay, periodically all threads
249
// must enter a specific idle state so that checkpoints may be saved or
250
// restored for rewinding. For threads which block on recorded resources
251
// --- they wait on a recorded lock (one which was created when events were not
252
// passed through) or an associated cvar --- this is handled automatically.
253
//
254
// Threads which block indefinitely on unrecorded resources must call
255
// NotifyUnrecordedWait first.
256
//
257
// The callback passed to NotifyUnrecordedWait will be invoked at most once
258
// by the main thread whenever the main thread is waiting for other threads to
259
// become idle, and at most once after the call to NotifyUnrecordedWait if the
260
// main thread is already waiting for other threads to become idle.
261
//
262
// The callback should poke the thread so that it is no longer blocked on the
263
// resource. The thread must call MaybeWaitForCheckpointSave before blocking
264
// again.
265
MFBT_API void NotifyUnrecordedWait(const std::function<void()>& aCallback);
266
MFBT_API void MaybeWaitForCheckpointSave();
267
268
// API for debugging inconsistent behavior between recording and replay.
269
// By calling Assert or AssertBytes a thread event will be inserted and any
270
// inconsistent execution order of events will be detected (as for normal
271
// thread events) and reported to the console.
272
//
273
// RegisterThing/UnregisterThing associate arbitrary pointers with indexes that
274
// will be consistent between recording/replaying and can be used in assertion
275
// strings.
276
static inline void RecordReplayAssert(const char* aFormat, ...);
277
static inline void RecordReplayAssertBytes(const void* aData, size_t aSize);
278
static inline void RegisterThing(void* aThing);
279
static inline void UnregisterThing(void* aThing);
280
static inline size_t ThingIndex(void* aThing);
281
282
// Give a directive to the record/replay system. For possible values for
283
// aDirective, see ProcessRecordReplay.h. This is used for testing purposes.
284
static inline void RecordReplayDirective(long aDirective);
285
286
// Helper for record/replay asserts, try to determine a name for a C++ object
287
// with virtual methods based on its vtable.
288
static inline const char* VirtualThingName(void* aThing);
289
290
// Enum which describes whether to preserve behavior between recording and
291
// replay sessions.
292
enum class Behavior {
293
  DontPreserve,
294
  Preserve
295
};
296
297
// Determine whether this is a recording/replaying or middleman process, and
298
// initialize record/replay state if so.
299
MFBT_API void Initialize(int aArgc, char* aArgv[]);
300
301
// Kinds of recording/replaying processes that can be spawned.
302
enum class ProcessKind {
303
  Recording,
304
  Replaying,
305
  MiddlemanRecording,
306
  MiddlemanReplaying
307
};
308
309
// Command line option for specifying the record/replay kind of a process.
310
static const char gProcessKindOption[] = "-recordReplayKind";
311
312
// Command line option for specifying the recording file to use.
313
static const char gRecordingFileOption[] = "-recordReplayFile";
314
315
///////////////////////////////////////////////////////////////////////////////
316
// JS interface
317
///////////////////////////////////////////////////////////////////////////////
318
319
// Get the counter used to keep track of how much progress JS execution has
320
// made while running on the main thread. Progress must advance whenever a JS
321
// function is entered or loop entry point is reached, so that no script
322
// location may be hit twice while the progress counter is the same. See
323
// JSControl.h for more.
324
typedef uint64_t ProgressCounter;
325
MFBT_API ProgressCounter* ExecutionProgressCounter();
326
327
static inline void
328
AdvanceExecutionProgressCounter()
329
0
{
330
0
  ++*ExecutionProgressCounter();
331
0
}
332
333
// Get an identifier for the current execution point which can be used to warp
334
// here later.
335
MFBT_API ProgressCounter NewTimeWarpTarget();
336
337
// Return whether a script is internal to the record/replay infrastructure,
338
// may run non-deterministically between recording and replaying, and whose
339
// execution must not update the progress counter.
340
MFBT_API bool IsInternalScript(const char* aURL);
341
342
// Define a RecordReplayControl object on the specified global object, with
343
// methods specialized to the current recording/replaying or middleman process
344
// kind.
345
MFBT_API bool DefineRecordReplayControlObject(JSContext* aCx, JSObject* aObj);
346
347
// Notify the infrastructure that some URL which contains JavaScript is
348
// being parsed. This is used to provide the complete contents of the URL to
349
// devtools code when it is inspecting the state of this process; that devtools
350
// code can't simply fetch the URL itself since it may have been changed since
351
// the recording was made or may no longer exist. The token for a parse may not
352
// be used in other parses until after EndContentParse() is called.
353
MFBT_API void BeginContentParse(const void* aToken,
354
                                const char* aURL, const char* aContentType);
355
356
// Add some parse data to an existing content parse.
357
MFBT_API void AddContentParseData(const void* aToken,
358
                                  const char16_t* aBuffer, size_t aLength);
359
360
// Mark a content parse as having completed.
361
MFBT_API void EndContentParse(const void* aToken);
362
363
// Perform an entire content parse, when the entire URL is available at once.
364
static inline void
365
NoteContentParse(const void* aToken,
366
                 const char* aURL, const char* aContentType,
367
                 const char16_t* aBuffer, size_t aLength)
368
0
{
369
0
  BeginContentParse(aToken, aURL, aContentType);
370
0
  AddContentParseData(aToken, aBuffer, aLength);
371
0
  EndContentParse(aToken);
372
0
}
373
374
///////////////////////////////////////////////////////////////////////////////
375
// API inline function implementation
376
///////////////////////////////////////////////////////////////////////////////
377
378
// Define inline wrappers on builds where recording/replaying is enabled.
379
#if defined(XP_MACOSX) && defined(NIGHTLY_BUILD)
380
381
#define MOZ_MakeRecordReplayWrapperVoid(aName, aFormals, aActuals)      \
382
  MFBT_API void Internal ##aName aFormals;                              \
383
  static inline void aName aFormals                                     \
384
  {                                                                     \
385
    if (IsRecordingOrReplaying()) {                                     \
386
      Internal ##aName aActuals;                                        \
387
    }                                                                   \
388
  }
389
390
#define MOZ_MakeRecordReplayWrapper(aName, aReturnType, aDefaultValue, aFormals, aActuals) \
391
  MFBT_API aReturnType Internal ##aName aFormals;                       \
392
  static inline aReturnType aName aFormals                              \
393
  {                                                                     \
394
    if (IsRecordingOrReplaying()) {                                     \
395
      return Internal ##aName aActuals;                                 \
396
    }                                                                   \
397
    return aDefaultValue;                                               \
398
  }
399
400
// Define inline wrappers on other builds. Avoiding references to the out of
401
// line method avoids link errors when e.g. using Atomic<> but not linking
402
// against MFBT.
403
#else
404
405
#define MOZ_MakeRecordReplayWrapperVoid(aName, aFormals, aActuals)      \
406
0
  static inline void aName aFormals {}
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::BeginOrderedAtomicAccess()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::EndOrderedAtomicAccess()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::BeginPassThroughThreadEvents()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::EndPassThroughThreadEvents()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::BeginDisallowThreadEvents()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::EndDisallowThreadEvents()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::RecordReplayBytes(void*, unsigned long)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::DestroyPLDHashTableCallbacks(PLDHashTableOps const*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::MovePLDHashTableContents(PLDHashTableOps const*, PLDHashTableOps const*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::InvalidateRecording(char const*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::RegisterWeakPointer(void const*, std::__1::function<void (bool)> const&)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::UnregisterWeakPointer(void const*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::WeakPointerAccess(void const*, bool)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::RecordReplayAssertBytes(void const*, unsigned long)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::RegisterThing(void*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::UnregisterThing(void*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::RecordReplayDirective(long)
407
408
#define MOZ_MakeRecordReplayWrapper(aName, aReturnType, aDefaultValue, aFormals, aActuals) \
409
0
  static inline aReturnType aName aFormals { return aDefaultValue; }
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::AreThreadEventsPassedThrough()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::AreThreadEventsDisallowed()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::RecordReplayValue(unsigned long)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::HasDivergedFromRecording()
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::GeneratePLDHashTableCallbacks(PLDHashTableOps const*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::UnwrapPLDHashTableCallbacks(PLDHashTableOps const*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::ThingIndex(void*)
Unexecuted instantiation: Unified_cpp_mfbt0.cpp:mozilla::recordreplay::VirtualThingName(void*)
410
411
#endif
412
413
MOZ_MakeRecordReplayWrapperVoid(BeginOrderedAtomicAccess, (), ())
414
MOZ_MakeRecordReplayWrapperVoid(EndOrderedAtomicAccess, (), ())
415
MOZ_MakeRecordReplayWrapperVoid(BeginPassThroughThreadEvents, (), ())
416
MOZ_MakeRecordReplayWrapperVoid(EndPassThroughThreadEvents, (), ())
417
MOZ_MakeRecordReplayWrapper(AreThreadEventsPassedThrough, bool, false, (), ())
418
MOZ_MakeRecordReplayWrapperVoid(BeginDisallowThreadEvents, (), ())
419
MOZ_MakeRecordReplayWrapperVoid(EndDisallowThreadEvents, (), ())
420
MOZ_MakeRecordReplayWrapper(AreThreadEventsDisallowed, bool, false, (), ())
421
MOZ_MakeRecordReplayWrapper(RecordReplayValue, size_t, aValue, (size_t aValue), (aValue))
422
MOZ_MakeRecordReplayWrapperVoid(RecordReplayBytes, (void* aData, size_t aSize), (aData, aSize))
423
MOZ_MakeRecordReplayWrapper(HasDivergedFromRecording, bool, false, (), ())
424
MOZ_MakeRecordReplayWrapper(GeneratePLDHashTableCallbacks,
425
                            const PLDHashTableOps*, aOps, (const PLDHashTableOps* aOps), (aOps))
426
MOZ_MakeRecordReplayWrapper(UnwrapPLDHashTableCallbacks,
427
                            const PLDHashTableOps*, aOps, (const PLDHashTableOps* aOps), (aOps))
428
MOZ_MakeRecordReplayWrapperVoid(DestroyPLDHashTableCallbacks,
429
                                (const PLDHashTableOps* aOps), (aOps))
430
MOZ_MakeRecordReplayWrapperVoid(MovePLDHashTableContents,
431
                                (const PLDHashTableOps* aFirstOps,
432
                                 const PLDHashTableOps* aSecondOps),
433
                                (aFirstOps, aSecondOps))
434
MOZ_MakeRecordReplayWrapperVoid(InvalidateRecording, (const char* aWhy), (aWhy))
435
MOZ_MakeRecordReplayWrapperVoid(RegisterWeakPointer,
436
                                (const void* aPtr, const std::function<void(bool)>& aCallback),
437
                                (aPtr, aCallback))
438
MOZ_MakeRecordReplayWrapperVoid(UnregisterWeakPointer, (const void* aPtr), (aPtr))
439
MOZ_MakeRecordReplayWrapperVoid(WeakPointerAccess,
440
                                (const void* aPtr, bool aSuccess), (aPtr, aSuccess))
441
MOZ_MakeRecordReplayWrapperVoid(RecordReplayAssertBytes,
442
                                (const void* aData, size_t aSize), (aData, aSize))
443
MOZ_MakeRecordReplayWrapperVoid(RegisterThing, (void* aThing), (aThing))
444
MOZ_MakeRecordReplayWrapperVoid(UnregisterThing, (void* aThing), (aThing))
445
MOZ_MakeRecordReplayWrapper(ThingIndex, size_t, 0, (void* aThing), (aThing))
446
MOZ_MakeRecordReplayWrapper(VirtualThingName, const char*, nullptr, (void* aThing), (aThing))
447
MOZ_MakeRecordReplayWrapperVoid(RecordReplayDirective, (long aDirective), (aDirective))
448
449
#undef MOZ_MakeRecordReplayWrapperVoid
450
#undef MOZ_MakeRecordReplayWrapper
451
452
MFBT_API void InternalRecordReplayAssert(const char* aFormat, va_list aArgs);
453
454
static inline void
455
RecordReplayAssert(const char* aFormat, ...)
456
0
{
457
0
  if (IsRecordingOrReplaying()) {
458
0
    va_list ap;
459
0
    va_start(ap, aFormat);
460
0
    InternalRecordReplayAssert(aFormat, ap);
461
0
    va_end(ap);
462
0
  }
463
0
}
464
465
} // recordreplay
466
} // mozilla
467
468
#endif /* mozilla_RecordReplay_h */