Bug 1480198 - Check for allowed patterns deeper in LSAN stack. r=jgraham
authorAndreas Farre <farre@mozilla.com>
Fri, 12 Oct 2018 11:43:38 +0000
changeset 496590 a0702430e839a7e0b1ea1dc78927d38ede1a007a
parent 496589 d1adf0333d63a102d7e91c887586da7219c0d650
child 496591 3f53bdf6763dc6da681b373cb10a87e07c381f5f
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgraham
bugs1480198
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1480198 - Check for allowed patterns deeper in LSAN stack. r=jgraham Add the property lsan-max-stack-depth to enable configuring how many stack frames we allow LSANLeaks to record. Differential Revision: https://phabricator.services.mozilla.com/D8192
testing/mozbase/mozleak/mozleak/lsan.py
testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
testing/web-platform/tests/tools/wptrunner/wptrunner/tests/test_wpttest.py
testing/web-platform/tests/tools/wptrunner/wptrunner/wpttest.py
--- a/testing/mozbase/mozleak/mozleak/lsan.py
+++ b/testing/mozbase/mozleak/mozleak/lsan.py
@@ -8,25 +8,25 @@ import re
 
 class LSANLeaks(object):
 
     """
     Parses the log when running an LSAN build, looking for interesting stack frames
     in allocation stacks
     """
 
-    def __init__(self, logger, scope=None, allowed=None):
+    def __init__(self, logger, scope=None, allowed=None, maxNumRecordedFrames=None):
         self.logger = logger
         self.inReport = False
         self.fatalError = False
         self.symbolizerError = False
         self.foundFrames = set()
         self.recordMoreFrames = None
         self.currStack = None
-        self.maxNumRecordedFrames = 4
+        self.maxNumRecordedFrames = maxNumRecordedFrames if maxNumRecordedFrames else 4
         self.summaryData = None
         self.scope = scope
         self.allowedMatch = None
         self.sawError = False
 
         # Don't various allocation-related stack frames, as they do not help much to
         # distinguish different leaks.
         unescapedSkipList = [
@@ -131,16 +131,17 @@ class LSANLeaks(object):
                               "This will cause leaks that "
                               "should be ignored to instead be reported as an error")
             failures += 1
 
         if self.foundFrames:
             self.logger.info("LeakSanitizer | To show the "
                              "addresses of leaked objects add report_objects=1 to LSAN_OPTIONS\n"
                              "This can be done in testing/mozbase/mozrunner/mozrunner/utils.py")
+            self.logger.info("Allowed depth was %d" % self.maxNumRecordedFrames)
 
             for frames, allowed in self.foundFrames:
                 self.logger.lsan_leak(frames, scope=self.scope, allowed_match=allowed)
                 if not allowed:
                     failures += 1
 
         if self.sawError and not (self.summaryData or
                                   self.foundFrames or
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -184,41 +184,44 @@ class FirefoxBrowser(Browser):
         else:
             self.stack_fixer = None
 
         if timeout_multiplier:
             self.init_timeout = self.init_timeout * timeout_multiplier
 
         self.asan = asan
         self.lsan_allowed = None
+        self.lsan_max_stack_depth = None
         self.leak_check = leak_check
         self.leak_report_file = None
         self.lsan_handler = None
         self.stylo_threads = stylo_threads
         self.chaos_mode_flags = chaos_mode_flags
         self.headless = headless
 
     def settings(self, test):
         self.lsan_allowed = test.lsan_allowed
+        self.lsan_max_stack_depth = test.lsan_max_stack_depth
         return {"check_leaks": self.leak_check and not test.leaks,
                 "lsan_allowed": test.lsan_allowed}
 
     def start(self, group_metadata=None, **kwargs):
         if group_metadata is None:
             group_metadata = {}
 
         if self.marionette_port is None:
             self.marionette_port = get_free_port(2828, exclude=self.used_ports)
             self.used_ports.add(self.marionette_port)
 
         if self.asan:
             print "Setting up LSAN"
             self.lsan_handler = mozleak.LSANLeaks(self.logger,
                                                   scope=group_metadata.get("scope", "/"),
-                                                  allowed=self.lsan_allowed)
+                                                  allowed=self.lsan_allowed,
+                                                  maxNumRecordedFrames=self.lsan_max_stack_depth)
 
         env = test_environment(xrePath=os.path.dirname(self.binary),
                                debugger=self.debug_info is not None,
                                log=self.logger,
                                lsanPath=self.prefs_root)
 
         env["STYLO_THREADS"] = str(self.stylo_threads)
         if self.chaos_mode_flags is not None:
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
@@ -151,16 +151,20 @@ class ExpectedManifest(ManifestItem):
     @property
     def prefs(self):
         return prefs(self)
 
     @property
     def lsan_allowed(self):
         return lsan_allowed(self)
 
