Skip to content

Commit

Permalink
feat: trigger rebind on set attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Dec 7, 2023
1 parent 493c371 commit ed97089
Show file tree
Hide file tree
Showing 29 changed files with 257 additions and 91 deletions.
14 changes: 8 additions & 6 deletions bolt/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,10 @@ def children(self, nodes: Iterable[str]) -> str:
"""Emit children helper."""
return self.helper("children", f"[{', '.join(nodes)}]")

def get_attribute(self, obj: str, name: str) -> str:
"""Emit get_attribute helper."""
return self.helper("get_attribute", obj, repr(name))
def get_attribute_handler(self, obj: str, name: str) -> str:
"""Emit get_attribute_handler helper."""
attribute_handler = self.helper("get_attribute_handler", obj)
return f'{attribute_handler}["{name}"]'

def import_module(self, name: str) -> str:
"""Emit import_module helper."""
Expand Down Expand Up @@ -1025,7 +1026,7 @@ def from_import_statement(
stmt = f"_bolt_from_import = {acc.import_module(module.path)}"
acc.statement(stmt, lineno=node)
for name in names:
rhs = acc.get_attribute("_bolt_from_import", name)
rhs = acc.get_attribute_handler("_bolt_from_import", name)
acc.statement(f"{name} = {rhs}", lineno=node)

return []
Expand Down Expand Up @@ -1303,7 +1304,7 @@ def attribute(
acc: Accumulator,
) -> Generator[AstNode, Optional[List[str]], Optional[List[str]]]:
value = yield from visit_single(node.value, required=True)
rhs = acc.get_attribute(value, node.name)
rhs = acc.get_attribute_handler(value, node.name)
acc.statement(f"{value} = {rhs}", lineno=node)
return [value]

Expand Down Expand Up @@ -1389,7 +1390,8 @@ def target_attribute(
acc: Accumulator,
) -> Generator[AstNode, Optional[List[str]], Optional[List[str]]]:
value = yield from visit_single(node.value, required=True)
return [f"{value}.{node.name}"]
target = acc.get_attribute_handler(value, node.name)
return [target]

@rule(AstTargetItem)
def target_item(
Expand Down
34 changes: 20 additions & 14 deletions bolt/contrib/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

__all__ = [
"Sandbox",
"SandboxedGetAttribute",
"SandboxedAttributeHandler",
"SandboxedImportModule",
"SecurityError",
"public_attrs",
Expand Down Expand Up @@ -184,11 +184,16 @@ def activate(self):
if self.active:
return
self.active = True

self.runtime.builtins &= self.allowed_builtins

get_attribute_handler = self.runtime.helpers["get_attribute_handler"]

self.runtime.helpers.update(
get_attribute=SandboxedGetAttribute(
get_attribute_handler=lambda obj: SandboxedAttributeHandler( # type: ignore
obj=obj,
handler=get_attribute_handler(obj),
sandbox=self,
get_attribute=self.runtime.helpers["get_attribute"],
),
import_module=SandboxedImportModule(
sandbox=self,
Expand All @@ -198,30 +203,31 @@ def activate(self):


@dataclass
class SandboxedGetAttribute:
"""Sandboxed get_attribute helper."""
class SandboxedAttributeHandler:
"""Sandboxed attribute handler helper."""

obj: Any
handler: Any
sandbox: Sandbox
get_attribute: Callable[[Any, str], Any]

@internal
def __call__(self, obj: Any, attr: str) -> Any:
if not hasattr(obj, attr):
return self.get_attribute(obj, attr)
def __getitem__(self, attr: str) -> Any:
if not hasattr(self.obj, attr):
return self.handler[attr]

try:
if allowed := self.sandbox.allowed_obj_attrs.get(obj):
if allowed := self.sandbox.allowed_obj_attrs.get(self.obj):
if attr in allowed:
return self.get_attribute(obj, attr)
return self.handler[attr]
except TypeError:
pass

for cls in type.mro(type(obj)):
for cls in type.mro(type(self.obj)):
if allowed := self.sandbox.allowed_type_attrs.get(cls):
if attr in allowed:
return self.get_attribute(obj, attr)
return self.handler[attr]

raise SecurityError(f"Access forbidden attribute {attr!r} of {type(obj)}.")
raise SecurityError(f"Access forbidden attribute {attr!r} of {type(self.obj)}.")


@dataclass
Expand Down
50 changes: 41 additions & 9 deletions bolt/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_bolt_helpers() -> Dict[str, Any]:
"branch": BranchDriver,
"get_dup": get_dup,
"get_rebind": get_rebind,
"get_attribute": get_attribute,
"get_attribute_handler": AttributeHandler,
"import_module": python_import_module,
"macro_call": macro_call,
"resolve_formatted_location": resolve_formatted_location,
Expand Down Expand Up @@ -166,15 +166,47 @@ def get_rebind(obj: Any):
return None


@internal
def get_attribute(obj: Any, attr: str):
try:
return getattr(obj, attr)
except AttributeError as exc:
@dataclass
class AttributeHandler:
obj: Any
item: bool = False

@internal
def __getitem__(self, attr: str) -> Any:
try:
return getattr(self.obj, attr)
except AttributeError as exc:
try:
result = self.obj[attr]
self.item = True
return result
except (TypeError, LookupError):
raise exc from None

@internal
def __setitem__(self, attr: str, value: Any):
try:
current = self.__getitem__(attr)
except AttributeError:
pass
else:
if func := getattr(type(current), "__rebind__", None):
value = func(current, value)
if self.item:
self.obj[attr] = value
else:
setattr(self.obj, attr, value)

@internal
def __delitem__(self, attr: str):
try:
return obj[attr]
except (TypeError, LookupError):
raise exc from None
self.__getitem__(attr)
except AttributeError:
pass
if self.item:
del self.obj[attr]
else:
delattr(self.obj, attr)


@internal
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from dataclasses import dataclass

@dataclass
class Var:
name: str

def __add__(self, value):
result = Var(f"{self}_plus_{value}")
say f"{result} = {self} + {value}"
return result

def __rebind__(self, value):
say f"{self.name} = {value}"
return self

def __str__(self):
return self.name

foo = Var("foo")
bar = Var("bar")
say f"{foo} {bar}"

foo = 123
foo += 456
foo = bar + 789
foo += bar + 999

del foo
del bar

foo = 123
bar = 456
say f"{foo} {bar}"

@dataclass
class A:
foo: Var
bar: Var

a = A(Var("foo"), Var("bar"))
say a

a.foo = 123
a.foo += 456
a.foo = a.bar + 789
a.foo += a.bar + 999

del a.foo
del a.bar

a.foo = 123
a.bar = 456
say a

b = {ayy: Var("ayy"), yoo: Var("yoo")}
say b

b.ayy = 123
b.ayy += 456
b.ayy = b.yoo + 789
b.ayy += b.yoo + 999

del b.ayy
del b.yoo

b["ayy"] = 123
b["yoo"] = 456
say b

c = {ayy: Var("ayy"), yoo: Var("yoo")}
say c

c["ayy"] = 123
c["ayy"] += 456
c["ayy"] = c["yoo"] + 789
c["ayy"] += c["yoo"] + 999

del c.ayy
del c.yoo

c["ayy"] = 123
c["yoo"] = 456
say c
4 changes: 2 additions & 2 deletions tests/snapshots/bolt__parse_126__1.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
_bolt_lineno = [1, 8], [1, 2]
_bolt_helper_import_module = _bolt_runtime.helpers['import_module']
_bolt_helper_get_attribute = _bolt_runtime.helpers['get_attribute']
_bolt_helper_get_attribute_handler = _bolt_runtime.helpers['get_attribute_handler']
_bolt_helper_children = _bolt_runtime.helpers['children']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
with _bolt_runtime.scope() as _bolt_var2:
math = _bolt_helper_import_module('math')
_bolt_var0 = math
_bolt_var0 = _bolt_helper_get_attribute(_bolt_var0, 'sin')
_bolt_var0 = _bolt_helper_get_attribute_handler(_bolt_var0)["sin"]
_bolt_var1 = 1
_bolt_var0 = _bolt_var0(_bolt_var1)
_bolt_var3 = _bolt_helper_replace(_bolt_refs[0], commands=_bolt_helper_children(_bolt_var2))
Expand Down
4 changes: 2 additions & 2 deletions tests/snapshots/bolt__parse_127__1.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
_bolt_lineno = [1, 8], [1, 2]
_bolt_helper_import_module = _bolt_runtime.helpers['import_module']
_bolt_helper_get_attribute = _bolt_runtime.helpers['get_attribute']
_bolt_helper_get_attribute_handler = _bolt_runtime.helpers['get_attribute_handler']
_bolt_helper_children = _bolt_runtime.helpers['children']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
with _bolt_runtime.scope() as _bolt_var2:
m = _bolt_helper_import_module('math')
_bolt_var0 = m
_bolt_var0 = _bolt_helper_get_attribute(_bolt_var0, 'sin')
_bolt_var0 = _bolt_helper_get_attribute_handler(_bolt_var0)["sin"]
_bolt_var1 = 1
_bolt_var0 = _bolt_var0(_bolt_var1)
_bolt_var3 = _bolt_helper_replace(_bolt_refs[0], commands=_bolt_helper_children(_bolt_var2))
Expand Down
14 changes: 7 additions & 7 deletions tests/snapshots/bolt__parse_139__1.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
_bolt_lineno = [1, 15, 20, 28, 40], [1, 2, 3, 4, 6]
_bolt_helper_get_attribute = _bolt_runtime.helpers['get_attribute']
_bolt_helper_get_attribute_handler = _bolt_runtime.helpers['get_attribute_handler']
_bolt_helper_interpolate_resource_location = _bolt_runtime.helpers['interpolate_resource_location']
_bolt_helper_children = _bolt_runtime.helpers['children']
_bolt_helper_operator_not = _bolt_runtime.helpers['operator_not']
Expand All @@ -13,37 +13,37 @@ with _bolt_runtime.scope() as _bolt_var10:
_bolt_var0 = _bolt_var0(_bolt_var1)
for node in _bolt_var0:
_bolt_var2 = node
_bolt_var2 = _bolt_helper_get_attribute(_bolt_var2, 'parent')
_bolt_var2 = _bolt_helper_get_attribute_handler(_bolt_var2)["parent"]
_bolt_var2 = _bolt_helper_interpolate_resource_location(_bolt_var2, _bolt_refs[0])
with _bolt_runtime.push_nesting('append:function:name:commands', *_bolt_helper_children([_bolt_var2])):
with _bolt_runtime.scope() as _bolt_var9:
_bolt_var3 = node
_bolt_var3 = _bolt_helper_get_attribute(_bolt_var3, 'partition')
_bolt_var3 = _bolt_helper_get_attribute_handler(_bolt_var3)["partition"]
_bolt_var4 = 5
_bolt_var3 = _bolt_var3(_bolt_var4)
_bolt_var3_inverse = _bolt_helper_operator_not(_bolt_var3)
with _bolt_helper_branch(_bolt_var3) as _bolt_condition:
if _bolt_condition:
with _bolt_runtime.push_nesting('execute:subcommand'):
_bolt_var5 = node
_bolt_var5 = _bolt_helper_get_attribute(_bolt_var5, 'range')
_bolt_var5 = _bolt_helper_get_attribute_handler(_bolt_var5)["range"]
_bolt_var5 = _bolt_helper_interpolate_range(_bolt_var5, _bolt_refs[1])
with _bolt_runtime.push_nesting('execute:if:score:target:targetObjective:matches:range:subcommand', *_bolt_helper_children([_bolt_refs[2], _bolt_refs[3], _bolt_var5])):
with _bolt_runtime.push_nesting('execute:run:subcommand'):
_bolt_var6 = node
_bolt_var6 = _bolt_helper_get_attribute(_bolt_var6, 'children')
_bolt_var6 = _bolt_helper_get_attribute_handler(_bolt_var6)["children"]
_bolt_var6 = _bolt_helper_interpolate_resource_location(_bolt_var6, _bolt_refs[4])
_bolt_runtime.commands.append(_bolt_helper_replace(_bolt_refs[8], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[7], arguments=_bolt_helper_children([*_bolt_helper_children([_bolt_refs[2], _bolt_refs[3], _bolt_var5]), _bolt_helper_replace(_bolt_refs[6], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[5], arguments=_bolt_helper_children([_bolt_var6]))]))]))])))
with _bolt_helper_branch(_bolt_var3_inverse) as _bolt_condition:
if _bolt_condition:
with _bolt_runtime.push_nesting('execute:subcommand'):
_bolt_var7 = node
_bolt_var7 = _bolt_helper_get_attribute(_bolt_var7, 'range')
_bolt_var7 = _bolt_helper_get_attribute_handler(_bolt_var7)["range"]
_bolt_var7 = _bolt_helper_interpolate_range(_bolt_var7, _bolt_refs[9])
with _bolt_runtime.push_nesting('execute:if:score:target:targetObjective:matches:range:subcommand', *_bolt_helper_children([_bolt_refs[10], _bolt_refs[11], _bolt_var7])):
with _bolt_runtime.push_nesting('execute:run:subcommand'):
_bolt_var8 = node
_bolt_var8 = _bolt_helper_get_attribute(_bolt_var8, 'value')
_bolt_var8 = _bolt_helper_get_attribute_handler(_bolt_var8)["value"]
_bolt_var8 = _bolt_helper_interpolate_message(_bolt_var8, _bolt_refs[12])
_bolt_runtime.commands.append(_bolt_helper_replace(_bolt_refs[16], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[15], arguments=_bolt_helper_children([*_bolt_helper_children([_bolt_refs[10], _bolt_refs[11], _bolt_var7]), _bolt_helper_replace(_bolt_refs[14], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[13], arguments=_bolt_helper_children([_bolt_var8]))]))]))])))
_bolt_runtime.commands.append(_bolt_helper_replace(_bolt_refs[18], arguments=_bolt_helper_children([*_bolt_helper_children([_bolt_var2]), _bolt_helper_replace(_bolt_refs[17], commands=_bolt_helper_children(_bolt_var9))])))
Expand Down
4 changes: 2 additions & 2 deletions tests/snapshots/bolt__parse_140__1.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
_bolt_lineno = [1], [1]
_bolt_helper_get_attribute = _bolt_runtime.helpers['get_attribute']
_bolt_helper_get_attribute_handler = _bolt_runtime.helpers['get_attribute_handler']
_bolt_helper_children = _bolt_runtime.helpers['children']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
with _bolt_runtime.scope() as _bolt_var3:
_bolt_var0 = print
_bolt_var1 = ctx
_bolt_var1 = _bolt_helper_get_attribute(_bolt_var1, 'directory')
_bolt_var1 = _bolt_helper_get_attribute_handler(_bolt_var1)["directory"]
_bolt_var2 = __name__
_bolt_var0 = _bolt_var0(_bolt_var1, _bolt_var2)
_bolt_var4 = _bolt_helper_replace(_bolt_refs[0], commands=_bolt_helper_children(_bolt_var3))
Expand Down
5 changes: 3 additions & 2 deletions tests/snapshots/bolt__parse_160__1.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
_bolt_lineno = [1, 6], [1, 2]
_bolt_lineno = [1, 7], [1, 2]
_bolt_helper_get_attribute_handler = _bolt_runtime.helpers['get_attribute_handler']
_bolt_helper_children = _bolt_runtime.helpers['children']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
with _bolt_runtime.scope() as _bolt_var0:
def f():
_bolt_var0 = {}
_bolt_var1 = f
_bolt_var1.data = _bolt_var0
_bolt_helper_get_attribute_handler(_bolt_var1)["data"] = _bolt_var0
_bolt_var1 = _bolt_helper_replace(_bolt_refs[0], commands=_bolt_helper_children(_bolt_var0))
---
output = _bolt_var1
Expand Down
5 changes: 3 additions & 2 deletions tests/snapshots/bolt__parse_162__1.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
_bolt_lineno = [1, 10], [1, 2]
_bolt_lineno = [1, 11], [1, 2]
_bolt_helper_get_attribute_handler = _bolt_runtime.helpers['get_attribute_handler']
_bolt_helper_children = _bolt_runtime.helpers['children']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
with _bolt_runtime.scope() as _bolt_var5:
Expand All @@ -8,7 +9,7 @@ with _bolt_runtime.scope() as _bolt_var5:
d = _bolt_var2
_bolt_var3 = 1
_bolt_var4 = d
_bolt_var4.foo += _bolt_var3
_bolt_helper_get_attribute_handler(_bolt_var4)["foo"] += _bolt_var3
_bolt_var6 = _bolt_helper_replace(_bolt_refs[0], commands=_bolt_helper_children(_bolt_var5))
---
output = _bolt_var6
Expand Down
Loading

0 comments on commit ed97089

Please sign in to comment.