Bug 1316450 - Enforce that nothing new depends on the XPCOM glue. r?gps draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 10 Nov 2016 08:41:48 +0900
changeset 438740 c4a19762fdccfa9bd11b6eb29b5428f91fa02c65
parent 438739 f44dbf7e6f4c96d960a58ed77274eec619a87c8a
child 536980 94527c0908a18f571e65ac49fe98535c13cdce15
push id35799
push userbmo:mh+mozilla@glandium.org
push dateMon, 14 Nov 2016 23:28:12 +0000
reviewersgps
bugs1316450
milestone52.0a1
Bug 1316450 - Enforce that nothing new depends on the XPCOM glue. r?gps
python/mozbuild/mozbuild/frontend/data.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/test/frontend/test_emitter.py
--- a/python/mozbuild/mozbuild/frontend/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -312,24 +312,26 @@ class LinkageWrongKindError(Exception):
 
 class LinkageMultipleRustLibrariesError(Exception):
     """Error thrown when trying to link multiple Rust libraries to an object"""
 
 
 class Linkable(ContextDerived):
     """Generic context derived container object for programs and libraries"""
     __slots__ = (
+        'name',
         'cxx_link',
         'lib_defines',
         'linked_libraries',
         'linked_system_libs',
     )
 
-    def __init__(self, context):
+    def __init__(self, context, name):
         ContextDerived.__init__(self, context)
+        self.name = name
         self.cxx_link = False
         self.linked_libraries = []
         self.linked_system_libs = []
         self.lib_defines = Defines(context, {})
 
     def link_library(self, obj):
         assert isinstance(obj, BaseLibrary)
         if isinstance(obj, SharedLibrary) and obj.variant == obj.COMPONENT:
@@ -378,17 +380,17 @@ class BaseProgram(Linkable):
     DICT_ATTRS = {
         'install_target',
         'KIND',
         'program',
         'relobjdir',
     }
 
     def __init__(self, context, program, is_unit_test=False):
-        Linkable.__init__(self, context)
+        Linkable.__init__(self, context, program)
 
         bin_suffix = context.config.substs.get(self.SUFFIX_VAR, '')
         if not program.endswith(bin_suffix):
             program += bin_suffix
         self.program = program
         self.is_unit_test = is_unit_test
 
     def __repr__(self):
@@ -425,17 +427,17 @@ class BaseLibrary(Linkable):
     __slots__ = (
         'basename',
         'lib_name',
         'import_name',
         'refs',
     )
 
     def __init__(self, context, basename):
-        Linkable.__init__(self, context)
+        Linkable.__init__(self, context, basename)
 
         self.basename = self.lib_name = basename
         if self.lib_name:
             self.lib_name = '%s%s%s' % (
                 context.config.lib_prefix,
                 self.lib_name,
                 context.config.lib_suffix
             )
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -96,16 +96,76 @@ from .context import (
     Path,
     SubContext,
     TemplateContext,
 )
 
 from mozbuild.base import ExecutionSummary
 
 
+ALLOWED_XPCOM_GLUE = {
+    ('test_duplex', 'media/libcubeb/tests'),
+    ('test_resampler', 'media/libcubeb/tests'),
+    ('test_record', 'media/libcubeb/tests'),
+    ('test_tone', 'media/libcubeb/tests'),
+    ('test_utils', 'media/libcubeb/tests'),
+    ('test_audio', 'media/libcubeb/tests'),
+    ('test_latency', 'media/libcubeb/tests'),
+    ('test_sanity', 'media/libcubeb/tests'),
+    ('TestMinStringAPI', 'xpcom/tests/external'),
+    ('testcomponent', 'xpcom/tests/component'),
+    ('test656331', 'xpcom/tests/bug656331_component'),
+    ('testcompnoaslr', 'xpcom/tests/component_no_aslr'),
+    ('TestStreamConv', 'netwerk/streamconv/test'),
+    ('PropertiesTest', 'netwerk/test'),
+    ('ReadNTLM', 'netwerk/test'),
+    ('TestBlockingSocket', 'netwerk/test'),
+    ('TestDNS', 'netwerk/test'),
+    ('TestIncrementalDownload', 'netwerk/test'),
+    ('TestNamedPipeService', 'netwerk/test'),
+    ('TestOpen', 'netwerk/test'),
+    ('TestProtocols', 'netwerk/test'),
+    ('TestServ', 'netwerk/test'),
+    ('TestStreamLoader', 'netwerk/test'),
+    ('TestUpload', 'netwerk/test'),
+    ('TestURLParser', 'netwerk/test'),
+    ('urltest', 'netwerk/test'),
+    ('TestBind', 'netwerk/test'),
+    ('TestCookie', 'netwerk/test'),
+    ('TestUDPSocket', 'netwerk/test'),
+    ('xpcshell', 'js/xpconnect/shell'),
+    ('xpctest', 'js/xpconnect/tests/components/native'),
+    ('test_AsXXX_helpers', 'storage/test'),
+    ('test_async_callbacks_with_spun_event_loops', 'storage/test'),
+    ('test_asyncStatementExecution_transaction', 'storage/test'),
+    ('test_binding_params', 'storage/test'),
+    ('test_deadlock_detector', 'storage/test'),
+    ('test_file_perms', 'storage/test'),
+    ('test_mutex', 'storage/test'),
+    ('test_service_init_background_thread', 'storage/test'),
+    ('test_statement_scoper', 'storage/test'),
+    ('test_StatementCache', 'storage/test'),
+    ('test_transaction_helper', 'storage/test'),
+    ('test_true_async', 'storage/test'),
+    ('test_unlock_notify', 'storage/test'),
+    ('TestTXMgr', 'editor/txmgr/tests'),
+    ('test_IHistory', 'toolkit/components/places/tests/cpp'),
+    ('testcrasher', 'toolkit/crashreporter/test'),
+    ('TestStartupCache', 'startupcache/test'),
+    ('jsep_session_unittest', 'media/webrtc/signaling/test'),
+    ('jsep_track_unittest', 'media/webrtc/signaling/test'),
+    ('mediaconduit_unittests', 'media/webrtc/signaling/test'),
+    ('mediapipeline_unittest', 'media/webrtc/signaling/test'),
+    ('sdp_file_parser', 'media/webrtc/signaling/fuzztest'),
+    ('sdp_unittests', 'media/webrtc/signaling/test'),
+    ('signaling_unittests', 'media/webrtc/signaling/test'),
+    ('TestMailCookie', 'mailnews/base/test'),
+}
+
+
 class TreeMetadataEmitter(LoggingMixin):
     """Converts the executed mozbuild files into data structures.
 
     This is a bridge between reader.py and data.py. It takes what was read by
     reader.BuildReader and converts it into the classes defined in the data
     module.
     """
 
