From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- js/src/devtools/rootAnalysis/t/hazards/source.cpp | 566 ++++++++++++++++++++++ js/src/devtools/rootAnalysis/t/hazards/test.py | 121 +++++ 2 files changed, 687 insertions(+) create mode 100644 js/src/devtools/rootAnalysis/t/hazards/source.cpp create mode 100644 js/src/devtools/rootAnalysis/t/hazards/test.py (limited to 'js/src/devtools/rootAnalysis/t/hazards') 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 + +#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(); +} 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..c4e9549305 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/hazards/test.py @@ -0,0 +1,121 @@ +# 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 "" in hazmap +assert "this" in hazmap + +assert hazmap["cell2"].function == "Cell* f()" + +# 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 == "" +) +assert "Cell* f()" in returnval_hazards +assert "Cell* refptr_test1()" in returnval_hazards +assert "Cell* refptr_test2()" not in returnval_hazards +assert "Cell* refptr_test3()" in returnval_hazards +assert "Cell* refptr_test4()" in returnval_hazards +assert "Cell* refptr_test5()" not in returnval_hazards +assert "Cell* refptr_test6()" in returnval_hazards +assert "Cell* refptr_test7()" in returnval_hazards +assert "Cell* refptr_test8()" in returnval_hazards +assert "Cell* refptr_test9()" not in returnval_hazards + +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[""].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 +assert "safe19" 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*" + +haz_functions = set(haz.function for haz in hazards) + +# RefPtr tests. + +haz_functions = set(haz.function for haz in hazards) +assert "Cell* refptr_test1()" in haz_functions +assert "Cell* refptr_test2()" not in haz_functions +assert "Cell* refptr_test3()" in haz_functions +assert "Cell* refptr_test4()" in haz_functions +assert "Cell* refptr_test5()" not in haz_functions +assert "Cell* refptr_test6()" in haz_functions +assert "Cell* refptr_test7()" in haz_functions +assert "Cell* refptr_test8()" in haz_functions +assert "Cell* refptr_test9()" not in haz_functions +assert "Cell* refptr_test10()" in haz_functions + +# aggr_init tests. + +assert "void aggr_init_safe()" not in haz_functions +assert "void aggr_init_unsafe()" in haz_functions -- cgit v1.2.3