зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
581394fcbf
Коммит
1150832058
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче