/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #define ANNOTATE(property) __attribute__((annotate(property))) // MarkVariableAsGCSafe is a magic function name used as an // explicit annotation. namespace JS { namespace detail { template static void MarkVariableAsGCSafe(T&) { asm(""); } } // namespace detail } // namespace JS #define JS_HAZ_VARIABLE_IS_GC_SAFE(var) JS::detail::MarkVariableAsGCSafe(var) struct Cell { int f; } ANNOTATE("GC Thing"); template struct UntypedContainer { char data[sizeof(T) + sizeof(U)]; } ANNOTATE("moz_inherit_type_annotations_from_template_args"); struct RootedCell { RootedCell(Cell*) {} } ANNOTATE("Rooted Pointer"); class AutoSuppressGC_Base { public: AutoSuppressGC_Base() {} ~AutoSuppressGC_Base() {} } ANNOTATE("Suppress GC"); class AutoSuppressGC_Child : public AutoSuppressGC_Base { public: AutoSuppressGC_Child() : AutoSuppressGC_Base() {} }; class AutoSuppressGC { AutoSuppressGC_Child helpImBeingSuppressed; public: AutoSuppressGC() {} }; class AutoCheckCannotGC { public: AutoCheckCannotGC() {} ~AutoCheckCannotGC() { asm(""); } } ANNOTATE("Invalidated by GC"); extern void GC() ANNOTATE("GC Call"); extern void invisible(); void GC() { // If the implementation is too trivial, the function body won't be emitted at // all. asm(""); invisible(); } extern void usecell(Cell*); extern bool flipcoin(); void suppressedFunction() { GC(); // Calls GC, but is always called within AutoSuppressGC } void halfSuppressedFunction() { GC(); // Calls GC, but is sometimes called within AutoSuppressGC } void unsuppressedFunction() { GC(); // Calls GC, never within AutoSuppressGC } class IDL_Interface { public: ANNOTATE("Can run script") virtual void canScriptThis() {} virtual void cannotScriptThis() {} ANNOTATE("Can run script") virtual void overridden_canScriptThis() = 0; virtual void overridden_cannotScriptThis() = 0; }; class IDL_Subclass : public IDL_Interface { ANNOTATE("Can run script") void overridden_canScriptThis() override {} void overridden_cannotScriptThis() override {} }; volatile static int x = 3; volatile static int* xp = &x; struct GCInDestructor { ~GCInDestructor() { invisible(); asm(""); *xp = 4; GC(); } }; template void usecontainer(T* value) { if (value) asm(""); } Cell* cell() { static Cell c; return &c; } Cell* f() { GCInDestructor kaboom; Cell* cell1 = cell(); Cell* cell2 = cell(); Cell* cell3 = cell(); Cell* cell4 = cell(); { AutoSuppressGC nogc; suppressedFunction(); halfSuppressedFunction(); } usecell(cell1); halfSuppressedFunction(); usecell(cell2); unsuppressedFunction(); { // Old bug: it would look from the first AutoSuppressGC constructor it // found to the last destructor. This statement *should* have no effect. AutoSuppressGC nogc; } usecell(cell3); Cell* cell5 = cell(); usecell(cell5); { // Templatized container that inherits attributes from Cell*, should // report a hazard. UntypedContainer container1; usecontainer(&container1); GC(); usecontainer(&container1); } { // As above, but with a non-GC type. UntypedContainer container2; usecontainer(&container2); GC(); usecontainer(&container2); } // Hazard in return value due to ~GCInDestructor Cell* cell6 = cell(); return cell6; } Cell* copy_and_gc(Cell* src) { GC(); return reinterpret_cast(88); } void use(Cell* cell) { static int x = 0; if (cell) x++; } struct CellContainer { Cell* cell; CellContainer() { asm(""); } }; void loopy() { Cell cell; // No hazard: haz1 is not live during call to copy_and_gc. Cell* haz1; for (int i = 0; i < 10; i++) { haz1 = copy_and_gc(haz1); } // No hazard: haz2 is live up to just before the GC, and starting at the // next statement after it, but not across the GC. Cell* haz2 = &cell; for (int j = 0; j < 10; j++) { use(haz2); GC(); haz2 = &cell; } // Hazard: haz3 is live from the final statement in one iteration, across // the GC in the next, to the use in the 2nd statement. Cell* haz3; for (int k = 0; k < 10; k++) { GC(); use(haz3); haz3 = &cell; } // Hazard: haz4 is live across a GC hidden in a loop. Cell* haz4 = &cell; for (int i2 = 0; i2 < 10; i2++) { GC(); } use(haz4); // Hazard: haz5 is live from within a loop across a GC. Cell* haz5; for (int i3 = 0; i3 < 10; i3++) { haz5 = &cell; } GC(); use(haz5); // No hazard: similar to the haz3 case, but verifying that we do not get // into an infinite loop. Cell* haz6; for (int i4 = 0; i4 < 10; i4++) { GC(); haz6 = &cell; } // No hazard: haz7 is constructed within the body, so it can't make a // hazard across iterations. Note that this requires CellContainer to have // a constructor, because otherwise the analysis doesn't see where // variables are declared. (With the constructor, it knows that // construction of haz7 obliterates any previous value it might have had. // Not that that's possible given its scope, but the analysis doesn't get // that information.) for (int i5 = 0; i5 < 10; i5++) { GC(); CellContainer haz7; use(haz7.cell); haz7.cell = &cell; } // Hazard: make sure we *can* see hazards across iterations involving // CellContainer; CellContainer haz8; for (int i6 = 0; i6 < 10; i6++) { GC(); use(haz8.cell); haz8.cell = &cell; } } namespace mozilla { template class UniquePtr { T* val; public: UniquePtr() : val(nullptr) { asm(""); } UniquePtr(T* p) : val(p) {} UniquePtr(UniquePtr&& u) : val(u.val) { u.val = nullptr; } ~UniquePtr() { use(val); } T* get() { return val; } void reset() { val = nullptr; } } ANNOTATE("moz_inherit_type_annotations_from_template_args"); } // namespace mozilla extern void consume(mozilla::UniquePtr uptr); void safevals() { Cell cell; // Simple hazard. Cell* unsafe1 = &cell; GC(); use(unsafe1); // Safe because it's known to be nullptr. Cell* safe2 = &cell; safe2 = nullptr; GC(); use(safe2); // Unsafe because it may not be nullptr. Cell* unsafe3 = &cell; if (reinterpret_cast(&cell) & 0x100) { unsafe3 = nullptr; } GC(); use(unsafe3); // Unsafe because it's not nullptr anymore. Cell* unsafe3b = &cell; unsafe3b = nullptr; unsafe3b = &cell; GC(); use(unsafe3b); // Hazard involving UniquePtr. { mozilla::UniquePtr unsafe4(&cell); GC(); // Destructor uses unsafe4. } // reset() to safe value before the GC. { mozilla::UniquePtr safe5(&cell); safe5.reset(); GC(); } // reset() to safe value after the GC. { mozilla::UniquePtr safe6(&cell); GC(); safe6.reset(); } // reset() to safe value after the GC -- but we've already used it, so it's // too late. { mozilla::UniquePtr unsafe7(&cell); GC(); use(unsafe7.get()); unsafe7.reset(); } // initialized to safe value. { mozilla::UniquePtr safe8; GC(); } // passed to a function that takes ownership before GC. { mozilla::UniquePtr safe9(&cell); consume(std::move(safe9)); GC(); } // passed to a function that takes ownership after GC. { mozilla::UniquePtr unsafe10(&cell); GC(); consume(std::move(unsafe10)); } // annotated to be safe before the GC. (This doesn't make // a lot of sense here; the annotation is for when some // type is known to only contain safe values, eg it is // initialized as empty, or it is a union and we know // that the GC pointer variants are not in use.) { mozilla::UniquePtr safe11(&cell); JS_HAZ_VARIABLE_IS_GC_SAFE(safe11); GC(); } // annotate as safe value after the GC -- since nothing else // has touched the variable, that means it was already safe // during the GC. { mozilla::UniquePtr safe12(&cell); GC(); JS_HAZ_VARIABLE_IS_GC_SAFE(safe12); } // annotate as safe after the GC -- but we've already used it, so it's // too late. { mozilla::UniquePtr unsafe13(&cell); GC(); use(unsafe13.get()); JS_HAZ_VARIABLE_IS_GC_SAFE(unsafe13); } // Check JS_HAZ_CAN_RUN_SCRIPT annotation handling. IDL_Subclass sub; IDL_Subclass* subp = ⊂ IDL_Interface* base = ⊂ { Cell* unsafe14 = &cell; base->canScriptThis(); use(unsafe14); } { Cell* unsafe15 = &cell; subp->canScriptThis(); use(unsafe15); } { // Almost the same as the last one, except call using the actual object, not // a pointer. The type is known, so there is no danger of the actual type // being a subclass that has overridden the method with an implementation // that calls script. Cell* safe16 = &cell; sub.canScriptThis(); use(safe16); } { Cell* safe17 = &cell; base->cannotScriptThis(); use(safe17); } { Cell* safe18 = &cell; subp->cannotScriptThis(); use(safe18); } { // A use after a GC, but not before. (This does not initialize safe19 by // setting it to a value, because assignment would start its live range, and // this test is to see if a variable with no known live range start requires // a use before the GC or not. It should.) Cell* safe19; GC(); extern void initCellPtr(Cell**); initCellPtr(&safe19); } } // Make sure `this` is live at the beginning of a function. class Subcell : public Cell { int method() { GC(); return f; // this->f } }; template struct RefPtr { ~RefPtr() { GC(); } bool forget() { return true; } bool use() { return true; } void assign_with_AddRef(T* aRawPtr) { asm(""); } }; extern bool flipcoin(); Cell* refptr_test1() { static Cell cell; RefPtr v1; Cell* ref_unsafe1 = &cell; return ref_unsafe1; } Cell* refptr_test2() { static Cell cell; RefPtr v2; Cell* ref_safe2 = &cell; v2.forget(); return ref_safe2; } Cell* refptr_test3() { static Cell cell; RefPtr v3; Cell* ref_unsafe3 = &cell; if (x) { v3.forget(); } return ref_unsafe3; } Cell* refptr_test4() { static Cell cell; RefPtr r; return &cell; // hazard in return value } Cell* refptr_test5() { static Cell cell; RefPtr r; return nullptr; // returning immobile value, so no hazard } float somefloat = 1.2; Cell* refptr_test6() { static Cell cell; RefPtr v6; Cell* ref_unsafe6 = &cell; // v6 can be used without an intervening forget() before the end of the // function, even though forget() will be called at least once. v6.forget(); if (x) { v6.forget(); v6.assign_with_AddRef(&somefloat); } return ref_unsafe6; } Cell* refptr_test7() { static Cell cell; RefPtr v7; Cell* ref_unsafe7 = &cell; // Similar to above, but with a loop. while (flipcoin()) { v7.forget(); v7.assign_with_AddRef(&somefloat); } return ref_unsafe7; } Cell* refptr_test8() { static Cell cell; RefPtr v8; Cell* ref_unsafe8 = &cell; // If the loop is traversed, forget() will be called. But that doesn't // matter, because even on the last iteration v8.use() will have been called // (and potentially dropped the refcount or whatever.) while (v8.use()) { v8.forget(); } return ref_unsafe8; } Cell* refptr_test9() { static Cell cell; RefPtr v9; Cell* ref_safe9 = &cell; // Even when not going through the loop, forget() will be called and so the // dtor will not Release. while (v9.forget()) { v9.assign_with_AddRef(&somefloat); } return ref_safe9; } Cell* refptr_test10() { static Cell cell; RefPtr v10; Cell* ref_unsafe10 = &cell; // The destructor has a backwards path that skips the loop body. v10.assign_with_AddRef(&somefloat); while (flipcoin()) { v10.forget(); } return ref_unsafe10; } std::pair pair_returning_function() { return std::make_pair(true, AutoCheckCannotGC()); } void aggr_init_unsafe() { // nogc will be live after the call, so across the GC. auto [ok, nogc] = pair_returning_function(); GC(); } void aggr_init_safe() { // The analysis should be able to tell that nogc is only live after the call, // not before. (This is to check for a problem where the return value was // getting stored into a different temporary than the local nogc variable, // and so its initialization was never seen and so it was assumed to be live // throughout the function.) GC(); auto [ok, nogc] = pair_returning_function(); }