summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/t/hazards
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /js/src/devtools/rootAnalysis/t/hazards
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/devtools/rootAnalysis/t/hazards')
-rw-r--r--js/src/devtools/rootAnalysis/t/hazards/source.cpp459
-rw-r--r--js/src/devtools/rootAnalysis/t/hazards/test.py101
2 files changed, 560 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..979591ef6b
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/hazards/source.cpp
@@ -0,0 +1,459 @@
+/* -*- 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() {}
+};
+
+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*);
+
+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);
+ }
+}
+
+// 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(); }
+ void forget() {}
+};
+
+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
+}
diff --git a/js/src/devtools/rootAnalysis/t/hazards/test.py b/js/src/devtools/rootAnalysis/t/hazards/test.py
new file mode 100644
index 0000000000..75b8811c0f
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/hazards/test.py
@@ -0,0 +1,101 @@
+# flake8: noqa: F821
+
+from collections import defaultdict
+
+test.compile("source.cpp")
+test.run_analysis_script("gcTypes")
+
+# gcFunctions should be the inverse, but we get to rely on unmangled names here.
+gcFunctions = test.load_gcFunctions()
+assert "void GC()" in gcFunctions
+assert "void suppressedFunction()" not in gcFunctions
+assert "void halfSuppressedFunction()" in gcFunctions
+assert "void unsuppressedFunction()" in gcFunctions
+assert "int32 Subcell::method()" in gcFunctions
+assert "Cell* f()" in gcFunctions
+
+hazards = test.load_hazards()
+hazmap = {haz.variable: haz for haz in hazards}
+assert "cell1" not in hazmap
+assert "cell2" in hazmap
+assert "cell3" in hazmap
+assert "cell4" not in hazmap
+assert "cell5" not in hazmap
+assert "cell6" not in hazmap
+assert "<returnvalue>" in hazmap
+assert "this" in hazmap
+
+# All hazards should be in f(), loopy(), safevals(), method(), and refptr_test{1,3,4}()
+assert hazmap["cell2"].function == "Cell* f()"
+haz_functions = set(haz.function for haz in hazards)
+print(haz_functions)
+assert len(haz_functions) == 7
+
+# Check that the correct GC call is reported for each hazard. (cell3 has a
+# hazard from two different GC calls; it doesn't really matter which is
+# reported.)
+assert hazmap["cell2"].GCFunction == "void halfSuppressedFunction()"
+assert hazmap["cell3"].GCFunction in (
+ "void halfSuppressedFunction()",
+ "void unsuppressedFunction()",
+)
+returnval_hazards = set(
+ haz.function for haz in hazards if haz.variable == "<returnvalue>"
+)
+assert returnval_hazards == set(
+ [
+ "Cell* f()",
+ "Cell* refptr_test1()",
+ "Cell* refptr_test3()",
+ "Cell* refptr_test4()",
+ ]
+)
+
+assert "container1" in hazmap
+assert "container2" not in hazmap
+
+# Type names are handy to have in the report.
+assert hazmap["cell2"].type == "Cell*"
+assert hazmap["<returnvalue>"].type == "Cell*"
+assert hazmap["this"].type == "Subcell*"
+
+# loopy hazards. See comments in source.
+assert "haz1" not in hazmap
+assert "haz2" not in hazmap
+assert "haz3" in hazmap
+assert "haz4" in hazmap
+assert "haz5" in hazmap
+assert "haz6" not in hazmap
+assert "haz7" not in hazmap
+assert "haz8" in hazmap
+
+# safevals hazards. See comments in source.
+assert "unsafe1" in hazmap
+assert "safe2" not in hazmap
+assert "unsafe3" in hazmap
+assert "unsafe3b" in hazmap
+assert "unsafe4" in hazmap
+assert "safe5" not in hazmap
+assert "safe6" not in hazmap
+assert "unsafe7" in hazmap
+assert "safe8" not in hazmap
+assert "safe9" not in hazmap
+assert "safe10" not in hazmap
+assert "safe11" not in hazmap
+assert "safe12" not in hazmap
+assert "unsafe13" in hazmap
+assert "unsafe14" in hazmap
+assert "unsafe15" in hazmap
+assert "safe16" not in hazmap
+assert "safe17" not in hazmap
+assert "safe18" not in hazmap
+
+# method hazard.
+
+byfunc = defaultdict(lambda: defaultdict(dict))
+for haz in hazards:
+ byfunc[haz.function][haz.variable] = haz
+
+methhaz = byfunc["int32 Subcell::method()"]
+assert "this" in methhaz
+assert methhaz["this"].type == "Subcell*"