summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/t/hazards/source.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/devtools/rootAnalysis/t/hazards/source.cpp')
-rw-r--r--js/src/devtools/rootAnalysis/t/hazards/source.cpp566
1 files changed, 566 insertions, 0 deletions
diff --git a/js/src/devtools/rootAnalysis/t/hazards/source.cpp b/js/src/devtools/rootAnalysis/t/hazards/source.cpp
new file mode 100644
index 0000000000..fe991653af
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/hazards/source.cpp
@@ -0,0 +1,566 @@
+/* -*- 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 <utility>
+
+#define ANNOTATE(property) __attribute__((annotate(property)))
+
+// MarkVariableAsGCSafe is a magic function name used as an
+// explicit annotation.
+
+namespace JS {
+namespace detail {
+template <typename T>
+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 <typename T, typename U>
+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 <typename T>
+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<int, Cell*> container1;
+ usecontainer(&container1);
+ GC();
+ usecontainer(&container1);
+ }
+
+ {
+ // As above, but with a non-GC type.
+ UntypedContainer<int, double> 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<Cell*>(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 <typename T>
+class UniquePtr {
+ T* val;
+
+ public:
+ UniquePtr() : val(nullptr) { asm(""); }
+ UniquePtr(T* p) : val(p) {}
+ UniquePtr(UniquePtr<T>&& 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<Cell> 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<long>(&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<Cell> unsafe4(&cell);
+ GC();
+ // Destructor uses unsafe4.
+ }
+
+ // reset() to safe value before the GC.
+ {
+ mozilla::UniquePtr<Cell> safe5(&cell);
+ safe5.reset();
+ GC();
+ }
+
+ // reset() to safe value after the GC.
+ {
+ mozilla::UniquePtr<Cell> 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<Cell> unsafe7(&cell);
+ GC();
+ use(unsafe7.get());
+ unsafe7.reset();
+ }
+
+ // initialized to safe value.
+ {
+ mozilla::UniquePtr<Cell> safe8;
+ GC();
+ }
+
+ // passed to a function that takes ownership before GC.
+ {
+ mozilla::UniquePtr<Cell> safe9(&cell);
+ consume(std::move(safe9));
+ GC();
+ }
+
+ // passed to a function that takes ownership after GC.
+ {
+ mozilla::UniquePtr<Cell> 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<Cell> 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<Cell> 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<Cell> 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 = &sub;
+ IDL_Interface* base = &sub;
+ {
+ 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 <typename T>
+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<float> v1;
+ Cell* ref_unsafe1 = &cell;
+ return ref_unsafe1;
+}
+
+Cell* refptr_test2() {
+ static Cell cell;
+ RefPtr<float> v2;
+ Cell* ref_safe2 = &cell;
+ v2.forget();
+ return ref_safe2;
+}
+
+Cell* refptr_test3() {
+ static Cell cell;
+ RefPtr<float> v3;
+ Cell* ref_unsafe3 = &cell;
+ if (x) {
+ v3.forget();
+ }
+ return ref_unsafe3;
+}
+
+Cell* refptr_test4() {
+ static Cell cell;
+ RefPtr<int> r;
+ return &cell; // hazard in return value
+}
+
+Cell* refptr_test5() {
+ static Cell cell;
+ RefPtr<int> r;
+ return nullptr; // returning immobile value, so no hazard
+}
+
+float somefloat = 1.2;
+
+Cell* refptr_test6() {
+ static Cell cell;
+ RefPtr<float> 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<float> 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<float> 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<float> 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<float> 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<bool, AutoCheckCannotGC> 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();
+}