author Iain Ireland <>
Fri, 11 Jan 2019 18:05:36 +0000
changeset 453523 025feea5945bffc625e9cca2e23be51ee8670315
parent 419599 da3c81f986fa63b42a2874b1791eddcdbfbe8424
child 467932 912fb96c21b3946b50e462b5ff00fda9f55050d9
permissions -rw-r--r--
Bug 1480390: Move ForOfIterClose logic inside TryNoteIter r=tcampbell This patch was intended to be a pure refactoring of existing code with no side-effects, moving the logic for handling for-of/for-of-iterclose trynotes inside TryNoteIter to avoid duplicating logic in all users of TryNoteIter. However, it turns out that there was a subtle preexisting bug in TryNoteIter that is fixed by the refactoring. Specifically, the logic to skip from a for-of-iterclose to its enclosing for-of must run before the logic to skip trynotes based on stack depth. Otherwise, the stack depth code may filter out the enclosing for-of (see the attached test case for an example) and we will skip too many try-notes. Differential Revision:

# vim: set ts=8 sts=4 et sw=4 tw=79:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at

# ----------------------------------------------------------------------------
# All heap allocations in SpiderMonkey must go through js_malloc, js_calloc,
# js_realloc, and js_free.  This is so that any embedder who uses a custom
# allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation
# go through that custom allocator.
# Therefore, the presence of any calls to "vanilla" allocation/free functions
# (e.g. malloc(), free()) is a bug.
# This script checks for the presence of such disallowed vanilla
# allocation/free function in SpiderMonkey when it's built as a library.  It
# relies on |nm| from the GNU binutils, and so only works on Linux, but one
# platform is good enough to catch almost all violations.
# This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in
# which the default definitions of js_malloc et al (in Utility.h) -- which call
# malloc et al -- are replaced with empty definitions.  This is because the
# presence and possible inlining of the default js_malloc et al can cause
# malloc/calloc/realloc/free calls show up in unpredictable places.
# Unfortunately, that configuration cannot be tested on Mozilla's standard
# testing infrastructure.  Instead, by default this script only tests that none
# of the other vanilla allocation/free functions (operator new, memalign, etc)
# are present.  If given the --aggressive flag, it will also check for
# malloc/calloc/realloc/free.
# Note:  We don't check for |operator delete| and |operator delete[]|.  These
# can be present somehow due to virtual destructors, but this is not too
# because vanilla delete/delete[] calls don't make sense without corresponding
# vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's
# mismatched alloc/free checking.
# ----------------------------------------------------------------------------

from __future__ import print_function

import argparse
import re
import subprocess
import sys
import buildconfig

# The obvious way to implement this script is to search for occurrences of
# malloc et al, succeed if none are found, and fail is some are found.
# However, "none are found" does not necessarily mean "none are present" --
# this script could be buggy.  (Or the output format of |nm| might change in
# the future.)
# So jsutil.cpp deliberately contains a (never-called) function that contains a
# single use of all the vanilla allocation/free functions.  And this script
# fails if it (a) finds uses of those functions in files other than jsutil.cpp,
# *or* (b) fails to find them in jsutil.cpp.

# Tracks overall success of the test.
has_failed = False

def fail(msg):
    print('TEST-UNEXPECTED-FAIL | |', msg)
    global has_failed
    has_failed = True

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--aggressive', action='store_true',
                        help='also check for malloc, calloc, realloc and free')
    parser.add_argument('file', type=str,
                        help='name of the file to check')
    args = parser.parse_args()

    # Run |nm|.  Options:
    # -u: show only undefined symbols
    # -C: demangle symbol names
    # -A: show an object filename for each undefined symbol
    nm = buildconfig.substs.get('NM') or 'nm'
    cmd = [nm, '-u', '-C', '-A', args.file]
    lines = subprocess.check_output(cmd, universal_newlines=True,

    # alloc_fns contains all the vanilla allocation/free functions that we look
    # for. Regexp chars are escaped appropriately.

    alloc_fns = [
        # Matches |operator new(unsigned T)|, where |T| is |int| or |long|.
        r'operator new\(unsigned',

        # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|.
        r'operator new\[\]\(unsigned',

        # These three aren't available on all Linux configurations.
        # r'posix_memalign',
        # r'aligned_alloc',
        # r'valloc',

    if args.aggressive:
        alloc_fns += [

    # This is like alloc_fns, but regexp chars are not escaped.
    alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns]

    # This regexp matches the relevant lines in the output of |nm|, which look
    # like the following.
    #   js/src/libjs_static.a:jsutil.o:              U malloc
    alloc_fns_re = r'([^:/ ]+):\s+U (' + r'|'.join(alloc_fns) + r')'

    # This tracks which allocation/free functions have been seen in jsutil.cpp.
    jsutil_cpp = set([])

    # Would it be helpful to emit detailed line number information after a failure?
    emit_line_info = False

    for line in lines:
        m =, line)
        if m is None:

        filename =

        # The stdc++compat library has an implicit call to operator new in
        # thread::_M_start_thread.
        if 'stdc++compat' in filename:

        # The memory allocator code contains calls to memalign. These are ok, so
        # we whitelist them.
        if "_memory_" in filename:

        # Ignore the fuzzing code imported from m-c
        if "Fuzzer" in filename:

        # Ignore the profiling pseudo-stack, since it needs to run even when
        # SpiderMonkey's allocator isn't initialized.
        if "ProfilingStack" in filename:

        fn =
        if filename == 'jsutil.o':
            # An allocation is present in a non-special file.  Fail!
            fail("'" + fn + "' present in " + filename)
            # Try to give more precise information about the offending code.
            emit_line_info = True

    # Check that all functions we expect are used in jsutil.cpp.  (This will
    # fail if the function-detection code breaks at any point.)
    for fn in alloc_fns_unescaped:
        if fn not in jsutil_cpp:
            fail("'" + fn + "' isn't used as expected in jsutil.cpp")

    # This should never happen, but check just in case.
    if jsutil_cpp:
        fail('unexpected allocation fns used in jsutil.cpp: ' +
             ', '.join(jsutil_cpp))

    # If we found any improper references to allocation functions, try to use
    # DWARF debug info to get more accurate line number information about the
    # bad calls. This is a lot slower than 'nm -A', and it is not always
    # precise when building with --enable-optimized.
    if emit_line_info:
        print(' Source lines with allocation calls:')
        print(' Accurate in unoptimized builds; jsutil.cpp expected.')

        # Run |nm|.  Options:
        # -u: show only undefined symbols
        # -C: demangle symbol names
        # -l: show line number information for each undefined symbol
        cmd = ['nm', '-u', '-C', '-l', args.file]
        lines = subprocess.check_output(cmd, universal_newlines=True,

        # This regexp matches the relevant lines in the output of |nm -l|,
        # which look like the following.
        #       U malloc jsutil.cpp:117
        alloc_lines_re = r'U ((' + r'|'.join(alloc_fns) + r').*)\s+(\S+:\d+)$'

        for line in lines:
            m =, line)
            if m:
            , 'called at',

    if has_failed:

    print('TEST-PASS | | ok')

if __name__ == '__main__':