зеркало из https://github.com/mozilla/gecko-dev.git
Bug 988041 - Enable LeakSanitizer for Mochitests. r=jmaher
This commit is contained in:
Родитель
c13f6d39c7
Коммит
a7fbd0d3c2
|
@ -474,7 +474,7 @@ class Automation(object):
|
|||
os.unlink(pwfilePath)
|
||||
return 0
|
||||
|
||||
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None):
|
||||
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None):
|
||||
if xrePath == None:
|
||||
xrePath = self.DIST_BIN
|
||||
if env == None:
|
||||
|
|
|
@ -448,7 +448,7 @@ def systemMemory():
|
|||
"""
|
||||
return int(os.popen("free").readlines()[1].split()[1])
|
||||
|
||||
def environment(xrePath, env=None, crashreporter=True, debugger=False, dmdPath=None):
|
||||
def environment(xrePath, env=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None):
|
||||
"""populate OS environment variables for mochitest"""
|
||||
|
||||
env = os.environ.copy() if env is None else env
|
||||
|
@ -521,11 +521,30 @@ def environment(xrePath, env=None, crashreporter=True, debugger=False, dmdPath=N
|
|||
# the amount of resources required to do the tests. Standard options
|
||||
# will otherwise lead to OOM conditions on the current test slaves.
|
||||
message = "INFO | runtests.py | ASan running in %s configuration"
|
||||
asanOptions = []
|
||||
if totalMemory <= 1024 * 1024 * 4:
|
||||
message = message % 'low-memory'
|
||||
env["ASAN_OPTIONS"] = "quarantine_size=50331648:malloc_context_size=5"
|
||||
asanOptions = ['quarantine_size=50331648', 'malloc_context_size=5']
|
||||
else:
|
||||
message = message % 'default memory'
|
||||
|
||||
if lsanPath:
|
||||
log.info("LSan enabled.")
|
||||
asanOptions.append('detect_leaks=1')
|
||||
lsanOptions = ["exitcode=0"]
|
||||
suppressionsFile = os.path.join(lsanPath, 'lsan_suppressions.txt')
|
||||
if os.path.exists(suppressionsFile):
|
||||
log.info("LSan using suppression file " + suppressionsFile)
|
||||
lsanOptions.append("suppressions=" + suppressionsFile)
|
||||
else:
|
||||
log.info("WARNING | runtests.py | LSan suppressions file does not exist! " + suppressionsFile)
|
||||
env["LSAN_OPTIONS"] = ':'.join(lsanOptions)
|
||||
# Run shutdown GCs and CCs to avoid spurious leaks.
|
||||
env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1'
|
||||
|
||||
if len(asanOptions):
|
||||
env['ASAN_OPTIONS'] = ':'.join(asanOptions)
|
||||
|
||||
except OSError,err:
|
||||
log.info("Failed determine available memory, disabling ASan low-memory configuration: %s", err.strerror)
|
||||
except:
|
||||
|
@ -678,3 +697,98 @@ class ShutdownLeaks(object):
|
|||
counted.add(url)
|
||||
|
||||
return sorted(counts, key=itemgetter(1), reverse=True)
|
||||
|
||||
|
||||
class LSANLeaks(object):
|
||||
"""
|
||||
Parses the log when running an LSAN build, looking for interesting stack frames
|
||||
in allocation stacks, and prints out reports.
|
||||
"""
|
||||
|
||||
def __init__(self, logger):
|
||||
self.logger = logger
|
||||
self.inReport = False
|
||||
self.foundFrames = set([])
|
||||
self.recordMoreFrames = None
|
||||
self.currStack = None
|
||||
self.maxNumRecordedFrames = 4
|
||||
|
||||
# Don't various allocation-related stack frames, as they do not help much to
|
||||
# distinguish different leaks.
|
||||
unescapedSkipList = [
|
||||
"malloc", "js_malloc", "malloc_", "__interceptor_malloc", "moz_malloc", "moz_xmalloc",
|
||||
"calloc", "js_calloc", "calloc_", "__interceptor_calloc", "moz_calloc", "moz_xcalloc",
|
||||
"realloc","js_realloc", "realloc_", "__interceptor_realloc", "moz_realloc", "moz_xrealloc",
|
||||
"new",
|
||||
"js::MallocProvider",
|
||||
]
|
||||
self.skipListRegExp = re.compile("^" + "|".join([re.escape(f) for f in unescapedSkipList]) + "$")
|
||||
|
||||
self.startRegExp = re.compile("==\d+==ERROR: LeakSanitizer: detected memory leaks")
|
||||
self.stackFrameRegExp = re.compile(" #\d+ 0x[0-9a-f]+ in ([^(</]+)")
|
||||
self.sysLibStackFrameRegExp = re.compile(" #\d+ 0x[0-9a-f]+ \(([^+]+)\+0x[0-9a-f]+\)")
|
||||
|
||||
|
||||
def log(self, line):
|
||||
if re.match(self.startRegExp, line):
|
||||
self.inReport = True
|
||||
return
|
||||
|
||||
if not self.inReport:
|
||||
return
|
||||
|
||||
if line.startswith("Direct leak"):
|
||||
self._finishStack()
|
||||
self.recordMoreFrames = True
|
||||
self.currStack = []
|
||||
return
|
||||
|
||||
if line.startswith("Indirect leak"):
|
||||
self._finishStack()
|
||||
# Only report direct leaks, in the hope that they are less flaky.
|
||||
self.recordMoreFrames = False
|
||||
return
|
||||
|
||||
if line.startswith("SUMMARY: AddressSanitizer"):
|
||||
self._finishStack()
|
||||
self.inReport = False
|
||||
return
|
||||
|
||||
if not self.recordMoreFrames:
|
||||
return
|
||||
|
||||
stackFrame = re.match(self.stackFrameRegExp, line)
|
||||
if stackFrame:
|
||||
# Split the frame to remove any return types.
|
||||
frame = stackFrame.group(1).split()[-1]
|
||||
if not re.match(self.skipListRegExp, frame):
|
||||
self._recordFrame(frame)
|
||||
return
|
||||
|
||||
sysLibStackFrame = re.match(self.sysLibStackFrameRegExp, line)
|
||||
if sysLibStackFrame:
|
||||
# System library stack frames will never match the skip list,
|
||||
# so don't bother checking if they do.
|
||||
self._recordFrame(sysLibStackFrame.group(1))
|
||||
|
||||
# If we don't match either of these, just ignore the frame.
|
||||
# We'll end up with "unknown stack" if everything is ignored.
|
||||
|
||||
def process(self):
|
||||
for f in self.foundFrames:
|
||||
self.logger("TEST-UNEXPECTED-FAIL | LeakSanitizer | leak at " + f)
|
||||
|
||||
def _finishStack(self):
|
||||
if self.recordMoreFrames and len(self.currStack) == 0:
|
||||
self.currStack = ["unknown stack"]
|
||||
if self.currStack:
|
||||
self.foundFrames.add(", ".join(self.currStack))
|
||||
self.currStack = None
|
||||
self.recordMoreFrames = False
|
||||
self.numRecordedFrames = 0
|
||||
|
||||
def _recordFrame(self, frame):
|
||||
self.currStack.append(frame)
|
||||
self.numRecordedFrames += 1
|
||||
if self.numRecordedFrames >= self.maxNumRecordedFrames:
|
||||
self.recordMoreFrames = False
|
||||
|
|
|
@ -48,7 +48,7 @@ class RemoteAutomation(Automation):
|
|||
self._remoteLog = logfile
|
||||
|
||||
# Set up what we need for the remote environment
|
||||
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None):
|
||||
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None):
|
||||
# Because we are running remote, we don't want to mimic the local env
|
||||
# so no copying of os.environ
|
||||
if env is None:
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
### !!! Please do not add suppressions for new leaks in Gecko code, unless they are intentional !!!
|
||||
|
||||
###
|
||||
### Some of these leak in every test run.
|
||||
###
|
||||
|
||||
# LSan runs with a shallow stack depth and no debug symbols, so some small intentional
|
||||
# leaks in system libraries show up with this. You do not want this enabled
|
||||
# when running locally with a deep stack, as it can catch too much.
|
||||
leak:libc.so
|
||||
|
||||
# nsComponentManagerImpl intentionally leaks factory entries, and probably some other stuff.
|
||||
leak:nsComponentManagerImpl
|
||||
# These two variants are needed when fast unwind is disabled and stack depth is limited.
|
||||
leak:mozJSComponentLoader::LoadModule
|
||||
leak:nsNativeModuleLoader::LoadModule
|
||||
|
||||
# Bug 980109 - Freeing this properly on shutdown is hard.
|
||||
leak:profiler_init
|
||||
|
||||
# Bug 981220 - Pixman fails to free TLS memory.
|
||||
leak:pixman_implementation_lookup_composite
|
||||
|
||||
# Bug 987918 - Font shutdown leaks when CLEANUP_MEMORY is not enabled.
|
||||
leak:libfontconfig.so
|
||||
leak:GI___strdup
|
||||
# The symbol is really __GI___strdup, but if you have the leading _, it doesn't suppress it.
|
||||
|
||||
|
||||
###
|
||||
### Many leaks only affect some test suites. The suite annotations are not checked.
|
||||
###
|
||||
|
||||
# Bug 800200 - JSD1 is leaking, but it is about to be removed, so ignore it. m4
|
||||
leak:jsd_CreateLock
|
||||
leak:jsdScript::GetExecutableLines
|
||||
leak:jsdService::ActivateDebugger
|
||||
|
||||
# Bug 979928 - WebRTC is leaky. m2, m3
|
||||
leak:/media/mtransport/
|
||||
leak:/media/webrtc/signaling/
|
||||
|
||||
# Bug 981195 - Small leak in the parser. m4
|
||||
leak:TypeCompartment::fixObjectType
|
||||
|
||||
# Bug 982111 - WebM is leaking. m1
|
||||
leak:nestegg_read_packet
|
||||
|
||||
# Bug 987385 - Various plugin leaks. m3
|
||||
leak:mozilla::plugins::PPluginInstanceParent::CallNPP_HandleEvent
|
||||
leak:mozilla::plugins::PPluginModuleParent::OnCallReceived
|
||||
|
||||
# Bug 987925 - Small leak under PK11_ChangePW. m5
|
||||
leak:sec_asn1e_allocate_item
|
||||
leak:PORT_Strdup_Util
|
||||
|
||||
# Bug 1021302 - Leak of fds allocated in nsSocketTransport::BuildSocket(). m1, m5, dt, oth
|
||||
leak:nsSocketTransport::BuildSocket
|
||||
leak:nsServerSocket::OnSocketReady
|
||||
|
||||
# Bug 1021350 - Small leak under event_base_once. m1, m4, bc3
|
||||
leak:event_base_once
|
||||
|
||||
# Bug 1021854 - nsFileStreamBase::DoOpen() leaking fds. bc1, oth
|
||||
leak:nsLocalFile::OpenNSPRFileDesc
|
||||
|
||||
# Bug 1021932 - AllocateArrayBufferContents leaks. m1
|
||||
# Bug 1023583 - Leak of array buffer data involving web audio and XHR::OnStopRequest(). m1
|
||||
# Bug 1023585 - Leak of array buffer in JSStructuredCloneWriter::transferOwnership(). m1
|
||||
leak:AllocateArrayBufferContents
|
||||
|
||||
# Bug 1022010 - Small leak under _render_glyph_outline. bc1
|
||||
leak:_render_glyph_outline
|
||||
|
||||
# Bug 1022042 - compareVariants in the test plugin leaks identifiers. oth
|
||||
leak:compareVariants(_NPP*,
|
||||
|
||||
# Bug 1022954 - ScriptSource leaks sourceMapURL_ sometimes. dt
|
||||
leak:ScriptSource::setSourceMapURL
|
||||
|
||||
# Bug 1023548 - Small leak under SECITEM_AllocItem_Util. bc1, bc3
|
||||
leak:SECITEM_AllocItem_Util
|
||||
|
||||
# This is a one-time leak, so it is probably okay to ignore. bc1, oth
|
||||
leak:GlobalPrinters::InitializeGlobalPrinters
|
||||
leak:nsPSPrinterList::GetPrinterList
|
||||
|
||||
|
||||
###
|
||||
### Leaks with system libraries in their stacks. These show up across a number of tests.
|
||||
### Better symbols and disabling fast stackwalking may help diagnose these.
|
||||
###
|
||||
|
||||
leak:libcairo.so
|
||||
leak:libdricore.so
|
||||
leak:libGL.so
|
||||
leak:libglib-2.0.so
|
||||
leak:libp11-kit.so
|
||||
leak:libpulse.so
|
||||
leak:libpulsecommon-1.1.so
|
||||
leak:libresolv.so
|
||||
leak:libstdc++.so
|
||||
leak:libXrandr.so
|
||||
leak:pthread_setspecific_internal
|
||||
leak:swrast_dri.so
|
|
@ -47,7 +47,8 @@ SERV_FILES = \
|
|||
android23.json \
|
||||
gl.json \
|
||||
b2g_start_script.js \
|
||||
$(NULL)
|
||||
$(topsrcdir)/build/sanitizers/lsan_suppressions.txt \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(MOZ_BUILD_APP),mobile/android)
|
||||
SERV_FILES += \
|
||||
|
|
|
@ -30,7 +30,7 @@ import traceback
|
|||
import urllib2
|
||||
import zipfile
|
||||
|
||||
from automationutils import environment, getDebuggerInfo, isURL, KeyValueParseError, parseKeyValue, processLeakLog, dumpScreen, ShutdownLeaks, printstatus
|
||||
from automationutils import environment, getDebuggerInfo, isURL, KeyValueParseError, parseKeyValue, processLeakLog, dumpScreen, ShutdownLeaks, printstatus, LSANLeaks
|
||||
from datetime import datetime
|
||||
from manifestparser import TestManifest
|
||||
from mochitest_options import MochitestOptions
|
||||
|
@ -1028,8 +1028,13 @@ class Mochitest(MochitestUtilsMixin):
|
|||
|
||||
def buildBrowserEnv(self, options, debugger=False):
|
||||
"""build the environment variables for the specific test and operating system"""
|
||||
if mozinfo.info["asan"]:
|
||||
lsanPath = SCRIPT_DIR
|
||||
else:
|
||||
lsanPath = None
|
||||
|
||||
browserEnv = self.environment(xrePath=options.xrePath, debugger=debugger,
|
||||
dmdPath=options.dmdPath)
|
||||
dmdPath=options.dmdPath, lsanPath=lsanPath)
|
||||
|
||||
# These variables are necessary for correct application startup; change
|
||||
# via the commandline at your own risk.
|
||||
|
@ -1237,6 +1242,11 @@ class Mochitest(MochitestUtilsMixin):
|
|||
else:
|
||||
shutdownLeaks = None
|
||||
|
||||
if mozinfo.info["asan"] and (mozinfo.isLinux or mozinfo.isMac):
|
||||
lsanLeaks = LSANLeaks(log.info)
|
||||
else:
|
||||
lsanLeaks = None
|
||||
|
||||
# create an instance to process the output
|
||||
outputHandler = self.OutputHandler(harness=self,
|
||||
utilityPath=utilityPath,
|
||||
|
@ -1244,6 +1254,7 @@ class Mochitest(MochitestUtilsMixin):
|
|||
dump_screen_on_timeout=not debuggerInfo,
|
||||
dump_screen_on_fail=screenshotOnFail,
|
||||
shutdownLeaks=shutdownLeaks,
|
||||
lsanLeaks=lsanLeaks,
|
||||
)
|
||||
|
||||
def timeoutHandler():
|
||||
|
@ -1518,7 +1529,7 @@ class Mochitest(MochitestUtilsMixin):
|
|||
|
||||
class OutputHandler(object):
|
||||
"""line output handler for mozrunner"""
|
||||
def __init__(self, harness, utilityPath, symbolsPath=None, dump_screen_on_timeout=True, dump_screen_on_fail=False, shutdownLeaks=None):
|
||||
def __init__(self, harness, utilityPath, symbolsPath=None, dump_screen_on_timeout=True, dump_screen_on_fail=False, shutdownLeaks=None, lsanLeaks=None):
|
||||
"""
|
||||
harness -- harness instance
|
||||
dump_screen_on_timeout -- whether to dump the screen on timeout
|
||||
|
@ -1529,6 +1540,7 @@ class Mochitest(MochitestUtilsMixin):
|
|||
self.dump_screen_on_timeout = dump_screen_on_timeout
|
||||
self.dump_screen_on_fail = dump_screen_on_fail
|
||||
self.shutdownLeaks = shutdownLeaks
|
||||
self.lsanLeaks = lsanLeaks
|
||||
|
||||
# perl binary to use
|
||||
self.perl = which('perl')
|
||||
|
@ -1556,6 +1568,7 @@ class Mochitest(MochitestUtilsMixin):
|
|||
self.dumpScreenOnFail,
|
||||
self.metro_subprocess_id,
|
||||
self.trackShutdownLeaks,
|
||||
self.trackLSANLeaks,
|
||||
self.log,
|
||||
self.countline,
|
||||
]
|
||||
|
@ -1607,6 +1620,9 @@ class Mochitest(MochitestUtilsMixin):
|
|||
if self.shutdownLeaks:
|
||||
self.shutdownLeaks.process()
|
||||
|
||||
if self.lsanLeaks:
|
||||
self.lsanLeaks.process()
|
||||
|
||||
|
||||
# output line handlers:
|
||||
# these take a line and return a line
|
||||
|
@ -1664,6 +1680,11 @@ class Mochitest(MochitestUtilsMixin):
|
|||
self.shutdownLeaks.log(line)
|
||||
return line
|
||||
|
||||
def trackLSANLeaks(self, line):
|
||||
if self.lsanLeaks:
|
||||
self.lsanLeaks.log(line)
|
||||
return line
|
||||
|
||||
def log(self, line):
|
||||
log.info(line)
|
||||
return line
|
||||
|
|
Загрузка…
Ссылка в новой задаче