Skip to content

Commit

Permalink
Transform 'a.b' into _getattr_(a, 'b')
Browse files Browse the repository at this point in the history
  • Loading branch information
stephan-hof committed Oct 4, 2016
1 parent bc0b3a8 commit 4c665a2
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/RestrictedPython/transformer.py
Expand Up @@ -159,6 +159,18 @@
])


# When new ast nodes are generated they have no 'lineno' and 'col_offset'.
# This function copies these two fields from the incoming node
def copy_locations(new_node, old_node):
assert 'lineno' in new_node._attributes
new_node.lineno = old_node.lineno

assert 'col_offset' in new_node._attributes
new_node.col_offset = old_node.col_offset

ast.fix_missing_locations(new_node)


class RestrictingNodeTransformer(ast.NodeTransformer):

def __init__(self, errors=[], warnings=[], used_names=[]):
Expand Down Expand Up @@ -513,6 +525,16 @@ def visit_Attribute(self, node):
if node.attr.startswith('_'):
self.error(
node, '"{name}" is an invalid attribute name because it starts with "_".'.format(name=node.attr))

if isinstance(node.ctx, ast.Load):
node = self.generic_visit(node)
new_node = ast.Call(
func=ast.Name('_getattr_', ast.Load()),
args=[node.value, ast.Str(node.attr)],
keywords=[])

copy_locations(new_node, node)
return new_node
else:
return self.generic_visit(node)

Expand Down
24 changes: 24 additions & 0 deletions tests/test_transformer.py
@@ -1,5 +1,6 @@
import pytest
import RestrictedPython
import six
import sys


Expand Down Expand Up @@ -113,6 +114,29 @@ def test_transformer__RestrictingNodeTransformer__visit_Attribute__1(compile):
'starts with "_".',) == errors


TRANSFORM_ATTRIBUTE_ACCESS = """\
def func():
return a.b
"""


@pytest.mark.parametrize(*compile)
def test_transformer__RestrictingNodeTransformer__visit_Attribute__2(compile, mocker):
code, errors, warnings, used_names = compile.compile_restricted_exec(
TRANSFORM_ATTRIBUTE_ACCESS)

glb = {
'_getattr_': mocker.stub(),
'a': [],
'b': 'b'
}

six.exec_(code, glb)
glb['func']()
glb['_getattr_'].assert_called_once_with([], 'b')



EXEC_FUNCTION = """\
def no_exec():
exec('q = 1')
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Expand Up @@ -19,6 +19,7 @@ deps =
pytest-remove-stale-bytecode
pytest-flake8
pytest-isort
pytest-mock
# pytest-mypy

[testenv:coverage-clean]
Expand Down

0 comments on commit 4c665a2

Please sign in to comment.