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 489198 a0702430e839a7e0b1ea1dc78927d38ede1a007a
parent 489197 d1adf0333d63a102d7e91c887586da7219c0d650
child 489199 3f53bdf6763dc6da681b373cb10a87e07c381f5f
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
reviewersjgraham
bugs1480198
milestone64.0a1
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