Bug 1316844 - Improve function unwrapping to properly cover templates. r?chmanchester draft
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 11 Nov 2016 11:32:43 +0900
changeset 437692 cd2324fb2f35d276603871dbc807db8b0748b126
parent 437671 bb7bc01deac5bf711297e216328e640aeb729895
child 437693 6b0126394d181d00b3be5291680f0c9d115532c3
push id35487
push userbmo:mh+mozilla@glandium.org
push dateFri, 11 Nov 2016 11:43:00 +0000
reviewerschmanchester
bugs1316844
milestone52.0a1
Bug 1316844 - Improve function unwrapping to properly cover templates. r?chmanchester
python/mozbuild/mozbuild/configure/__init__.py
python/mozbuild/mozbuild/configure/lint.py
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -655,39 +655,42 @@ class ConfigureSandbox(dict):
                     return func
                 return obj
 
             # The following function may end up being prepared to be sandboxed,
             # so it mustn't depend on anything from the global scope in this
             # file. It can however depend on variables from the closure, thus
             # maybe_prepare_function and isfunction are declared above to be
             # available there.
-            @wraps(template)
+            @self.wraps(template)
             def wrapper(*args, **kwargs):
                 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 isfunction(ret):
                     # We can't expect the sandboxed code to think about all the
                     # details of implementing decorators, so do some of the
                     # work for them. If the function takes exactly one function
                     # as argument and returns a function, it must be a
                     # decorator, so mark the returned function as wrapping the
                     # function passed in.
                     if len(args) == 1 and not kwargs and isfunction(args[0]):
-                        ret = wraps(args[0])(ret)
+                        ret = self.wraps(args[0])(ret)
                     return wrap_template(ret)
                 return ret
             return wrapper
 
         wrapper = wrap_template(template)
         self._templates.add(wrapper)
         return wrapper
 
+    def wraps(self, func):
+        return wraps(func)
+
     RE_MODULE = re.compile('^[a-zA-Z0-9_\.]+$')
 
     def imports_impl(self, _import, _from=None, _as=None):
         '''Implementation of @imports.
         This decorator imports the given _import from the given _from module
         optionally under a different _as name.
         The options correspond to the various forms for the import builtin.
             @imports('sys')
@@ -912,24 +915,24 @@ class ConfigureSandbox(dict):
             def makecell(content):
                 def f():
                     content
                 return f.func_closure[0]
 
             closure = tuple(makecell(cell.cell_contents)
                             for cell in func.func_closure)
 
-        new_func = wraps(func)(types.FunctionType(
+        new_func = self.wraps(func)(types.FunctionType(
             func.func_code,
             glob,
             func.__name__,
             func.func_defaults,
             closure
         ))
-        @wraps(new_func)
+        @self.wraps(new_func)
         def wrapped(*args, **kwargs):
             if func in self._imports:
                 self._apply_imports(func, glob)
                 del self._imports[func]
             return new_func(*args, **kwargs)
 
         self._prepared_functions.add(wrapped)
         return wrapped, glob
--- a/python/mozbuild/mozbuild/configure/lint.py
+++ b/python/mozbuild/mozbuild/configure/lint.py
@@ -1,20 +1,22 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
+from functools import wraps
 from StringIO import StringIO
 from . import (
     CombinedDependsFunction,
     ConfigureError,
     ConfigureSandbox,
     DependsFunction,
+    SandboxedGlobal,
 )
 from .lint_util import disassemble_as_iter
 from mozbuild.util import memoize
 
 
 class LintSandbox(ConfigureSandbox):
     def __init__(self, environ=None, argv=None, stdout=None, stderr=None):
         out = StringIO()
@@ -32,17 +34,17 @@ class LintSandbox(ConfigureSandbox):
 
     def _missing_help_dependency(self, obj):
         if isinstance(obj, CombinedDependsFunction):
             return False
         if isinstance(obj, DependsFunction):
             if (self._help_option in obj.dependencies or
                 obj in (self._always, self._never)):
                 return False
-            func, glob = self._wrapped[obj.func]
+            func, glob = self.unwrap(obj.func)
             # We allow missing --help dependencies for functions that:
             # - don't use @imports
             # - don't have a closure
             # - don't use global variables
             if func in self._imports or func.func_closure:
                 return True
             for op, arg in disassemble_as_iter(func):
                 if op in ('LOAD_GLOBAL', 'STORE_GLOBAL'):
@@ -66,13 +68,21 @@ class LintSandbox(ConfigureSandbox):
                         % (obj.name, arg.name, arg.name))
         elif ((self._help or need_help_dependency) and
               self._missing_help_dependency(obj)):
             raise ConfigureError("Missing @depends for `%s`: '--help'" %
                                  obj.name)
         return super(LintSandbox, self)._value_for_depends(
             obj, need_help_dependency)
 
-    def _prepare_function(self, func):
-        wrapped, glob = super(LintSandbox, self)._prepare_function(func)
-        if wrapped not in self._wrapped:
-            self._wrapped[wrapped] = func, glob
-        return wrapped, glob
+    def unwrap(self, func):
+        glob = func.func_globals
+        while func in self._wrapped:
+            if isinstance(func.func_globals, SandboxedGlobal):
+                glob = func.func_globals
+            func = self._wrapped[func]
+        return func, glob
+
+    def wraps(self, func):
+        def do_wraps(wrapper):
+            self._wrapped[wrapper] = func
+            return wraps(func)(wrapper)
+        return do_wraps