Bug 988041 - Enable LeakSanitizer for Mochitests. r=jmaher

This commit is contained in:
Andrew McCreight 2014-06-20 09:08:30 -07:00
Родитель c13f6d39c7
Коммит a7fbd0d3c2
6 изменённых файлов: 249 добавлений и 8 удалений

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

@ -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