/usr/lib/python2.7/dist-packages/foolscap/slicer.py is in python-foolscap 0.13.1-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | # -*- test-case-name: foolscap.test.test_banana -*-
from twisted.python.components import registerAdapter
from twisted.python import log
from zope.interface import implements
from twisted.internet.defer import Deferred
import tokens
from tokens import Violation, BananaError
from foolscap.ipb import IBroker
class SlicerClass(type):
# auto-register Slicers
def __init__(self, name, bases, dict):
type.__init__(self, name, bases, dict)
typ = dict.get('slices')
#reg = dict.get('slicerRegistry')
if typ:
registerAdapter(self, typ, tokens.ISlicer)
class BaseSlicer(object):
__metaclass__ = SlicerClass
implements(tokens.ISlicer)
slices = None
parent = None
sendOpen = True
opentype = ()
trackReferences = False
def __init__(self, obj):
# this simplifies Slicers which are adapters
self.obj = obj
def requireBroker(self, protocol):
broker = IBroker(protocol, None)
if not broker:
msg = "This object can only be serialized by a broker"
raise Violation(msg)
return broker
def registerRefID(self, refid, obj):
# optimize: most Slicers will delegate this up to the Root
return self.parent.registerRefID(refid, obj)
def slicerForObject(self, obj):
# optimize: most Slicers will delegate this up to the Root
return self.parent.slicerForObject(obj)
def slice(self, streamable, banana):
# this is what makes us ISlicer
self.streamable = streamable
assert self.opentype
for o in self.opentype:
yield o
for t in self.sliceBody(streamable, banana):
yield t
def sliceBody(self, streamable, banana):
raise NotImplementedError
def childAborted(self, f):
return f
def describe(self):
return "??"
class ScopedSlicer(BaseSlicer):
"""This Slicer provides a containing scope for referenceable things like
lists. The same list will not be serialized twice within this scope, but
it will not survive outside it."""
def __init__(self, obj):
BaseSlicer.__init__(self, obj)
self.references = {} # maps id(obj) -> (obj,refid)
def registerRefID(self, refid, obj):
# keep references here, not in the actual PBRootSlicer
# This use of id(obj) requires a bit of explanation. We are making
# the assumption that the object graph remains unmodified until
# serialization is complete. In particular, we assume that all the
# objects in it remain alive, and no new objects are added to it,
# until serialization is complete. id(obj) is only unique for live
# objects: once the object is garbage-collected, a new object may be
# created with the same id(obj) value.
#
# The concern is that a custom Slicer will call something that
# mutates the object graph before it has finished being serialized.
# This might be one which calls some user-level function during
# Slicing, or one which uses a Deferred to put off serialization for
# a while, creating an opportunity for some other code to get
# control.
# The specific concern is that if, in the middle of serialization, an
# object that was already serialized is gc'ed, and a new object is
# created and attached to a portion of the object graph that hasn't
# been serialized yet, and if the new object gets the same id(obj) as
# the dead object, then we could be tricked into sending the
# reference number of the old (dead) object. On the receiving end,
# this would result in a mangled object graph.
# User code isn't supposed to allow the object graph to change during
# serialization, so this mangling "should not happen" under normal
# circumstances. However, as a reasonably cheap way to mitigate the
# worst sort of mangling when user code *does* mess up,
# self.references maps from id(obj) to a tuple of (obj,refid) instead
# of just the refid. This insures that the object will stay alive
# until the ScopedSlicer dies, guaranteeing that we won't get
# duplicate id(obj) values. If user code mutates the object graph
# during serialization we might still get inconsistent results, but
# they'll be the ordinary kind of inconsistent results (snapshots of
# different branches of the object graph at different points in time)
# rather than the blatantly wrong mangling that would occur with
# re-used id(obj) values.
self.references[id(obj)] = (obj,refid)
def slicerForObject(self, obj):
# check for an object which was sent previously or has at least
# started sending
obj_refid = self.references.get(id(obj), None)
if obj_refid is not None:
# we've started to send this object already, so just include a
# reference to it
return ReferenceSlicer(obj_refid[1])
# otherwise go upstream so we can serialize the object completely
return self.parent.slicerForObject(obj)
UnslicerRegistry = {}
BananaUnslicerRegistry = {}
def registerUnslicer(opentype, factory, registry=None):
if registry is None:
registry = UnslicerRegistry
assert not registry.has_key(opentype)
registry[opentype] = factory
class UnslicerClass(type):
# auto-register Unslicers
def __init__(self, name, bases, dict):
type.__init__(self, name, bases, dict)
opentype = dict.get('opentype')
reg = dict.get('unslicerRegistry')
if opentype:
registerUnslicer(opentype, self, reg)
class BaseUnslicer(object):
__metaclass__ = UnslicerClass
opentype = None
implements(tokens.IUnslicer)
def __init__(self):
pass
def describe(self):
return "??"
def setConstraint(self, constraint):
pass
def start(self, count):
pass
def checkToken(self, typebyte, size):
return # no restrictions
def openerCheckToken(self, typebyte, size, opentype):
return self.parent.openerCheckToken(typebyte, size, opentype)
def open(self, opentype):
"""Return an IUnslicer object based upon the 'opentype' tuple.
Subclasses that wish to change the way opentypes are mapped to
Unslicers can do so by changing this behavior.
This method does not apply constraints, it only serves to map
opentype into Unslicer. Most subclasses will implement this by
delegating the request to their parent (and thus, eventually, to the
RootUnslicer), and will set the new child's .opener attribute so
that they can do the same. Subclasses that wish to change the way
opentypes are mapped to Unslicers can do so by changing this
behavior."""
return self.parent.open(opentype)
def doOpen(self, opentype):
"""Return an IUnslicer object based upon the 'opentype' tuple. This
object will receive all tokens destined for the subnode.
If you want to enforce a constraint, you must override this method
and do two things: make sure your constraint accepts the opentype,
and set a per-item constraint on the new child unslicer.
This method gets the IUnslicer from our .open() method. That might
return None instead of a child unslicer if the they want a
multi-token opentype tuple, so be sure to check for Noneness before
adding a per-item constraint.
"""
return self.open(opentype)
def receiveChild(self, obj, ready_deferred=None):
"""Unslicers for containers should accumulate their children's
ready_deferreds, then combine them in an AsyncAND when receiveClose()
happens, and return the AsyncAND as the ready_deferreds half of the
receiveClose() return value.
"""
pass
def reportViolation(self, why):
return why
def receiveClose(self):
raise NotImplementedError
def finish(self):
pass
def setObject(self, counter, obj):
"""To pass references to previously-sent objects, the [OPEN,
'reference', number, CLOSE] sequence is used. The numbers are
generated implicitly by the sending Banana, counting from 0 for the
object described by the very first OPEN sent over the wire,
incrementing for each subsequent one. The objects themselves are
stored in any/all Unslicers who cares to. Generally this is the
RootUnslicer, but child slices could do it too if they wished.
"""
# TODO: examine how abandoned child objects could mess up this
# counter
pass
def getObject(self, counter):
"""'None' means 'ask our parent instead'.
"""
return None
def explode(self, failure):
"""If something goes wrong in a Deferred callback, it may be too late
to reject the token and to normal error handling. I haven't figured
out how to do sensible error-handling in this situation. This method
exists to make sure that the exception shows up *somewhere*. If this
is called, it is also likely that a placeholder (probably a Deferred)
will be left in the unserialized object graph about to be handed to
the RootUnslicer.
"""
# RootUnslicer pays attention to this .exploded attribute and refuses
# to deliver anything if it is set. But PBRootUnslicer ignores it.
# TODO: clean this up, and write some unit tests to trigger it (by
# violating schemas?)
log.msg("BaseUnslicer.explode: %s" % failure)
self.protocol.exploded = failure
class ScopedUnslicer(BaseUnslicer):
"""This Unslicer provides a containing scope for referenceable things
like lists. It corresponds to the ScopedSlicer base class."""
def __init__(self):
BaseUnslicer.__init__(self)
self.references = {}
def setObject(self, counter, obj):
if self.protocol.debugReceive:
print "setObject(%s): %s{%s}" % (counter, obj, id(obj))
self.references[counter] = obj
def getObject(self, counter):
obj = self.references.get(counter)
if self.protocol.debugReceive:
print "getObject(%s) -> %s{%s}" % (counter, obj, id(obj))
return obj
class LeafUnslicer(BaseUnslicer):
# inherit from this to reject any child nodes
# .checkToken in LeafUnslicer subclasses should reject OPEN tokens
def doOpen(self, opentype):
raise Violation("'%s' does not accept sub-objects" % self)
# References are special enough to put here instead of slicers/
class ReferenceSlicer(BaseSlicer):
# this is created explicitly, not as an adapter
opentype = ('reference',)
trackReferences = False
def __init__(self, refid):
assert type(refid) is int
self.refid = refid
def sliceBody(self, streamable, banana):
yield self.refid
class ReferenceUnslicer(LeafUnslicer):
opentype = ('reference',)
constraint = None
finished = False
def setConstraint(self, constraint):
self.constraint = constraint
def checkToken(self, typebyte,size):
if typebyte != tokens.INT:
raise BananaError("ReferenceUnslicer only accepts INTs")
def receiveChild(self, obj, ready_deferred=None):
assert not isinstance(obj, Deferred)
assert ready_deferred is None
if self.finished:
raise BananaError("ReferenceUnslicer only accepts one int")
self.obj = self.protocol.getObject(obj)
self.finished = True
# assert that this conforms to the constraint
if self.constraint:
self.constraint.checkObject(self.obj, True)
# TODO: it might be a Deferred, but we should know enough about the
# incoming value to check the constraint. This requires a subclass
# of Deferred which can give us the metadata.
def receiveClose(self):
return self.obj, None
|