UbiNode.cpp (16030B)
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 #include "js/UbiNode.h" 8 9 #include "mozilla/Assertions.h" 10 11 #include <algorithm> 12 13 #include "debugger/Debugger.h" 14 #include "gc/GC.h" 15 #include "jit/JitCode.h" 16 #include "js/Debug.h" 17 #include "js/TracingAPI.h" 18 #include "js/TypeDecls.h" 19 #include "js/UbiNodeUtils.h" 20 #include "js/Utility.h" 21 #include "util/Text.h" 22 #include "vm/BigIntType.h" 23 #include "vm/Compartment.h" 24 #include "vm/EnvironmentObject.h" 25 #include "vm/GetterSetter.h" 26 #include "vm/GlobalObject.h" 27 #include "vm/JSContext.h" 28 #include "vm/JSObject.h" 29 #include "vm/JSScript.h" 30 #include "vm/PropMap.h" 31 #include "vm/Scope.h" 32 #include "vm/Shape.h" 33 #include "vm/StringType.h" 34 #include "vm/SymbolType.h" 35 36 #include "debugger/Debugger-inl.h" 37 #include "gc/StableCellHasher-inl.h" 38 #include "vm/JSObject-inl.h" 39 40 using namespace js; 41 42 using JS::ApplyGCThingTyped; 43 using JS::HandleValue; 44 using JS::Value; 45 using JS::ZoneSet; 46 using JS::ubi::AtomOrTwoByteChars; 47 using JS::ubi::CoarseType; 48 using JS::ubi::Concrete; 49 using JS::ubi::Edge; 50 using JS::ubi::EdgeRange; 51 using JS::ubi::EdgeVector; 52 using JS::ubi::Node; 53 using JS::ubi::StackFrame; 54 using JS::ubi::TracerConcrete; 55 using JS::ubi::TracerConcreteWithRealm; 56 using mozilla::RangedPtr; 57 58 struct CopyToBufferMatcher { 59 RangedPtr<char16_t> destination; 60 size_t maxLength; 61 62 CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength) 63 : destination(destination), maxLength(maxLength) {} 64 65 template <typename CharT> 66 static size_t copyToBufferHelper(const CharT* src, RangedPtr<char16_t> dest, 67 size_t length) { 68 size_t i = 0; 69 for (; i < length; i++) { 70 dest[i] = src[i]; 71 } 72 return i; 73 } 74 75 size_t operator()(JSAtom* atom) { 76 if (!atom) { 77 return 0; 78 } 79 80 size_t length = std::min(atom->length(), maxLength); 81 JS::AutoCheckCannotGC noGC; 82 return atom->hasTwoByteChars() 83 ? copyToBufferHelper(atom->twoByteChars(noGC), destination, 84 length) 85 : copyToBufferHelper(atom->latin1Chars(noGC), destination, 86 length); 87 } 88 89 size_t operator()(const char16_t* chars) { 90 if (!chars) { 91 return 0; 92 } 93 94 size_t length = std::min(js_strlen(chars), maxLength); 95 return copyToBufferHelper(chars, destination, length); 96 } 97 }; 98 99 size_t JS::ubi::AtomOrTwoByteChars::copyToBuffer( 100 RangedPtr<char16_t> destination, size_t length) { 101 CopyToBufferMatcher m(destination, length); 102 return match(m); 103 } 104 105 struct LengthMatcher { 106 size_t operator()(JSAtom* atom) { return atom ? atom->length() : 0; } 107 108 size_t operator()(const char16_t* chars) { 109 return chars ? js_strlen(chars) : 0; 110 } 111 }; 112 113 size_t JS::ubi::AtomOrTwoByteChars::length() { 114 LengthMatcher m; 115 return match(m); 116 } 117 118 size_t StackFrame::source(RangedPtr<char16_t> destination, 119 size_t length) const { 120 auto s = source(); 121 return s.copyToBuffer(destination, length); 122 } 123 124 size_t StackFrame::functionDisplayName(RangedPtr<char16_t> destination, 125 size_t length) const { 126 auto name = functionDisplayName(); 127 return name.copyToBuffer(destination, length); 128 } 129 130 size_t StackFrame::sourceLength() { return source().length(); } 131 132 size_t StackFrame::functionDisplayNameLength() { 133 return functionDisplayName().length(); 134 } 135 136 // All operations on null ubi::Nodes crash. 137 CoarseType Concrete<void>::coarseType() const { MOZ_CRASH("null ubi::Node"); } 138 const char16_t* Concrete<void>::typeName() const { 139 MOZ_CRASH("null ubi::Node"); 140 } 141 JS::Zone* Concrete<void>::zone() const { MOZ_CRASH("null ubi::Node"); } 142 JS::Compartment* Concrete<void>::compartment() const { 143 MOZ_CRASH("null ubi::Node"); 144 } 145 JS::Realm* Concrete<void>::realm() const { MOZ_CRASH("null ubi::Node"); } 146 147 UniquePtr<EdgeRange> Concrete<void>::edges(JSContext*, bool) const { 148 MOZ_CRASH("null ubi::Node"); 149 } 150 151 Node::Size Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const { 152 MOZ_CRASH("null ubi::Node"); 153 } 154 155 Node::Node(JS::GCCellPtr thing) { 156 ApplyGCThingTyped(thing, [this](auto t) { this->construct(t); }); 157 } 158 159 Node::Node(HandleValue value) { 160 if (!ApplyGCThingTyped(value, [this](auto t) { this->construct(t); })) { 161 construct<void>(nullptr); 162 } 163 } 164 165 static bool IsSafeToExposeToJS(JSObject* obj) { 166 if (obj->is<js::EnvironmentObject>() || obj->is<js::ScriptSourceObject>() || 167 obj->is<js::DebugEnvironmentProxy>()) { 168 return false; 169 } 170 if (obj->is<JSFunction>() && js::IsInternalFunctionObject(*obj)) { 171 return false; 172 } 173 return true; 174 } 175 176 Value Node::exposeToJS() const { 177 Value v; 178 179 if (is<JSObject>()) { 180 JSObject* obj = as<JSObject>(); 181 if (IsSafeToExposeToJS(obj)) { 182 v.setObject(*obj); 183 } else { 184 v.setUndefined(); 185 } 186 } else if (is<JSString>()) { 187 v.setString(as<JSString>()); 188 } else if (is<JS::Symbol>()) { 189 v.setSymbol(as<JS::Symbol>()); 190 } else if (is<BigInt>()) { 191 v.setBigInt(as<BigInt>()); 192 } else { 193 v.setUndefined(); 194 } 195 196 ExposeValueToActiveJS(v); 197 198 return v; 199 } 200 201 // A JS::CallbackTracer subclass that adds a Edge to a Vector for each 202 // edge on which it is invoked. 203 class EdgeVectorTracer final : public JS::CallbackTracer { 204 // The vector to which we add Edges. 205 EdgeVector* vec; 206 207 // True if we should populate the edge's names. 208 bool wantNames; 209 210 void onChild(JS::GCCellPtr thing, const char* name) override { 211 if (!okay) { 212 return; 213 } 214 215 // Don't trace permanent atoms and well-known symbols that are owned by 216 // a parent JSRuntime. 217 if (thing.is<JSString>() && thing.as<JSString>().isPermanentAtom()) { 218 return; 219 } 220 if (thing.is<JS::Symbol>() && thing.as<JS::Symbol>().isWellKnownSymbol()) { 221 return; 222 } 223 224 char16_t* name16 = nullptr; 225 if (wantNames) { 226 // Ask the tracer to compute an edge name for us. 227 char buffer[1024]; 228 context().getEdgeName(name, buffer, sizeof(buffer)); 229 name = buffer; 230 231 // Convert the name to char16_t characters. 232 name16 = js_pod_malloc<char16_t>(strlen(name) + 1); 233 if (!name16) { 234 okay = false; 235 return; 236 } 237 238 size_t i; 239 for (i = 0; name[i]; i++) { 240 name16[i] = name[i]; 241 } 242 name16[i] = '\0'; 243 } 244 245 // The simplest code is correct! The temporary Edge takes 246 // ownership of name; if the append succeeds, the vector element 247 // then takes ownership; if the append fails, then the temporary 248 // retains it, and its destructor will free it. 249 if (!vec->append(Edge(name16, Node(thing)))) { 250 okay = false; 251 return; 252 } 253 } 254 255 public: 256 // True if no errors (OOM, say) have yet occurred. 257 bool okay; 258 259 EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames) 260 : JS::CallbackTracer(rt), vec(vec), wantNames(wantNames), okay(true) {} 261 }; 262 263 template <typename Referent> 264 JS::Zone* TracerConcrete<Referent>::zone() const { 265 return get().zoneFromAnyThread(); 266 } 267 268 template JS::Zone* TracerConcrete<js::BaseScript>::zone() const; 269 template JS::Zone* TracerConcrete<js::Shape>::zone() const; 270 template JS::Zone* TracerConcrete<js::BaseShape>::zone() const; 271 template JS::Zone* TracerConcrete<js::GetterSetter>::zone() const; 272 template JS::Zone* TracerConcrete<js::PropMap>::zone() const; 273 template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const; 274 template JS::Zone* TracerConcrete<js::Scope>::zone() const; 275 template JS::Zone* TracerConcrete<JS::Symbol>::zone() const; 276 template JS::Zone* TracerConcrete<BigInt>::zone() const; 277 template JS::Zone* TracerConcrete<JSString>::zone() const; 278 279 template <typename Referent> 280 UniquePtr<EdgeRange> TracerConcrete<Referent>::edges(JSContext* cx, 281 bool wantNames) const { 282 auto range = js::MakeUnique<SimpleEdgeRange>(); 283 if (!range) { 284 return nullptr; 285 } 286 287 if (!range->addTracerEdges(cx->runtime(), ptr, 288 JS::MapTypeToTraceKind<Referent>::kind, 289 wantNames)) { 290 return nullptr; 291 } 292 293 // Note: Clang 3.8 (or older) require an explicit construction of the 294 // target UniquePtr type. When we no longer require to support these Clang 295 // versions the return statement can be simplified to |return range;|. 296 return UniquePtr<EdgeRange>(range.release()); 297 } 298 299 template UniquePtr<EdgeRange> TracerConcrete<js::BaseScript>::edges( 300 JSContext* cx, bool wantNames) const; 301 template UniquePtr<EdgeRange> TracerConcrete<js::Shape>::edges( 302 JSContext* cx, bool wantNames) const; 303 template UniquePtr<EdgeRange> TracerConcrete<js::BaseShape>::edges( 304 JSContext* cx, bool wantNames) const; 305 template UniquePtr<EdgeRange> TracerConcrete<js::GetterSetter>::edges( 306 JSContext* cx, bool wantNames) const; 307 template UniquePtr<EdgeRange> TracerConcrete<js::PropMap>::edges( 308 JSContext* cx, bool wantNames) const; 309 template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::edges( 310 JSContext* cx, bool wantNames) const; 311 template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges( 312 JSContext* cx, bool wantNames) const; 313 template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges( 314 JSContext* cx, bool wantNames) const; 315 template UniquePtr<EdgeRange> TracerConcrete<BigInt>::edges( 316 JSContext* cx, bool wantNames) const; 317 template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges( 318 JSContext* cx, bool wantNames) const; 319 320 template <typename Referent> 321 JS::Compartment* TracerConcreteWithRealm<Referent>::compartment() const { 322 return TracerBase::get().compartment(); 323 } 324 325 template <typename Referent> 326 Realm* TracerConcreteWithRealm<Referent>::realm() const { 327 return TracerBase::get().realm(); 328 } 329 330 template Realm* TracerConcreteWithRealm<js::BaseScript>::realm() const; 331 template JS::Compartment* TracerConcreteWithRealm<js::BaseScript>::compartment() 332 const; 333 334 bool Concrete<JSObject>::hasAllocationStack() const { 335 return !!js::Debugger::getObjectAllocationSite(get()); 336 } 337 338 StackFrame Concrete<JSObject>::allocationStack() const { 339 MOZ_ASSERT(hasAllocationStack()); 340 return StackFrame(js::Debugger::getObjectAllocationSite(get())); 341 } 342 343 const char* Concrete<JSObject>::jsObjectClassName() const { 344 return Concrete::get().getClass()->name; 345 } 346 347 JS::Compartment* Concrete<JSObject>::compartment() const { 348 return Concrete::get().compartment(); 349 } 350 351 Realm* Concrete<JSObject>::realm() const { 352 // Cross-compartment wrappers are shared by all realms in the compartment, 353 // so we return nullptr in that case. 354 return JS::GetObjectRealmOrNull(&Concrete::get()); 355 } 356 357 const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol"; 358 const char16_t Concrete<BigInt>::concreteTypeName[] = u"JS::BigInt"; 359 const char16_t Concrete<js::BaseScript>::concreteTypeName[] = u"js::BaseScript"; 360 const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] = 361 u"js::jit::JitCode"; 362 const char16_t Concrete<js::Shape>::concreteTypeName[] = u"js::Shape"; 363 const char16_t Concrete<js::BaseShape>::concreteTypeName[] = u"js::BaseShape"; 364 const char16_t Concrete<js::GetterSetter>::concreteTypeName[] = 365 u"js::GetterSetter"; 366 const char16_t Concrete<js::PropMap>::concreteTypeName[] = u"js::PropMap"; 367 const char16_t Concrete<js::Scope>::concreteTypeName[] = u"js::Scope"; 368 const char16_t Concrete<js::RegExpShared>::concreteTypeName[] = 369 u"js::RegExpShared"; 370 371 namespace JS { 372 namespace ubi { 373 374 RootList::RootList(JSContext* cx, bool wantNames /* = false */) 375 : cx(cx), wantNames(wantNames), inited(false) {} 376 377 std::pair<bool, JS::AutoCheckCannotGC> RootList::init() { 378 EdgeVectorTracer tracer(cx->runtime(), &edges, wantNames); 379 js::TraceRuntime(&tracer); 380 inited = tracer.okay; 381 return {tracer.okay, JS::AutoCheckCannotGC(cx)}; 382 } 383 384 std::pair<bool, JS::AutoCheckCannotGC> RootList::init( 385 CompartmentSet& debuggees) { 386 EdgeVector allRootEdges; 387 EdgeVectorTracer tracer(cx->runtime(), &allRootEdges, wantNames); 388 389 ZoneSet debuggeeZones; 390 for (auto range = debuggees.all(); !range.empty(); range.popFront()) { 391 if (!debuggeeZones.put(range.front()->zone())) { 392 return {false, JS::AutoCheckCannotGC(cx)}; 393 } 394 } 395 396 js::TraceRuntime(&tracer); 397 if (!tracer.okay) { 398 return {false, JS::AutoCheckCannotGC(cx)}; 399 } 400 js::gc::TraceIncomingCCWs(&tracer, debuggees); 401 if (!tracer.okay) { 402 return {false, JS::AutoCheckCannotGC(cx)}; 403 } 404 405 for (EdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) { 406 Edge& edge = r.front(); 407 408 JS::Compartment* compartment = edge.referent.compartment(); 409 if (compartment && !debuggees.has(compartment)) { 410 continue; 411 } 412 413 Zone* zone = edge.referent.zone(); 414 if (zone && !debuggeeZones.has(zone)) { 415 continue; 416 } 417 418 if (!edges.append(std::move(edge))) { 419 return {false, JS::AutoCheckCannotGC(cx)}; 420 } 421 } 422 423 inited = true; 424 return {true, JS::AutoCheckCannotGC(cx)}; 425 } 426 427 std::pair<bool, JS::AutoCheckCannotGC> RootList::init(HandleObject debuggees) { 428 MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees)); 429 js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get()); 430 431 CompartmentSet debuggeeCompartments; 432 433 for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); 434 r.popFront()) { 435 if (!debuggeeCompartments.put(r.front()->compartment())) { 436 return {false, JS::AutoCheckCannotGC(cx)}; 437 } 438 } 439 440 auto [ok, nogc] = init(debuggeeCompartments); 441 if (!ok) { 442 return {false, nogc}; 443 } 444 445 // Ensure that each of our debuggee globals are in the root list. 446 for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); 447 r.popFront()) { 448 if (!addRoot(JS::ubi::Node(static_cast<JSObject*>(r.front())), 449 u"debuggee global")) { 450 return {false, nogc}; 451 } 452 } 453 454 inited = true; 455 return {true, nogc}; 456 } 457 458 bool RootList::addRoot(Node node, const char16_t* edgeName) { 459 MOZ_ASSERT_IF(wantNames, edgeName); 460 461 UniqueTwoByteChars name; 462 if (edgeName) { 463 name = js::DuplicateString(edgeName); 464 if (!name) { 465 return false; 466 } 467 } 468 469 return edges.append(Edge(name.release(), node)); 470 } 471 472 const char16_t Concrete<RootList>::concreteTypeName[] = u"JS::ubi::RootList"; 473 474 UniquePtr<EdgeRange> Concrete<RootList>::edges(JSContext* cx, 475 bool wantNames) const { 476 MOZ_ASSERT_IF(wantNames, get().wantNames); 477 return js::MakeUnique<PreComputedEdgeRange>(get().edges); 478 } 479 480 bool SimpleEdgeRange::addTracerEdges(JSRuntime* rt, void* thing, 481 JS::TraceKind kind, bool wantNames) { 482 EdgeVectorTracer tracer(rt, &edges, wantNames); 483 JS::TraceChildren(&tracer, JS::GCCellPtr(thing, kind)); 484 settle(); 485 return tracer.okay; 486 } 487 488 void Concrete<JSObject>::construct(void* storage, JSObject* ptr) { 489 if (ptr) { 490 auto clasp = ptr->getClass(); 491 auto callback = ptr->compartment() 492 ->runtimeFromMainThread() 493 ->constructUbiNodeForDOMObjectCallback; 494 if (clasp->isDOMClass() && callback) { 495 AutoSuppressGCAnalysis suppress; 496 callback(storage, ptr); 497 return; 498 } 499 } 500 new (storage) Concrete(ptr); 501 } 502 503 void SetConstructUbiNodeForDOMObjectCallback(JSContext* cx, 504 void (*callback)(void*, 505 JSObject*)) { 506 cx->runtime()->constructUbiNodeForDOMObjectCallback = callback; 507 } 508 509 JS_PUBLIC_API const char* CoarseTypeToString(CoarseType type) { 510 switch (type) { 511 case CoarseType::Other: 512 return "Other"; 513 case CoarseType::Object: 514 return "Object"; 515 case CoarseType::Script: 516 return "Script"; 517 case CoarseType::String: 518 return "String"; 519 case CoarseType::DOMNode: 520 return "DOMNode"; 521 default: 522 return "Unknown"; 523 } 524 }; 525 526 } // namespace ubi 527 } // namespace JS