Bug 1063432 - Do type coercion on sandbox function arguments. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 02 Oct 2014 09:14:08 +0900
changeset 231504 729b1324a3ce5938856a8bcf79026067ee2bd5fd
parent 231503 6986ab0f163ef2597dc2f72d4f808eeb5a598f15
child 231505 8e70d430c86a6d9653756e8c1090f610bf2dc17f
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1063432
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1063432 - Do type coercion on sandbox function arguments. r=gps
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/reader.py
python/mozbuild/mozbuild/test/frontend/test_sandbox.py
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -28,17 +28,17 @@ from mozbuild.util import (
     memoize,
     memoized_property,
     ReadOnlyKeyedDefaultDict,
     StrictOrderingOnAppendList,
     StrictOrderingOnAppendListWithFlagsFactory,
     TypedList,
 )
 import mozpack.path as mozpath
-from types import StringTypes
+from types import FunctionType
 from UserString import UserString
 
 import itertools
 
 
 class ContextDerivedValue(object):
     """Classes deriving from this one receive a special treatment in a
     Context. See Context documentation.
@@ -1262,17 +1262,17 @@ FUNCTIONS = {
         """),
 
     'error': (lambda self: self._error, (str,),
         """Issue a fatal error.
 
         If this function is called, processing is aborted immediately.
         """),
 
-    'template': (lambda self: self._template_decorator, (),
+    'template': (lambda self: self._template_decorator, (FunctionType,),
         """Decorator for template declarations.
 
         Templates are a special kind of functions that can be declared in
         mozbuild files. Uppercase variables assigned in the function scope
         are considered to be the result of the template.
 
         Contrary to traditional python functions:
            - return values from template functions are ignored,
--- a/python/mozbuild/mozbuild/frontend/reader.py
+++ b/python/mozbuild/mozbuild/frontend/reader.py
@@ -51,16 +51,17 @@ from .sandbox import (
     SandboxError,
     SandboxExecutionError,
     SandboxLoadError,
     Sandbox,
 )
 
 from .context import (
     Context,
+    ContextDerivedValue,
     FUNCTIONS,
     VARIABLES,
     DEPRECATION_HINTS,
     SPECIAL_VARIABLES,
     TemplateContext,
 )
 
 if sys.version_info.major == 2:
@@ -132,17 +133,17 @@ class MozbuildSandbox(Sandbox):
         self.exports = set(exports.keys())
         context.update(exports)
         self.templates = self.metadata.setdefault('templates', {})
 
     def __getitem__(self, key):
         if key in SPECIAL_VARIABLES:
             return SPECIAL_VARIABLES[key][0](self._context)
         if key in FUNCTIONS:
-            return FUNCTIONS[key][0](self)
+            return self._create_function(FUNCTIONS[key])
         if key in self.templates:
             return self._create_template_function(self.templates[key])
         return Sandbox.__getitem__(self, key)
 
     def __setitem__(self, key, value):
         if key in SPECIAL_VARIABLES or key in FUNCTIONS:
             raise KeyError()
         if key in self.exports:
@@ -344,16 +345,37 @@ class MozbuildSandbox(Sandbox):
         # So we need to prepend with n - 1 newlines so that line numbers
         # are unchanged.
         code = '\n' * (firstlineno + begin[0] - 3) + 'if True:\n'
         code += ''.join(lines[begin[0] - 1:])
 
         self.templates[name] = func, code, self._context.current_path
 
     @memoize
+    def _create_function(self, function_def):
+        """Returns a function object for use within the sandbox for the given
+        function definition.
+
+        The wrapper function does type coercion on the function arguments
+        """
+        func, args_def, doc = function_def
+        def function(*args):
+            def coerce(arg, type):
+                if not isinstance(arg, type):
+                    if issubclass(type, ContextDerivedValue):
+                        arg = type(self._context, arg)
+                    else:
+                        arg = type(arg)
+                return arg
+            args = [coerce(arg, type) for arg, type in zip(args, args_def)]
+            return func(self)(*args)
+
+        return function
+
+    @memoize
     def _create_template_function(self, template):
         """Returns a function object for use within the sandbox for the given
         template.
 
         When a moz.build file contains a reference to a template call, the
         sandbox needs a function to execute. This is what this method returns.
         That function creates a new sandbox for execution of the template.
         After the template is executed, the data from its execution is merged
--- a/python/mozbuild/mozbuild/test/frontend/test_sandbox.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_sandbox.py
@@ -448,11 +448,50 @@ def Template():
         e = se.exception
         self.assertIsInstance(e.exc_value, KeyError)
 
         e = se.exception.exc_value
         self.assertEqual(e.message,
             'A template named "Template" was already declared in %s.' %
             sandbox.normalize_path('templates.mozbuild'))
 
+    def test_function_args(self):
+        class Foo(int): pass
+
+        def foo(a, b):
+            return type(a), type(b)
+
+        FUNCTIONS.update({
+            'foo': (lambda self: foo, (Foo, int), ''),
+        })
+
+        try:
+            sandbox = self.sandbox()
+            source = 'foo("a", "b")'
+
+            with self.assertRaises(SandboxExecutionError) as se:
+                sandbox.exec_source(source,
+                    sandbox.normalize_path('foo.mozbuild'))
+
+            e = se.exception
+            self.assertIsInstance(e.exc_value, ValueError)
+
+            sandbox = self.sandbox()
+            source = 'foo(1, "b")'
+
+            with self.assertRaises(SandboxExecutionError) as se:
+                sandbox.exec_source(source,
+                    sandbox.normalize_path('foo.mozbuild'))
+
+            e = se.exception
+            self.assertIsInstance(e.exc_value, ValueError)
+
+            sandbox = self.sandbox()
+            source = 'a = foo(1, 2)'
+            sandbox.exec_source(source, sandbox.normalize_path('foo.mozbuild'))
+
+            self.assertEquals(sandbox['a'], (Foo, int))
+        finally:
+            del FUNCTIONS['foo']
+
 
 if __name__ == '__main__':
     main()