Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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();
}