+    @property
+    def lsan_max_stack_depth(self):
+        return int_prop("lsan-max-stack-depth", self)
+
 
 class DirectoryManifest(ManifestItem):
     @property
     def disabled(self):
         return bool_prop("disabled", self)
 
     @property
     def restart_after(self):
@@ -185,16 +189,19 @@ class DirectoryManifest(ManifestItem):
     @property
     def prefs(self):
         return prefs(self)
 
     @property
     def lsan_allowed(self):
         return lsan_allowed(self)
 
+    @property
+    def lsan_max_stack_depth(self):
+        return int_prop("lsan-max-stack-depth", self)
 
 class TestNode(ManifestItem):
     def __init__(self, name):
         """Tree node associated with a particular test in a manifest
 
         :param name: name of the test"""
         assert name is not None
         ManifestItem.__init__(self, name)
@@ -246,16 +253,20 @@ class TestNode(ManifestItem):
     @property
     def prefs(self):
         return prefs(self)
 
     @property
     def lsan_allowed(self):
         return lsan_allowed(self)
 
+    @property
+    def lsan_max_stack_depth(self):
+        return int_prop("lsan-max-stack-depth", self)
+
     def append(self, node):
         """Add a subtest to the current test
 
         :param node: AST Node associated with the subtest"""
         child = ManifestItem.append(self, node)
         self.subtests[child.name] = child
 
     def get_subtest(self, name):
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/tests/test_wpttest.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/tests/test_wpttest.py
@@ -13,31 +13,40 @@ prefs: [a:b]
 
 dir_ini_1 = """\
 prefs: [@Reset, b:c]
 max-asserts: 2
 min-asserts: 1
 tags: [b, c]
 """
 
+dir_ini_2 = """\
+lsan-max-stack-depth: 42
+"""
+
 test_0 = """\
 [0.html]
   prefs: [c:d]
   max-asserts: 3
   tags: [a, @Reset]
 """
 
 test_1 = """\
 [1.html]
   prefs:
     if os == 'win': [a:b, c:d]
   expected:
     if os == 'win': FAIL
 """
 
+test_2 = """\
+[2.html]
+  lsan-max-stack-depth: 42
+"""
+
 
 def test_metadata_inherit():
     tests = make_mock_manifest(("test", "a", 10), ("test", "a/b", 10),
                                ("test", "c", 10))
 
     inherit_metadata = [
         manifestexpected.static.compile(
             BytesIO(item),
@@ -67,8 +76,45 @@ def test_conditional():
                                                     data_cls_getter=manifestexpected.data_cls_getter,
                                                     test_path="a",
                                                     url_base="")
 
     test = tests[1][2].pop()
     test_obj = wpttest.from_manifest(test, [], test_metadata.get_test(test.id))
     assert test_obj.prefs == {"a": "b", "c": "d"}
     assert test_obj.expected() == "FAIL"
+
+def test_metadata_lsan_stack_depth():
+    tests = make_mock_manifest(("test", "a", 10), ("test", "a/b", 10))
+
+    test_metadata = manifestexpected.static.compile(BytesIO(test_2),
+                                                    {},
+                                                    data_cls_getter=manifestexpected.data_cls_getter,
+                                                    test_path="a",
+                                                    url_base="")
+
+    test = tests[2][2].pop()
+    test_obj = wpttest.from_manifest(test, [], test_metadata.get_test(test.id))
+
+    assert test_obj.lsan_max_stack_depth == 42
+
+    test = tests[1][2].pop()
+    test_obj = wpttest.from_manifest(test, [], test_metadata.get_test(test.id))
+
+    assert test_obj.lsan_max_stack_depth == None
+
+    test_metadata = manifestexpected.static.compile(BytesIO(test_0),
+                                                    {},
+                                                    data_cls_getter=manifestexpected.data_cls_getter,
+                                                    test_path="a",
+                                                    url_base="")
+
+    inherit_metadata = [
+        manifestexpected.static.compile(
+            BytesIO(dir_ini_2),
+            {},
+            data_cls_getter=lambda x,y: manifestexpected.DirectoryManifest)
+    ]
+
+    test = tests[0][2].pop()
+    test_obj = wpttest.from_manifest(test, inherit_metadata, test_metadata.get_test(test.id))
+
+    assert test_obj.lsan_max_stack_depth == 42
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wpttest.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wpttest.py
@@ -230,16 +230,24 @@ class Test(object):
         for meta in self.itermeta():
             lsan_allowed |= meta.lsan_allowed
             if atom_reset in lsan_allowed:
                 lsan_allowed.remove(atom_reset)
                 break
         return lsan_allowed
 
     @property
+    def lsan_max_stack_depth(self):
+        for meta in self.itermeta(None):
+            depth = meta.lsan_max_stack_depth
+            if depth is not None:
+                return depth
+        return None
+
+    @property
     def tags(self):
         tags = set()
         for meta in self.itermeta():
             meta_tags = meta.tags
             tags |= meta_tags
             if atom_reset in meta_tags:
                 tags.remove(atom_reset)
                 break