Bug 1259351 - Properly sandbox functions that are decorated with templates. r?nalexander
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -229,17 +229,17 @@ class ConfigureSandbox(dict):
def __setitem__(self, key, value):
if (key in self.BUILTINS or key == '__builtins__' or
hasattr(self, '%s_impl' % key)):
raise KeyError('Cannot reassign builtins')
if (not isinstance(value, DependsFunction) and
value not in self._templates and
- not issubclass(value, Exception)):
+ not (inspect.isclass(value) and issubclass(value, Exception))):
raise KeyError('Cannot assign `%s` because it is neither a '
'@depends nor a @template' % key)
return super(ConfigureSandbox, self).__setitem__(key, value)
def _resolve(self, arg, need_help_dependency=True):
if isinstance(arg, DependsFunction):
assert arg in self._depends
@@ -388,18 +388,42 @@ class ConfigureSandbox(dict):
Templates allow to simplify repetitive constructs, or to implement
helper decorators and somesuch.
'''
template, glob = self._prepare_function(func)
glob.update(
(k[:-len('_impl')], getattr(self, k))
for k in dir(self) if k.endswith('_impl') and k != 'template_impl'
)
- self._templates.add(template)
- return template
+
+ # Any function argument to the template must be prepared to be sandboxed.
+ # If the template itself returns a function (in which case, it's very
+ # likely a decorator), that function must be prepared to be sandboxed as
+ # well.
+ def wrap_template(template):
+ @wraps(template)
+ def wrapper(*args, **kwargs):
+ def maybe_prepare_function(obj):
+ if inspect.isfunction(obj):
+ func, _ = self._prepare_function(obj)
+ return func
+ return obj
+
+ args = [maybe_prepare_function(arg) for arg in args]
+ kwargs = {k: maybe_prepare_function(v)
+ for k, v in kwargs.iteritems()}
+ ret = template(*args, **kwargs)
+ if inspect.isfunction(ret):
+ return wrap_template(ret)
+ return ret
+ return wrapper
+
+ wrapper = wrap_template(template)
+ self._templates.add(wrapper)
+ return wrapper
def advanced_impl(self, func):
'''Implementation of @advanced.
This function gives the decorated function access to the complete set
of builtins, allowing the import keyword as an expected side effect.
'''
func, glob = self._prepare_function(func)
glob.update(__builtins__=__builtins__)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/decorators.configure
@@ -0,0 +1,44 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+@template
+def simple_decorator(func):
+ return func
+
+@template
+def wrapper_decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+ return wrapper
+
+@template
+def function_decorator(*args, **kwargs):
+ # We could return wrapper_decorator from above here, but then we wouldn't
+ # know if this works as expected because wrapper_decorator itself was
+ # modified or because the right thing happened here.
+ def wrapper_decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+ return wrapper
+ return wrapper_decorator
+
+@depends('--help')
+@simple_decorator
+def foo(help):
+ global FOO
+ FOO = 1
+
+@depends('--help')
+@wrapper_decorator
+def bar(help):
+ global BAR
+ BAR = 1
+
+@depends('--help')
+@function_decorator('a', 'b', 'c')
+def qux(help):
+ global QUX
+ QUX = 1
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -254,16 +254,27 @@ class TestConfigure(unittest.TestCase):
self.assertIn('TEMPLATE_VALUE_2', config)
self.assertEquals(config['TEMPLATE_VALUE_2'], 21)
def test_template_advanced(self):
config = self.get_config(['--enable-advanced-template'])
self.assertIn('PLATFORM', config)
self.assertEquals(config['PLATFORM'], sys.platform)
+ def test_decorators(self):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, {}, [], out, out)
+
+ sandbox.exec_file(mozpath.join(test_data_path, 'decorators.configure'))
+
+ self.assertNotIn('FOO', sandbox)
+ self.assertNotIn('BAR', sandbox)
+ self.assertNotIn('QUX', sandbox)
+
def test_set_config(self):
def get_config(*args):
return self.get_config(*args, configure='set_config.configure')
config, out = self.get_result(['--help'],
configure='set_config.configure')
self.assertEquals(config, {})