Bug 1500247 - Add a mechanism to embed known hazards into test files and verify that they are caught, r=jonco

Use it to verify that MOZ_INHERIT_ATTRIBUTE_FROM_TEMPLATE_PARAM works.

--HG--
extra : rebase_source : cb0d6d901d723274053988cdf70fedd92504e40e
This commit is contained in:
Steve Fink 2018-12-28 21:13:32 -08:00
Родитель 581394fcbf
Коммит 1150832058
6 изменённых файлов: 111 добавлений и 10 удалений

Просмотреть файл

@ -11,6 +11,8 @@
// and functions.
#ifdef XGILL_PLUGIN
# define JS_EXPECT_HAZARDS __attribute__((annotate("Expect Hazards")))
// Mark a type as being a GC thing (eg js::gc::Cell has this annotation).
# define JS_HAZ_GC_THING __attribute__((annotate("GC Thing")))
@ -63,6 +65,7 @@
#else
# define JS_EXPECT_HAZARDS
# define JS_HAZ_GC_THING
# define JS_HAZ_GC_POINTER
# define JS_HAZ_ROOTED

Просмотреть файл

@ -790,6 +790,21 @@ function processBodies(functionName)
if (!("DefineVariable" in functionBodies[0]))
return;
var suppressed = Boolean(limitedFunctions[mangled(functionName)] & LIMIT_CANNOT_GC);
// Look for the JS_EXPECT_HAZARDS annotation, and output a different
// message in that case that won't be counted as a hazard.
var annotations = new Set();
for (const variable of functionBodies[0].DefineVariable) {
if (variable.Variable.Kind == "Func" && variable.Variable.Name[0] == functionName) {
for (const { Name: [tag, value] } of (variable.Type.Annotation || [])) {
if (tag == 'annotate')
annotations.add(value);
}
}
}
var missingExpectedHazard = annotations.has("Expect Hazards");
for (var variable of functionBodies[0].DefineVariable) {
var name;
if (variable.Variable.Kind == "This")
@ -817,11 +832,20 @@ function processBodies(functionName)
var result = variableLiveAcrossGC(suppressed, variable.Variable);
if (result) {
var lineText = findLocation(result.gcInfo.body, result.gcInfo.ppoint);
print("\nFunction '" + functionName + "'" +
" has unrooted '" + name + "'" +
" of type '" + typeDesc(variable.Type) + "'" +
" live across GC call " + result.gcInfo.name +
" at " + lineText);
if (annotations.has('Expect Hazards')) {
print("\nThis is expected, but '" + functionName + "'" +
" has unrooted '" + name + "'" +
" of type '" + typeDesc(variable.Type) + "'" +
" live across GC call " + result.gcInfo.name +
" at " + lineText);
missingExpectedHazard = false;
} else {
print("\nFunction '" + functionName + "'" +
" has unrooted '" + name + "'" +
" of type '" + typeDesc(variable.Type) + "'" +
" live across GC call " + result.gcInfo.name +
" at " + lineText);
}
printEntryTrace(functionName, result);
}
result = unsafeVariableAddressTaken(suppressed, variable.Variable);
@ -834,6 +858,20 @@ function processBodies(functionName)
}
}
}
if (missingExpectedHazard) {
const {
Location: [
{ CacheString: startfile, Line: startline },
{ CacheString: endfile, Line: endline }
]
} = functionBodies[0];
const loc = (startfile == endfile) ? `${startfile}:${startline}-${endline}`
: `${startfile}:${startline}`;
print("\nFunction '" + functionName + "' expected hazard(s) but none were found at " + loc);
}
}
if (batch == 1)

Просмотреть файл

@ -17,6 +17,8 @@ args = parser.parse_args()
num_hazards = 0
num_refs = 0
num_missing = 0
try:
with open(args.rootingHazards) as rootingHazards, \
open(args.hazards, 'w') as hazards, \
@ -32,6 +34,9 @@ try:
# ordering of the hazards
hazardOrder = []
# Map from a hazardous GC function to the filename containing it.
fileOfFunction = {}
for line in rootingHazards:
m = re.match(r'^Time: (.*)', line)
mm = re.match(r'^Run on:', line)
@ -53,7 +58,7 @@ try:
continue
m = re.match(
r"^Function.*has unrooted.*of type.*live across GC call ('?)(.*?)('?) at \S+:\d+$", line) # NOQA: E501
r"^Function.*has unrooted.*of type.*live across GC call ('?)(.*?)('?) at (\S+):\d+$", line) # NOQA: E501
if m:
# Function names are surrounded by single quotes. Field calls
# are unquoted.
@ -62,6 +67,13 @@ try:
hazardOrder.append((current_gcFunction,
len(hazardousGCFunctions[current_gcFunction]) - 1))
num_hazards += 1
fileOfFunction[current_gcFunction] = m.group(4)
continue
m = re.match(r'Function.*expected hazard.*but none were found', line)
if m:
num_missing += 1
print(line + "\n", file=hazards)
continue
if current_gcFunction:
@ -104,4 +116,4 @@ except IOError as e:
print("Wrote %s" % args.hazards)
print("Wrote %s" % args.extra)
print("Wrote %s" % args.refs)
print("Found %d hazards and %d unsafe references" % (num_hazards, num_refs))
print("Found %d hazards %d unsafe references %d missing" % (num_hazards, num_refs, num_missing))