@@ -269,21 +329,25 @@ class TreeMetadataEmitter(LoggingMixin):
         'host': 'HOST_LIBRARY_NAME',
         'target': 'LIBRARY_NAME',
     }
 
     def _link_libraries(self, context, obj, variable):
         """Add linkage declarations to a given object."""
         assert isinstance(obj, Linkable)
 
+        use_xpcom = False
+
         for path in context.get(variable, []):
             force_static = path.startswith('static:') and obj.KIND == 'target'
             if force_static:
                 path = path[7:]
             name = mozpath.basename(path)
+            if name in ('xpcomglue', 'xpcomglue_s'):
+                use_xpcom = True
             dir = mozpath.dirname(path)
             candidates = [l for l in self._libs[name] if l.KIND == obj.KIND]
             if dir:
                 if dir.startswith('/'):
                     dir = mozpath.normpath(
                         mozpath.join(obj.topobjdir, dir[1:]))
                 else:
                     dir = mozpath.normpath(
@@ -360,16 +424,39 @@ class TreeMetadataEmitter(LoggingMixin):
                     SharedLibrary):
                 self._static_linking_shared.add(obj)
             obj.link_library(candidates[0])
 
         # Link system libraries from OS_LIBS/HOST_OS_LIBS.
         for lib in context.get(variable.replace('USE', 'OS'), []):
             obj.link_system_library(lib)
 
+        key = (obj.name, obj.relativedir)
+        substs = context.config.substs
+        extra_allowed = [
+            (substs.get('MOZ_APP_NAME'), '%s/app' % substs.get('MOZ_BUILD_APP')),
+            ('%s-bin' % substs.get('MOZ_APP_NAME'), '%s/app' % substs.get('MOZ_BUILD_APP')),
+        ]
+        if substs.get('MOZ_WIDGET_TOOLKIT') != 'android':
+            extra_allowed.append((substs.get('MOZ_CHILD_PROCESS_NAME'), 'ipc/app'))
+
+        if key in ALLOWED_XPCOM_GLUE or key in extra_allowed:
+            if not use_xpcom:
+                raise SandboxValidationError(
+                    "%s is in the exception list for XPCOM glue dependency but "
+                    "doesn't depend on the XPCOM glue. Please adjust the list "
+                    "in %s." % (obj.name, __file__), context
+                )
+        elif use_xpcom:
+            raise SandboxValidationError(
+                "%s depends on the XPCOM glue. "
+                "No new dependency on the XPCOM glue is allowed."
+                % obj.name, context
+            )
+
     @memoize
     def _get_external_library(self, dir, name, force_static):
         # Create ExternalStaticLibrary or ExternalSharedLibrary object with a
         # context more or less truthful about where the external library is.
         context = Context(config=self.config)
         context.add_source(mozpath.join(self.config.topsrcdir, dir, 'dummy'))
         if force_static:
             return ExternalStaticLibrary(context, name)
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -2,16 +2,18 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import unicode_literals
 
 import os
 import unittest
 
+from collections import defaultdict
+from buildconfig import topsrcdir
 from mozunit import main
 
 from mozbuild.frontend.context import (
     ObjDirPath,
     Path,
 )
 from mozbuild.frontend.data import (
     AndroidResDirs,
@@ -1162,11 +1164,34 @@ class TestEmitterBasic(unittest.TestCase
         """Test that a SYMBOLS_FILE in the objdir that's missing
         from GENERATED_FILES is an error.
         """
         reader = self.reader('test-symbols-file-objdir-missing-generated')
         with self.assertRaisesRegexp(SandboxValidationError,
              'Objdir file specified in SYMBOLS_FILE not in GENERATED_FILES:'):
             self.read_topsrcdir(reader)
 
+    def test_allowed_xpcom_glue(self):
+        """Test that the ALLOWED_XPCOM_GLUE list is still relevant."""
+        from mozbuild.frontend.emitter import ALLOWED_XPCOM_GLUE
+
+        allowed = defaultdict(list)
+        useless = []
+        for name, path in ALLOWED_XPCOM_GLUE:
+            allowed[path].append(name)
+
+        for path, names in allowed.iteritems():
+            if path.startswith('mailnews/'):
+                continue
+            try:
+                content = open(os.path.join(topsrcdir, path, 'moz.build')).read()
+            except:
+                content = ''
+            for name in names:
+                if "'%s'" % name in content or '"%s"' % name in content:
+                    continue
+                useless.append((name, path))
+
+        self.assertEqual(useless, [])
+
 
 if __name__ == '__main__':
     main()