1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
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 "js/UbiNode.h"
8 
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/Range.h"
12 #include "mozilla/Scoped.h"
13 
14 #include <algorithm>
15 
16 #include "builtin/String.h"
17 
18 #include "jit/IonCode.h"
19 #include "js/Debug.h"
20 #include "js/TracingAPI.h"
21 #include "js/TypeDecls.h"
22 #include "js/Utility.h"
23 #include "js/Vector.h"
24 #include "util/Text.h"
25 #include "vm/Debugger.h"
26 #include "vm/EnvironmentObject.h"
27 #include "vm/GlobalObject.h"
28 #include "vm/JSContext.h"
29 #include "vm/JSObject.h"
30 #include "vm/JSScript.h"
31 #include "vm/Scope.h"
32 #include "vm/Shape.h"
33 #include "vm/StringType.h"
34 #include "vm/SymbolType.h"
35 
36 #include "vm/Debugger-inl.h"
37 #include "vm/JSObject-inl.h"
38 
39 using namespace js;
40 
41 using JS::DispatchTyped;
42 using JS::HandleValue;
43 using JS::Value;
44 using JS::ZoneSet;
45 using JS::ubi::AtomOrTwoByteChars;
46 using JS::ubi::CoarseType;
47 using JS::ubi::Concrete;
48 using JS::ubi::Edge;
49 using JS::ubi::EdgeRange;
50 using JS::ubi::EdgeVector;
51 using JS::ubi::Node;
52 using JS::ubi::StackFrame;
53 using JS::ubi::TracerConcrete;
54 using JS::ubi::TracerConcreteWithCompartment;
55 using mozilla::RangedPtr;
56 
57 struct CopyToBufferMatcher {
58   RangedPtr<char16_t> destination;
59   size_t maxLength;
60 
CopyToBufferMatcherCopyToBufferMatcher61   CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
62       : destination(destination), maxLength(maxLength) {}
63 
64   template <typename CharT>
copyToBufferHelperCopyToBufferMatcher65   static size_t copyToBufferHelper(const CharT* src, RangedPtr<char16_t> dest,
66                                    size_t length) {
67     size_t i = 0;
68     for (; i < length; i++) dest[i] = src[i];
69     return i;
70   }
71 
matchCopyToBufferMatcher72   size_t match(JSAtom* atom) {
73     if (!atom) return 0;
74 
75     size_t length = std::min(atom->length(), maxLength);
76     JS::AutoCheckCannotGC noGC;
77     return atom->hasTwoByteChars()
78                ? copyToBufferHelper(atom->twoByteChars(noGC), destination,
79                                     length)
80                : copyToBufferHelper(atom->latin1Chars(noGC), destination,
81                                     length);
82   }
83 
matchCopyToBufferMatcher84   size_t match(const char16_t* chars) {
85     if (!chars) return 0;
86 
87     size_t length = std::min(js_strlen(chars), maxLength);
88     return copyToBufferHelper(chars, destination, length);
89   }
90 };
91 
copyToBuffer(RangedPtr<char16_t> destination,size_t length)92 size_t JS::ubi::AtomOrTwoByteChars::copyToBuffer(
93     RangedPtr<char16_t> destination, size_t length) {
94   CopyToBufferMatcher m(destination, length);
95   return match(m);
96 }
97 
98 struct LengthMatcher {
matchLengthMatcher99   size_t match(JSAtom* atom) { return atom ? atom->length() : 0; }
100 
matchLengthMatcher101   size_t match(const char16_t* chars) { return chars ? js_strlen(chars) : 0; }
102 };
103 
length()104 size_t JS::ubi::AtomOrTwoByteChars::length() {
105   LengthMatcher m;
106   return match(m);
107 }
108 
source(RangedPtr<char16_t> destination,size_t length) const109 size_t StackFrame::source(RangedPtr<char16_t> destination,
110                           size_t length) const {
111   auto s = source();
112   return s.copyToBuffer(destination, length);
113 }
114 
functionDisplayName(RangedPtr<char16_t> destination,size_t length) const115 size_t StackFrame::functionDisplayName(RangedPtr<char16_t> destination,
116                                        size_t length) const {
117   auto name = functionDisplayName();
118   return name.copyToBuffer(destination, length);
119 }
120 
sourceLength()121 size_t StackFrame::sourceLength() { return source().length(); }
122 
functionDisplayNameLength()123 size_t StackFrame::functionDisplayNameLength() {
124   return functionDisplayName().length();
125 }
126 
127 // All operations on null ubi::Nodes crash.
coarseType() const128 CoarseType Concrete<void>::coarseType() const { MOZ_CRASH("null ubi::Node"); }
typeName() const129 const char16_t* Concrete<void>::typeName() const {
130   MOZ_CRASH("null ubi::Node");
131 }
zone() const132 JS::Zone* Concrete<void>::zone() const { MOZ_CRASH("null ubi::Node"); }
compartment() const133 JSCompartment* Concrete<void>::compartment() const {
134   MOZ_CRASH("null ubi::Node");
135 }
136 
edges(JSContext *,bool) const137 UniquePtr<EdgeRange> Concrete<void>::edges(JSContext*, bool) const {
138   MOZ_CRASH("null ubi::Node");
139 }
140 
size(mozilla::MallocSizeOf mallocSizeof) const141 Node::Size Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const {
142   MOZ_CRASH("null ubi::Node");
143 }
144 
145 struct Node::ConstructFunctor : public js::BoolDefaultAdaptor<Value, false> {
146   template <typename T>
operator ()Node::ConstructFunctor147   bool operator()(T* t, Node* node) {
148     node->construct(t);
149     return true;
150   }
151 };
152 
Node(const JS::GCCellPtr & thing)153 Node::Node(const JS::GCCellPtr& thing) {
154   DispatchTyped(ConstructFunctor(), thing, this);
155 }
156 
Node(HandleValue value)157 Node::Node(HandleValue value) {
158   if (!DispatchTyped(ConstructFunctor(), value, this)) construct<void>(nullptr);
159 }
160 
exposeToJS() const161 Value Node::exposeToJS() const {
162   Value v;
163 
164   if (is<JSObject>()) {
165     JSObject& obj = *as<JSObject>();
166     if (obj.is<js::EnvironmentObject>()) {
167       v.setUndefined();
168     } else if (obj.is<JSFunction>() && js::IsInternalFunctionObject(obj)) {
169       v.setUndefined();
170     } else {
171       v.setObject(obj);
172     }
173   } else if (is<JSString>()) {
174     v.setString(as<JSString>());
175   } else if (is<JS::Symbol>()) {
176     v.setSymbol(as<JS::Symbol>());
177   } else {
178     v.setUndefined();
179   }
180 
181   ExposeValueToActiveJS(v);
182 
183   return v;
184 }
185 
186 // A JS::CallbackTracer subclass that adds a Edge to a Vector for each
187 // edge on which it is invoked.
188 class EdgeVectorTracer : public JS::CallbackTracer {
189   // The vector to which we add Edges.
190   EdgeVector* vec;
191 
192   // True if we should populate the edge's names.
193   bool wantNames;
194 
onChild(const JS::GCCellPtr & thing)195   void onChild(const JS::GCCellPtr& thing) override {
196     if (!okay) return;
197 
198     // Don't trace permanent atoms and well-known symbols that are owned by
199     // a parent JSRuntime.
200     if (thing.is<JSString>() && thing.as<JSString>().isPermanentAtom()) return;
201     if (thing.is<JS::Symbol>() && thing.as<JS::Symbol>().isWellKnownSymbol())
202       return;
203 
204     char16_t* name16 = nullptr;
205     if (wantNames) {
206       // Ask the tracer to compute an edge name for us.
207       char buffer[1024];
208       getTracingEdgeName(buffer, sizeof(buffer));
209       const char* name = buffer;
210 
211       // Convert the name to char16_t characters.
212       name16 = js_pod_malloc<char16_t>(strlen(name) + 1);
213       if (!name16) {
214         okay = false;
215         return;
216       }
217 
218       size_t i;
219       for (i = 0; name[i]; i++) name16[i] = name[i];
220       name16[i] = '\0';
221     }
222 
223     // The simplest code is correct! The temporary Edge takes
224     // ownership of name; if the append succeeds, the vector element
225     // then takes ownership; if the append fails, then the temporary
226     // retains it, and its destructor will free it.
227     if (!vec->append(mozilla::Move(Edge(name16, Node(thing))))) {
228       okay = false;
229       return;
230     }
231   }
232 
233  public:
234   // True if no errors (OOM, say) have yet occurred.
235   bool okay;
236 
EdgeVectorTracer(JSRuntime * rt,EdgeVector * vec,bool wantNames)237   EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames)
238       : JS::CallbackTracer(rt), vec(vec), wantNames(wantNames), okay(true) {}
239 };
240 
241 // An EdgeRange concrete class that simply holds a vector of Edges,
242 // populated by the init method.
243 class SimpleEdgeRange : public EdgeRange {
244   EdgeVector edges;
245   size_t i;
246 
settle()247   void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; }
248 
249  public:
SimpleEdgeRange()250   explicit SimpleEdgeRange() : edges(), i(0) {}
251 
init(JSRuntime * rt,void * thing,JS::TraceKind kind,bool wantNames=true)252   bool init(JSRuntime* rt, void* thing, JS::TraceKind kind,
253             bool wantNames = true) {
254     EdgeVectorTracer tracer(rt, &edges, wantNames);
255     js::TraceChildren(&tracer, thing, kind);
256     settle();
257     return tracer.okay;
258   }
259 
popFront()260   void popFront() override {
261     i++;
262     settle();
263   }
264 };
265 
266 template <typename Referent>
zone() const267 JS::Zone* TracerConcrete<Referent>::zone() const {
268   return get().zoneFromAnyThread();
269 }
270 
271 template JS::Zone* TracerConcrete<JSScript>::zone() const;
272 template JS::Zone* TracerConcrete<js::LazyScript>::zone() const;
273 template JS::Zone* TracerConcrete<js::Shape>::zone() const;
274 template JS::Zone* TracerConcrete<js::BaseShape>::zone() const;
275 template JS::Zone* TracerConcrete<js::ObjectGroup>::zone() const;
276 template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const;
277 template JS::Zone* TracerConcrete<js::Scope>::zone() const;
278 template JS::Zone* TracerConcrete<JS::Symbol>::zone() const;
279 template JS::Zone* TracerConcrete<JSString>::zone() const;
280 
281 template <typename Referent>
edges(JSContext * cx,bool wantNames) const282 UniquePtr<EdgeRange> TracerConcrete<Referent>::edges(JSContext* cx,
283                                                      bool wantNames) const {
284   UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(
285       js_new<SimpleEdgeRange>());
286   if (!range) return nullptr;
287 
288   if (!range->init(cx->runtime(), ptr, JS::MapTypeToTraceKind<Referent>::kind,
289                    wantNames))
290     return nullptr;
291 
292   return UniquePtr<EdgeRange>(range.release());
293 }
294 
295 template UniquePtr<EdgeRange> TracerConcrete<JSScript>::edges(
296     JSContext* cx, bool wantNames) const;
297 template UniquePtr<EdgeRange> TracerConcrete<js::LazyScript>::edges(
298     JSContext* cx, bool wantNames) const;
299 template UniquePtr<EdgeRange> TracerConcrete<js::Shape>::edges(
300     JSContext* cx, bool wantNames) const;
301 template UniquePtr<EdgeRange> TracerConcrete<js::BaseShape>::edges(
302     JSContext* cx, bool wantNames) const;
303 template UniquePtr<EdgeRange> TracerConcrete<js::ObjectGroup>::edges(
304     JSContext* cx, bool wantNames) const;
305 template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::edges(
306     JSContext* cx, bool wantNames) const;
307 template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges(
308     JSContext* cx, bool wantNames) const;
309 template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(
310     JSContext* cx, bool wantNames) const;
311 template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(
312     JSContext* cx, bool wantNames) const;
313 
314 template <typename Referent>
compartment() const315 JSCompartment* TracerConcreteWithCompartment<Referent>::compartment() const {
316   return TracerBase::get().compartment();
317 }
318 
319 template JSCompartment* TracerConcreteWithCompartment<JSScript>::compartment()
320     const;
321 
hasAllocationStack() const322 bool Concrete<JSObject>::hasAllocationStack() const {
323   return !!js::Debugger::getObjectAllocationSite(get());
324 }
325 
allocationStack() const326 StackFrame Concrete<JSObject>::allocationStack() const {
327   MOZ_ASSERT(hasAllocationStack());
328   return StackFrame(js::Debugger::getObjectAllocationSite(get()));
329 }
330 
jsObjectClassName() const331 const char* Concrete<JSObject>::jsObjectClassName() const {
332   return Concrete::get().getClass()->name;
333 }
334 
jsObjectConstructorName(JSContext * cx,UniqueTwoByteChars & outName) const335 bool Concrete<JSObject>::jsObjectConstructorName(
336     JSContext* cx, UniqueTwoByteChars& outName) const {
337   JSAtom* name = Concrete::get().maybeConstructorDisplayAtom();
338   if (!name) {
339     outName.reset(nullptr);
340     return true;
341   }
342 
343   auto len = JS_GetStringLength(name);
344   auto size = len + 1;
345 
346   outName.reset(cx->pod_malloc<char16_t>(size * sizeof(char16_t)));
347   if (!outName) return false;
348 
349   mozilla::Range<char16_t> chars(outName.get(), size);
350   if (!JS_CopyStringChars(cx, chars, name)) return false;
351 
352   outName[len] = '\0';
353   return true;
354 }
355 
356 const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol";
357 const char16_t Concrete<JSScript>::concreteTypeName[] = u"JSScript";
358 const char16_t Concrete<js::LazyScript>::concreteTypeName[] = u"js::LazyScript";
359 const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] =
360     u"js::jit::JitCode";
361 const char16_t Concrete<js::Shape>::concreteTypeName[] = u"js::Shape";
362 const char16_t Concrete<js::BaseShape>::concreteTypeName[] = u"js::BaseShape";
363 const char16_t Concrete<js::ObjectGroup>::concreteTypeName[] =
364     u"js::ObjectGroup";
365 const char16_t Concrete<js::Scope>::concreteTypeName[] = u"js::Scope";
366 const char16_t Concrete<js::RegExpShared>::concreteTypeName[] =
367     u"js::RegExpShared";
368 
369 namespace JS {
370 namespace ubi {
371 
RootList(JSContext * cx,Maybe<AutoCheckCannotGC> & noGC,bool wantNames)372 RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC,
373                    bool wantNames /* = false */)
374     : noGC(noGC), cx(cx), edges(), wantNames(wantNames) {}
375 
init()376 bool RootList::init() {
377   EdgeVectorTracer tracer(cx->runtime(), &edges, wantNames);
378   js::TraceRuntime(&tracer);
379   if (!tracer.okay) return false;
380   noGC.emplace();
381   return true;
382 }
383 
init(CompartmentSet & debuggees)384 bool RootList::init(CompartmentSet& debuggees) {
385   EdgeVector allRootEdges;
386   EdgeVectorTracer tracer(cx->runtime(), &allRootEdges, wantNames);
387 
388   ZoneSet debuggeeZones;
389   if (!debuggeeZones.init()) return false;
390   for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
391     if (!debuggeeZones.put(range.front()->zone())) return false;
392   }
393 
394   js::TraceRuntime(&tracer);
395   if (!tracer.okay) return false;
396   TraceIncomingCCWs(&tracer, debuggees);
397   if (!tracer.okay) return false;
398 
399   for (EdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) {
400     Edge& edge = r.front();
401 
402     JSCompartment* compartment = edge.referent.compartment();
403     if (compartment && !debuggees.has(compartment)) continue;
404 
405     Zone* zone = edge.referent.zone();
406     if (zone && !debuggeeZones.has(zone)) continue;
407 
408     if (!edges.append(mozilla::Move(edge))) return false;
409   }
410 
411   noGC.emplace();
412   return true;
413 }
414 
init(HandleObject debuggees)415 bool RootList::init(HandleObject debuggees) {
416   MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees));
417   js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get());
418 
419   CompartmentSet debuggeeCompartments;
420   if (!debuggeeCompartments.init()) return false;
421 
422   for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
423        r.popFront()) {
424     if (!debuggeeCompartments.put(r.front()->compartment())) return false;
425   }
426 
427   if (!init(debuggeeCompartments)) return false;
428 
429   // Ensure that each of our debuggee globals are in the root list.
430   for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
431        r.popFront()) {
432     if (!addRoot(JS::ubi::Node(static_cast<JSObject*>(r.front())),
433                  u"debuggee global")) {
434       return false;
435     }
436   }
437 
438   return true;
439 }
440 
addRoot(Node node,const char16_t * edgeName)441 bool RootList::addRoot(Node node, const char16_t* edgeName) {
442   MOZ_ASSERT(noGC.isSome());
443   MOZ_ASSERT_IF(wantNames, edgeName);
444 
445   UniqueTwoByteChars name;
446   if (edgeName) {
447     name = js::DuplicateString(edgeName);
448     if (!name) return false;
449   }
450 
451   return edges.append(mozilla::Move(Edge(name.release(), node)));
452 }
453 
454 const char16_t Concrete<RootList>::concreteTypeName[] = u"JS::ubi::RootList";
455 
edges(JSContext * cx,bool wantNames) const456 UniquePtr<EdgeRange> Concrete<RootList>::edges(JSContext* cx,
457                                                bool wantNames) const {
458   MOZ_ASSERT_IF(wantNames, get().wantNames);
459   return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
460 }
461 
462 }  // namespace ubi
463 }  // namespace JS
464