Просмотреть файл

@ -164,6 +164,45 @@ BEGIN_TEST(testGCRootedHashMap) {
}
END_TEST(testGCRootedHashMap)
// Repeat of the test above, but without rooting. This is a rooting hazard. The
// JS_EXPECT_HAZARDS annotation will cause the hazard taskcluster job to fail
// if the hazard below is *not* detected.
BEGIN_TEST_WITH_ATTRIBUTES(testUnrootedGCHashMap,JS_EXPECT_HAZARDS) {
MyHashMap map(cx, 15);
for (size_t i = 0; i < 10; ++i) {
RootedObject obj(cx, JS_NewObject(cx, nullptr));
RootedValue val(cx, UndefinedValue());
// Construct a unique property name to ensure that the object creates a
// new shape.
char buffer[2];
buffer[0] = 'a' + i;
buffer[1] = '\0';
CHECK(JS_SetProperty(cx, obj, buffer, val));
CHECK(map.putNew(obj->as<NativeObject>().lastProperty(), obj));
}
JS_GC(cx);
// Access map to keep it live across the GC.
CHECK(map.count() == 10);
return true;
}
END_TEST(testUnrootedGCHashMap)
BEGIN_TEST(testSafelyUnrootedGCHashMap) {
// This is not rooted, but it doesn't use GC pointers as keys or values so
// it's ok.
js::GCHashMap<uint64_t, uint64_t> map(cx, 15);
JS_GC(cx);
CHECK(map.putNew(12, 13));
return true;
}
END_TEST(testSafelyUnrootedGCHashMap)
static bool FillMyHashMap(JSContext* cx, MutableHandle<MyHashMap> map) {
for (size_t i = 0; i < 10; ++i) {
RootedObject obj(cx, JS_NewObject(cx, nullptr));

Просмотреть файл

@ -353,11 +353,13 @@ class JSAPITest {
virtual JSObject* createGlobal(JSPrincipals* principals = nullptr);
};
#define BEGIN_TEST(testname) \
#define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs) \
class cls_##testname : public JSAPITest { \
public: \
virtual const char* name() override { return #testname; } \
virtual bool run(JS::HandleObject global) override
virtual bool run(JS::HandleObject global) override attrs
#define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, )
#define END_TEST(testname) \
} \

Просмотреть файл

@ -159,15 +159,16 @@ function check_hazards () {
NUM_UNNECESSARY=$(grep -c '^Function.* has unnecessary root' "$1"/unnecessary.txt)
NUM_DROPPED=$(grep -c '^Dropped CFG' "$1"/build_xgill.log)
NUM_WRITE_HAZARDS=$(perl -lne 'print $1 if m!found (\d+)/\d+ allowed errors!' "$1"/heapWriteHazards.txt)
NUM_MISSING=$(grep -c '^Function.*expected hazard.*but none were found' "$1"/rootingHazards.txt)
set +x
echo "TinderboxPrint: rooting hazards<br/>$NUM_HAZARDS"
echo "TinderboxPrint: (unsafe references to unrooted GC pointers)<br/>$NUM_UNSAFE"
echo "TinderboxPrint: (unnecessary roots)<br/>$NUM_UNNECESSARY"
echo "TinderboxPrint: missing expected hazards<br/>$NUM_MISSING"
echo "TinderboxPrint: heap write hazards<br/>$NUM_WRITE_HAZARDS"
# Display errors in a way that will get picked up by the taskcluster scraper.
perl -le 'print "TEST-UNEXPECTED-FAIL | hazards | $ENV{NUM_HAZARDS} rooting hazards" if $ENV{NUM_HAZARDS}'
perl -lne 'print "TEST-UNEXPECTED-FAIL | hazards | $1 $2" if /^Function.* has (unrooted .*live across GC call).* (at .*)$/' "$1"/hazards.txt
exit_status=0
@ -178,6 +179,12 @@ function check_hazards () {
exit_status=1
fi
if [ $NUM_MISSING -gt 0 ]; then
echo "TEST-UNEXPECTED-FAIL | hazards | $NUM_MISSING expected hazards went undetected" >&2
echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_rooting_hazards_failure'>static rooting hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
exit_status=1
fi
NUM_ALLOWED_WRITE_HAZARDS=0
if [ $NUM_WRITE_HAZARDS -gt $NUM_ALLOWED_WRITE_HAZARDS ]; then
echo "TEST-UNEXPECTED-FAIL | heap-write-hazards | $NUM_WRITE_HAZARDS heap write hazards detected out of $NUM_ALLOWED_WRITE_HAZARDS allowed" >&2