def test_simple(self): t = Terminator() assert t.lookup("name") is None new_attr = t.add_attribute("name") a1 = new_attr.lookup("name") assert a1 is new_attr a2 = t.add_attribute("name") assert a2 is a1 a3 = a1.add_attribute("name2") assert a3.lookup("name") is a2 assert a3.lookup("name2") is a3 assert a3.lookup("xyz") is None assert a3.index == 1 assert a2.index == 0 assert t.index == 0
def test_simple(self): t = Terminator(klass) assert t.lookup("name") is None new_attr = t.add_attribute("name") a1 = new_attr.lookup("name") assert a1 is new_attr a2 = t.add_attribute("name") assert a2 is a1 a3 = a1.add_attribute("name2") assert a3.lookup("name") is a2 assert a3.lookup("name2") is a3 assert a3.lookup("xyz") is None assert a3.index == 1 assert a2.index == 0 assert t.index == 0
def test_getallkeys(self): t = Terminator(klass) a = t.add_attribute("a") a = a.add_attribute("b") a = a.add_attribute("c") assert a.klass is klass assert a.get_all_keys() == ["a", "b", "c"]
def test_delattr_2(self): t = Terminator() a = t.add_attribute("a") a1 = a.add_attribute("b") a2 = a1.add_attribute("c") assert a2.lookup("c").index == 2 a3, _ = a2.del_attribute("a") assert a3.lookup("a") is None assert a3.lookup("b").index == 0 assert a3.lookup("c").index == 1
class ClassBase(AbstractFunction, AccessMixin): access_flags = 0 extends_name = None base_interface_names = None constructor_method = None method__clone = None method__get = None method__set = None method__unset = None method__isset = None method__call = None method__callstatic = None method__tostring = None method__invoke = None custom_instance_class = None is_iterator = False is_array_access = False immediate_parents = None parentclass = None _all_nonstatic_special_properties = None # lazy _immutable_fields_ = ["custom_instance_class", "constructor_method", "parentclass"] def __init__(self, name): self.name = name self.constants_w = {} self.properties = OrderedDict() self.methods = OrderedDict() self.all_parents = {self.get_identifier(): None} # classes and intfs self.base_map = Terminator() self.initial_storage_w = None def __repr__(self): if self.is_interface(): cat = "Interface" else: cat = "Class" return "<%s %r>" % (cat, self.name) def _init_constructor(self): if "__construct" in self.methods: method = self.methods["__construct"] elif self.get_identifier() in self.methods: method = self.methods[self.get_identifier()] else: return if method.is_static(): raise CompilerError("Constructor %s cannot be static" % (method.repr())) self.constructor_method = method def _collect_all_methods(self): all_methods = [] seen = {} seenclasses = {} seenclasses[None] = None pending = [self] while len(pending) > 0: fromclass = pending.pop(0) if fromclass in seenclasses: continue seenclasses[fromclass] = None for meth_id, m in fromclass.methods.iteritems(): if meth_id not in seen: seen[meth_id] = None all_methods.append(m) pending.extend(fromclass.immediate_parents) return all_methods def _check_abstract_methods(self): if self.is_abstract(): return all_methods = self._collect_all_methods() abstract_methods = [] for m in all_methods: if m.is_abstract(): abstract_methods.append("%s::%s" % (m.getclass().name, m.get_name())) if abstract_methods: msg = _msg_abstract(self.name, abstract_methods) raise ClassDeclarationError(msg) def _inherit_method(self, parent_method): meth_id = parent_method.get_identifier() if meth_id not in self.methods: self.methods[meth_id] = parent_method else: method = self.methods[meth_id] self._check_inheritance(method, parent_method) def _check_inheritance(self, method, parent_method): if parent_method.is_final(): raise ClassDeclarationError("Cannot override final method %s" % parent_method.repr()) if parent_method.is_public() and not method.is_public(): raise ClassDeclarationError( "Access level to %s must be public (as in class %s)" % (method.repr(), parent_method.getclass().name) ) if parent_method.is_protected() and method.is_private(): raise ClassDeclarationError( "Access level to %s must be protected (as in class %s) or " "weaker" % (method.repr(), parent_method.getclass().name) ) if method.is_static() and not parent_method.is_static(): raise ClassDeclarationError( "Cannot make non static method %s static in class %s" % (parent_method.repr(), method.getclass().name) ) if not method.is_static() and parent_method.is_static(): raise ClassDeclarationError( "Cannot make static method %s non static in class %s" % (parent_method.repr(), method.getclass().name) ) def _make_property(self, prop, w_initial_value): if isinstance(prop, tuple): name, access_flags = prop p = Property(name, self, access_flags, w_initial_value) self.properties[name] = p elif not we_are_translated(): # compile time only prop = prop.build(self) self.properties[prop.name] = prop def _inherit_property(self, prop): if prop.is_private(): return name = prop.name if name not in self.properties: self.properties[name] = prop.copy() return derived = self.properties[name] if derived.is_static() and not prop.is_static(): raise ClassDeclarationError("Cannot redeclare non static %s as static %s" % (prop.repr(), derived.repr())) if not derived.is_static() and prop.is_static(): raise ClassDeclarationError("Cannot redeclare static %s as non static %s" % (prop.repr(), derived.repr())) if prop.is_public() and not derived.is_public(): raise ClassDeclarationError( "Access level to %s must be public " "(as in class %s)" % (derived.repr(), prop.klass.name) ) if prop.is_protected() and derived.is_private(): raise ClassDeclarationError( "Access level to %s must be protected " "(as in class %s) or weaker" % (derived.repr(), prop.klass.name) ) def _init_magic_methods(self, interp): for name in magic_methods_unrolled: if name in self.methods: method = self.methods[name] else: continue if name == "__call": if method.getclass() == self and (method.is_static() or not method.is_public()): interp.warn("The magic method __call() must have " "public visibility and cannot be static") setattr(self, "method" + name, method) def _create_initial_storage(self, space): l = [] for p in self.properties.itervalues(): if not p.is_static() and not p.is_special and p.klass is self: w_val = p.value.eval_static(space) self.base_map = self.base_map.add_attribute(p.mangle_name()) assert self.base_map.index == len(l) l.append(w_val) if self.parentclass is not None: parent = self.parentclass if parent.initial_storage_w is None: parent._create_initial_storage(space) allattrs = parent.base_map.get_all_attrs() allmykeys = self.base_map.get_all_keys() d = {} for key in allmykeys: d[key] = None for parent_attr in allattrs: key = parent_attr.name w_value = parent.initial_storage_w[parent_attr.index] if key.startswith("\x00*\x00") and key[3:] in d: continue if key in d: continue self.base_map = self.base_map.add_attribute(key) l.append(w_value) self.initial_storage_w = l[:] # make it non-resizable def lookup_property_name(self, TYPE, interp, this, name, contextclass, w_newvalue=None, givenotice=True): # Returns the name to use for accessing '$a->name' on instances: # usually just 'name', but may be mangled in case it's a non-static # protected or private attribute. This also does access checks and # raises if forbidden. Note that a static member has an effect here # too, even though it's not actually used later, as in PHP. # (All lookup() methods may raise a VisibilityError.) property = self._lookup_property(name, contextclass, not interp.allow_direct_class_access) if property is None: return name elif givenotice and property.is_static(): interp.strict("Accessing static property %s as non static" % property.repr()) # the following can raise SpecialPropertyReturn, otherwise does # nothing in case it's a builtin property property.special_lookup(TYPE, interp, this, w_newvalue) return property.mangle_name() def can_access_protected_properties_from(self, contextclass): return contextclass is not None and (self.is_parent_of(contextclass) or contextclass.is_parent_of(self)) def check_access_and_demangle_property(self, name, contextclass): if not name.startswith("\x00"): return name i = name.find("\x00", 1) if i < 0: return name # malformed name? prvname = name[1:i] # protected property if prvname == "*": if self.can_access_protected_properties_from(contextclass): return name[i + 1 :] return None # private property if contextclass is not None and contextclass.is_parent_of(self) and prvname == contextclass.name: return name[i + 1 :] else: return None def get_methods(self, contextclass): """Returns a list of accessible method names for the given context.""" methods = [] for method_name, method in self.methods.iteritems(): try: self._visibility_check(method, method_name, contextclass) methods.append(method.get_name()) except VisibilityError: pass return methods def lookup_staticmember(self, name, contextclass, check_visibility): property = self._lookup_property(name, contextclass, check_visibility) if property is not None and property.is_static(): return property return None def _lookup_property(self, name, contextclass, check_visibility): self = jit.hint(self, promote=True) # name = jit.hint(name, promote=True) should be promote_string return self._lookup_property_elidable(name, contextclass, check_visibility) @jit.elidable_promote() def _lookup_property_elidable(self, name, contextclass, check_visibility): # Implementation of the rules described in the big comment in # instanceobject.py if contextclass is not None and contextclass.is_parent_of(self): try: result = contextclass.properties[name] except KeyError: pass else: if result.is_private(): return result try: result = self.properties[name] except KeyError: return None if check_visibility: self._visibility_check(result, name, contextclass) return result def locate_static_method(self, name, contextclass, check_visibility): try: return self._lookup_static_method(name, contextclass, check_visibility) except VisibilityError: callstatic = self.method__callstatic if callstatic is None: raise return Method(W_UnderUnderCall(name, callstatic.method_func), callstatic.access_flags, self) @jit.elidable_promote() def _lookup_static_method(self, name, contextclass, check_visibility): key = name.lower() try: result = self.methods[key] except KeyError: raise VisibilityError("undefined", self, name, None) if check_visibility: self._visibility_check(result, name, contextclass) return result def locate_method(self, name, contextclass, searchclass=None, check_visibility=True): if searchclass is None: searchclass = self try: return self._lookup_method(name, contextclass, check_visibility) except VisibilityError: call = searchclass.method__call if call is None: raise return Method(W_UnderUnderCall(name, call.method_func), call.access_flags, searchclass) @jit.elidable_promote() def _lookup_method(self, name, contextclass, check_visibility): key = name.lower() if contextclass is not None and contextclass.is_parent_of(self): try: result = contextclass.methods[key] if result.is_private(): if check_visibility: contextclass._visibility_check(result, name, contextclass) return result except KeyError: pass try: result = self.methods[key] except KeyError: raise VisibilityError("undefined", self, name, None) if check_visibility: self._visibility_check(result, name, contextclass) return result def _visibility_check(self, result, name, contextclass): if result.is_protected(): if not self.can_access_protected_properties_from(contextclass): raise VisibilityError("protected", result.getclass(), name, contextclass) elif result.is_private(): if result.getclass() is not contextclass: raise VisibilityError("private", result.getclass(), name, contextclass) @jit.elidable def is_parent_of(self, otherclass): while otherclass is not self: otherclass = otherclass.parentclass if otherclass is None: return False return True @jit.elidable_promote() def is_subclass_of_class_or_intf_name(self, parent_or_interface_name): return parent_or_interface_name.lower() in self.all_parents def get_super_method(self, methname, superclass, check_visibility): return self.locate_method(methname, superclass, searchclass=superclass, check_visibility=check_visibility) def _static_call_warning(self, interp, method, assuming_this): interp.strict("Non-static method %s should not be " "called statically%s" % (method.repr(), assuming_this)) def getstaticmeth(self, methname, contextclass, context_w_this, interp): check_vis = not interp.allow_direct_class_access if contextclass is not None and self.is_parent_of(contextclass): return self.get_super_method(methname, contextclass, check_vis) method = self.locate_static_method(methname, contextclass, check_vis) if not method.is_static(): if context_w_this is None: self._static_call_warning(interp, method, "") elif not self.is_parent_of(context_w_this.klass): self._static_call_warning(interp, method, ", assuming $this from incompatible context") return method def lookup_w_constant(self, space, constantname): try: w_value = self.constants_w[constantname] except KeyError: return None return w_value.eval_static(space) def check_constructor_from_context(self, interp, contextclass): if self.is_abstract(): if self.is_interface(): cat = "interface" else: cat = "abstract class" interp.fatal("Cannot instantiate %s %s" % (cat, self.name)) method = self.constructor_method visibility_error = "" # please the annotator if method is None: return # no constructor at all elif method.is_public(): return # public constructor elif method.is_protected(): if contextclass is not None and (self.is_parent_of(contextclass) or contextclass.is_parent_of(self)): return # protected constructor visibility_error = "protected" elif method.is_private(): if contextclass is self: return # private constructor visibility_error = "private" interp.fatal("Call to %s %s from invalid context" % (visibility_error, method.repr())) def needs_ref(self, i): method = self.constructor_method if method is not None: return method.method_func.needs_ref(i) return False def get_fresh_storage_w(self, space): return self.get_initial_storage_w(space)[:] def get_initial_storage_w(self, space): if self.initial_storage_w is None: self._create_initial_storage(space) return self.initial_storage_w def call_args(self, interp, args_w, w_this=None, thisclass=None, closureargs=None): storage_w = self.get_fresh_storage_w(interp.space) w_res = self.create_instance(interp, storage_w) method = self.constructor_method if method is not None: assert isinstance(method, Method) method.method_func.call_args(interp, args_w, w_this=w_res, thisclass=self) return w_res def create_instance(self, interp, storage_w): if self.custom_instance_class is not None: w_res = self.custom_instance_class(self, storage_w) w_res.setup(interp) else: w_res = W_InstanceObject(self, storage_w) return w_res def get_empty_instance(self, space): return self.create_instance(space.ec.interpreter, self.get_fresh_storage_w(space)) def do_clone(self, interp, w_obj1, contextclass): assert isinstance(w_obj1, W_InstanceObject) w_res = w_obj1._clone(interp) method = self.method__clone if method is not None: try: self._visibility_check(method, method.get_identifier(), contextclass) except VisibilityError as exc: exc.reraise_magic(interp) if method is not None: method.method_func.call_args(interp, [], w_this=w_res, thisclass=self) return w_res def do_get(self, interp, w_obj, attr, isref): key = (w_obj, attr) recursion = rec_custom_get_set if key in recursion: interp.notice("Undefined property: %s::$%s" % (self.name, attr)) w_res = interp.space.w_Null else: recursion[key] = None try: method = self.method__get space = interp.space w_res = method.method_func.call_args(interp, [space.newstr(attr)], w_this=w_obj, thisclass=self) finally: del recursion[key] if isref and not isinstance(w_res, W_Reference): interp.notice("Indirect modification of overloaded property " "%s::$%s has no effect" % (self.name, attr)) w_res = W_Reference(w_res) return w_res def do_set(self, interp, w_obj, attr, w_value): key = (w_obj, attr) space = interp.space recursion = rec_custom_get_set if key in recursion: return False recursion[key] = None try: method = self.method__set method.method_func.call_args(interp, [space.newstr(attr), w_value], w_this=w_obj, thisclass=self) finally: del recursion[key] return True def do_unset(self, interp, w_obj, attr): key = (w_obj, attr) recursion = rec_custom_get_set if key in recursion: return False recursion[key] = None try: method = self.method__unset method.method_func.call_args(interp, [interp.space.newstr(attr)], w_this=w_obj, thisclass=self) finally: del recursion[key] return True def do_isset(self, interp, w_obj, attr): key = (w_obj, attr) recursion = rec_custom_get_set if key in recursion: return None recursion[key] = None try: method = self.method__isset w_res = method.method_func.call_args(interp, [interp.space.newstr(attr)], w_this=w_obj, thisclass=self) finally: del recursion[key] return w_res def do_tostring(self, space, w_obj, quiet=False): method = self.method__tostring if method is None: if not quiet: space.ec.recoverable_fatal("Object of class %s could not be " "converted to string" % self.name) return space.newstr("") else: return None try: w_res = method.method_func.call_args(space.ec.interpreter, [], w_this=w_obj, thisclass=self) except PHPException: raise space.ec.fatal("Method %s::__toString() must not " "throw an exception" % self.name) if not isinstance(w_res, W_StringObject): space.ec.catchable_fatal("Method %s::__toString() must " "return a string value" % self.name) return w_res def get_invoke_method(self, w_obj): method = self.method__invoke if method is None: return None return W_InvokeCall(self, method.method_func, w_obj) def get_all_nonstatic_special_properties(self): result = self._all_nonstatic_special_properties if result is None: lst = [] for base in self.immediate_parents: lst.extend(base.get_all_nonstatic_special_properties()) for prop in self.properties.values(): if not prop.is_static() and prop.is_special: lst.append((prop.mangle_name(), prop)) result = lst[:] self._all_nonstatic_special_properties = result return result
class ClassBase(AbstractFunction, AccessMixin): access_flags = 0 base_interface_names = None constructor_method = None method__clone = None method__get = None method__set = None method__unset = None method__isset = None method__call = None method__callstatic = None method__tostring = None method__invoke = None custom_instance_class = None is_iterator = False is_array_access = False immediate_parents = None parentclass = None _all_nonstatic_special_properties = None # lazy _immutable_fields_ = [ 'custom_instance_class', 'constructor_method', 'parentclass' ] def __init__(self, name): self.name = name self.constants_w = {} self.properties = OrderedDict() self.methods = OrderedDict() self.all_parents = {self.get_identifier(): None} # classes and intfs self.base_map = Terminator() self.initial_storage_w = None def __repr__(self): if self.is_interface(): cat = 'Interface' else: cat = 'Class' return '<%s %r>' % (cat, self.name) def _init_constructor(self): if '__construct' in self.methods: method = self.methods['__construct'] elif self.get_identifier() in self.methods: method = self.methods[self.get_identifier()] else: return if method.is_static(): raise CompilerError("Constructor %s cannot be static" % (method.repr())) self.constructor_method = method def _collect_all_methods(self): all_methods = [] seen = {} seenclasses = {} seenclasses[None] = None pending = [self] while len(pending) > 0: fromclass = pending.pop(0) if fromclass in seenclasses: continue seenclasses[fromclass] = None for meth_id, m in fromclass.methods.iteritems(): if meth_id not in seen: seen[meth_id] = None all_methods.append(m) pending.extend(fromclass.immediate_parents) return all_methods def _check_abstract_methods(self): if self.is_abstract(): return all_methods = self._collect_all_methods() abstract_methods = [] for m in all_methods: if m.is_abstract(): abstract_methods.append("%s::%s" % (m.getclass().name, m.get_name())) if abstract_methods: msg = _msg_abstract(self.name, abstract_methods) raise ClassDeclarationError(msg) def _inherit_method(self, parent_method): meth_id = parent_method.get_identifier() if meth_id not in self.methods: self.methods[meth_id] = parent_method else: method = self.methods[meth_id] self._check_inheritance(method, parent_method) def _check_inheritance(self, method, parent_method): if parent_method.is_final(): raise ClassDeclarationError("Cannot override final method %s" % parent_method.repr()) if parent_method.is_public() and not method.is_public(): raise ClassDeclarationError( "Access level to %s must be public (as in class %s)" % (method.repr(), parent_method.getclass().name)) if parent_method.is_protected() and method.is_private(): raise ClassDeclarationError( "Access level to %s must be protected (as in class %s) or " "weaker" % (method.repr(), parent_method.getclass().name)) if method.is_static() and not parent_method.is_static(): raise ClassDeclarationError( "Cannot make non static method %s static in class %s" % (parent_method.repr(), method.getclass().name)) if not method.is_static() and parent_method.is_static(): raise ClassDeclarationError( "Cannot make static method %s non static in class %s" % (parent_method.repr(), method.getclass().name)) def _make_property(self, prop, w_initial_value): if isinstance(prop, tuple): name, access_flags = prop p = Property(name, self, access_flags, w_initial_value) self.properties[name] = p elif not we_are_translated(): # compile time only prop = prop.build(self) self.properties[prop.name] = prop def _inherit_property(self, prop): if prop.is_private(): return name = prop.name if name not in self.properties: self.properties[name] = prop.copy() return derived = self.properties[name] if derived.is_static() and not prop.is_static(): raise ClassDeclarationError( "Cannot redeclare non static %s as static %s" % (prop.repr(), derived.repr())) if not derived.is_static() and prop.is_static(): raise ClassDeclarationError( "Cannot redeclare static %s as non static %s" % (prop.repr(), derived.repr())) if prop.is_public() and not derived.is_public(): raise ClassDeclarationError("Access level to %s must be public " "(as in class %s)" % (derived.repr(), prop.klass.name)) if prop.is_protected() and derived.is_private(): raise ClassDeclarationError("Access level to %s must be protected " "(as in class %s) or weaker" % (derived.repr(), prop.klass.name)) def _init_magic_methods(self, interp): for name in magic_methods_unrolled: if name in self.methods: method = self.methods[name] else: continue if name == '__call': if (method.getclass() == self and (method.is_static() or not method.is_public())): interp.warn("The magic method __call() must have " "public visibility and cannot be static") setattr(self, 'method' + name, method) def _create_initial_storage(self, space): l = [] for p in self.properties.itervalues(): if not p.is_static() and not p.is_special and p.klass is self: w_val = p.value.eval_static(space) self.base_map = self.base_map.add_attribute(p.mangle_name()) assert self.base_map.index == len(l) l.append(w_val) if self.parentclass is not None: parent = self.parentclass if parent.initial_storage_w is None: parent._create_initial_storage(space) allattrs = parent.base_map.get_all_attrs() allmykeys = self.base_map.get_all_keys() d = {} for key in allmykeys: d[key] = None for parent_attr in allattrs: key = parent_attr.name w_value = parent.initial_storage_w[parent_attr.index] if (key.startswith('\x00*\x00') and key[3:] in d): continue if key in d: continue self.base_map = self.base_map.add_attribute(key) l.append(w_value) self.initial_storage_w = l[:] # make it non-resizable def lookup_property_name(self, TYPE, interp, this, name, contextclass, w_newvalue=None, givenotice=True): # Returns the name to use for accessing '$a->name' on instances: # usually just 'name', but may be mangled in case it's a non-static # protected or private attribute. This also does access checks and # raises if forbidden. Note that a static member has an effect here # too, even though it's not actually used later, as in PHP. # (All lookup() methods may raise a VisibilityError.) property = self._lookup_property(name, contextclass, not interp.allow_direct_class_access) if property is None: return name elif givenotice and property.is_static(): interp.strict("Accessing static property %s as non static" % property.repr()) # the following can raise SpecialPropertyReturn, otherwise does # nothing in case it's a builtin property property.special_lookup(TYPE, interp, this, w_newvalue) return property.mangle_name() def can_access_protected_properties_from(self, contextclass): return contextclass is not None and (self.is_parent_of(contextclass) or contextclass.is_parent_of(self)) def check_access_and_demangle_property(self, name, contextclass): if not name.startswith('\x00'): return name i = name.find('\x00', 1) if i < 0: return name # malformed name? prvname = name[1:i] # protected property if prvname == "*": if self.can_access_protected_properties_from(contextclass): return name[i + 1:] return None # private property if (contextclass is not None and contextclass.is_parent_of(self) and prvname == contextclass.name): return name[i + 1:] else: return None def get_methods(self, contextclass): '''Returns a list of accessible method names for the given context.''' methods = [] for method_name, method in self.methods.iteritems(): try: self._visibility_check(method, method_name, contextclass) methods.append(method.get_name()) except VisibilityError: pass return methods def lookup_staticmember(self, name, contextclass, check_visibility): property = self._lookup_property(name, contextclass, check_visibility) if property is not None and property.is_static(): return property return None def _lookup_property(self, name, contextclass, check_visibility): self = jit.hint(self, promote=True) #name = jit.hint(name, promote=True) should be promote_string return self._lookup_property_elidable(name, contextclass, check_visibility) @jit.elidable_promote() def _lookup_property_elidable(self, name, contextclass, check_visibility): # Implementation of the rules described in the big comment in # instanceobject.py if (contextclass is not None and contextclass.is_parent_of(self)): try: result = contextclass.properties[name] except KeyError: pass else: if result.is_private(): return result try: result = self.properties[name] except KeyError: return None if check_visibility: self._visibility_check(result, name, contextclass) return result def locate_static_method(self, name, contextclass, check_visibility): try: return self._lookup_static_method(name, contextclass, check_visibility) except VisibilityError: callstatic = self.method__callstatic if callstatic is None: raise return Method(W_UnderUnderCall(name, callstatic.method_func), callstatic.access_flags, self) @jit.elidable_promote() def _lookup_static_method(self, name, contextclass, check_visibility): key = name.lower() try: result = self.methods[key] except KeyError: raise VisibilityError("undefined", self, name, None) if check_visibility: self._visibility_check(result, name, contextclass) return result def locate_method(self, name, contextclass, searchclass=None, check_visibility=True): if searchclass is None: searchclass = self try: return self._lookup_method(name, contextclass, check_visibility) except VisibilityError: call = searchclass.method__call if call is None: raise return Method(W_UnderUnderCall(name, call.method_func), call.access_flags, searchclass) @jit.elidable_promote() def _lookup_method(self, name, contextclass, check_visibility): key = name.lower() if (contextclass is not None and contextclass.is_parent_of(self)): try: result = contextclass.methods[key] if result.is_private(): if check_visibility: contextclass._visibility_check(result, name, contextclass) return result except KeyError: pass try: result = self.methods[key] except KeyError: raise VisibilityError("undefined", self, name, None) if check_visibility: self._visibility_check(result, name, contextclass) return result def _visibility_check(self, result, name, contextclass): if result.is_protected(): if not self.can_access_protected_properties_from(contextclass): raise VisibilityError("protected", result.getclass(), name, contextclass) elif result.is_private(): if result.getclass() is not contextclass: raise VisibilityError("private", result.getclass(), name, contextclass) @jit.elidable def is_parent_of(self, otherclass): while otherclass is not self: otherclass = otherclass.parentclass if otherclass is None: return False return True @jit.elidable_promote() def is_subclass_of_class_or_intf_name(self, parent_or_interface_name): return parent_or_interface_name.lower() in self.all_parents def get_super_method(self, methname, superclass, check_visibility): return self.locate_method(methname, superclass, searchclass=superclass, check_visibility=check_visibility) def _static_call_warning(self, interp, method, assuming_this): interp.strict("Non-static method %s should not be " "called statically%s" % (method.repr(), assuming_this)) def getstaticmeth(self, methname, contextclass, context_w_this, interp): check_vis = not interp.allow_direct_class_access if contextclass is not None and self.is_parent_of(contextclass): return self.get_super_method(methname, contextclass, check_vis) method = self.locate_static_method(methname, contextclass, check_vis) if not method.is_static(): if context_w_this is None: self._static_call_warning(interp, method, "") elif not self.is_parent_of(context_w_this.klass): self._static_call_warning( interp, method, ", assuming $this from incompatible context") return method def lookup_w_constant(self, space, constantname): try: w_value = self.constants_w[constantname] except KeyError: return None return w_value.eval_static(space) def check_constructor_from_context(self, interp, contextclass): if self.is_abstract(): if self.is_interface(): cat = "interface" else: cat = "abstract class" interp.fatal("Cannot instantiate %s %s" % (cat, self.name)) method = self.constructor_method visibility_error = "" # please the annotator if method is None: return # no constructor at all elif method.is_public(): return # public constructor elif method.is_protected(): if contextclass is not None and (self.is_parent_of(contextclass) or contextclass.is_parent_of(self)): return # protected constructor visibility_error = "protected" elif method.is_private(): if contextclass is self: return # private constructor visibility_error = "private" interp.fatal("Call to %s %s from invalid context" % (visibility_error, method.repr())) def needs_ref(self, i): method = self.constructor_method if method is not None: return method.method_func.needs_ref(i) return False def get_fresh_storage_w(self, space): return self.get_initial_storage_w(space)[:] def get_initial_storage_w(self, space): if self.initial_storage_w is None: self._create_initial_storage(space) return self.initial_storage_w def call_args(self, interp, args_w, w_this=None, thisclass=None, closureargs=None): storage_w = self.get_fresh_storage_w(interp.space) w_res = self.create_instance(interp, storage_w) method = self.constructor_method if method is not None: assert isinstance(method, Method) method.method_func.call_args(interp, args_w, w_this=w_res, thisclass=self) return w_res def create_instance(self, interp, storage_w): if self.custom_instance_class is not None: w_res = self.custom_instance_class(self, storage_w) w_res.setup(interp) else: w_res = W_InstanceObject(self, storage_w) return w_res def get_empty_instance(self, space): return self.create_instance(space.ec.interpreter, self.get_fresh_storage_w(space)) def do_clone(self, interp, w_obj1, contextclass): assert isinstance(w_obj1, W_InstanceObject) w_res = w_obj1._clone(interp) method = self.method__clone if method is not None: try: self._visibility_check(method, method.get_identifier(), contextclass) except VisibilityError as exc: exc.reraise_magic(interp) if method is not None: method.method_func.call_args(interp, [], w_this=w_res, thisclass=self) return w_res def do_get(self, interp, w_obj, attr, isref): key = (w_obj, attr) recursion = rec_custom_get_set if key in recursion: interp.notice("Undefined property: %s::$%s" % (self.name, attr)) w_res = interp.space.w_Null else: recursion[key] = None try: method = self.method__get space = interp.space w_res = method.method_func.call_args(interp, [space.newstr(attr)], w_this=w_obj, thisclass=self) finally: del recursion[key] if isref and not isinstance(w_res, W_Reference): interp.notice("Indirect modification of overloaded property " "%s::$%s has no effect" % (self.name, attr)) w_res = W_Reference(w_res) return w_res def do_set(self, interp, w_obj, attr, w_value): key = (w_obj, attr) space = interp.space recursion = rec_custom_get_set if key in recursion: return False recursion[key] = None try: method = self.method__set method.method_func.call_args(interp, [space.newstr(attr), w_value], w_this=w_obj, thisclass=self) finally: del recursion[key] return True def do_unset(self, interp, w_obj, attr): key = (w_obj, attr) recursion = rec_custom_get_set if key in recursion: return False recursion[key] = None try: method = self.method__unset method.method_func.call_args(interp, [interp.space.newstr(attr)], w_this=w_obj, thisclass=self) finally: del recursion[key] return True def do_isset(self, interp, w_obj, attr): key = (w_obj, attr) recursion = rec_custom_get_set if key in recursion: return None recursion[key] = None try: method = self.method__isset w_res = method.method_func.call_args(interp, [interp.space.newstr(attr)], w_this=w_obj, thisclass=self) finally: del recursion[key] return w_res def do_tostring(self, space, w_obj, quiet=False): method = self.method__tostring if method is None: if not quiet: space.ec.recoverable_fatal("Object of class %s could not be " "converted to string" % self.name) return space.newstr("") else: return None try: w_res = method.method_func.call_args(space.ec.interpreter, [], w_this=w_obj, thisclass=self) except PHPException: raise space.ec.fatal("Method %s::__toString() must not " "throw an exception" % self.name) if not isinstance(w_res, W_StringObject): space.ec.catchable_fatal("Method %s::__toString() must " "return a string value" % self.name) return w_res def get_invoke_method(self, w_obj): method = self.method__invoke if method is None: return None return W_InvokeCall(self, method.method_func, w_obj) def get_all_nonstatic_special_properties(self): result = self._all_nonstatic_special_properties if result is None: lst = [] for base in self.immediate_parents: lst.extend(base.get_all_nonstatic_special_properties()) for prop in self.properties.values(): if not prop.is_static() and prop.is_special: lst.append((prop.mangle_name(), prop)) result = lst[:] self._all_nonstatic_special_properties = result return result
class ClassBase(AbstractFunction): access_flags = 0 extends_name = None base_interface_names = None constructor_method = None method__clone = None method__get = None method__set = None method__unset = None method__isset = None method__call = None method__callstatic = None method__tostring = None method__invoke = None custom_instance_class = None is_iterator = False immediate_parents = None parentclass = None def __init__(self, name): self.name = name self.identifier = name.lower() self.properties = OrderedDict() self.property_decl = [] self.methods = OrderedDict() self.constants_w = {} self.all_parents = {self.identifier: None} #classes and intfs self.base_map = Terminator() self.initial_storage_w = None def __repr__(self): if self.is_interface(): cat = 'Interface' else: cat = 'Class' return '<%s %r>' % (cat, self.name) def is_interface(self): return (self.access_flags & consts.ACC_INTERFACE) != 0 def is_final(self): return bool(self.access_flags & consts.ACC_FINAL) def is_abstract(self): return bool(self.access_flags & consts.ACC_ABSTRACT) def _init_constructor(self): if '__construct' in self.methods: method = self.methods['__construct'] elif self.identifier in self.methods: method = self.methods[self.identifier] else: return if method.is_static(): raise CompilerError("Constructor %s cannot be static" % (method.repr())) self.constructor_method = method def _make_property(self, prop, w_initial_value): if isinstance(prop, tuple): name, access_flags = prop p = Property(name, self, access_flags) self.properties[name] = p self.property_decl.append(p) if not p.is_static(): p.value = w_initial_value else: if not isinstance(w_initial_value, W_Object): r_value = w_initial_value else: r_value = W_Reference(w_initial_value) p.r_value = r_value elif not we_are_translated(): # compile time only prop = prop.build(self) self.properties[prop.name] = prop self.property_decl.append(prop) def _collect_all_methods(self): all_methods = [] seen = {} seenclasses = {} seenclasses[None] = None pending = [self] while len(pending) > 0: fromclass = pending.pop(0) if fromclass in seenclasses: continue seenclasses[fromclass] = None for meth_id, m in fromclass.methods.iteritems(): if meth_id not in seen: seen[meth_id] = None all_methods.append(m) pending.extend(fromclass.immediate_parents) return all_methods def _check_abstract_methods(self): if self.is_abstract(): return all_methods = self._collect_all_methods() abstract_methods = [] for m in all_methods: if m.is_abstract(): abstract_methods.append("%s::%s" % (m.getclass().name, m.get_name())) if abstract_methods: msg = _msg_abstract(self.name, abstract_methods) raise ClassDeclarationError(msg) def _inherit_method(self, parent_method): meth_id = parent_method.get_identifier() if meth_id not in self.methods: self.methods[meth_id] = parent_method else: method = self.methods[meth_id] self._check_inheritance(method, parent_method) def _check_inheritance(self, method, parent_method): if parent_method.is_final(): raise ClassDeclarationError("Cannot override final method %s" % parent_method.repr()) if parent_method.is_public() and not method.is_public(): raise ClassDeclarationError( "Access level to %s must be public (as in class %s)" % (method.repr(), parent_method.getclass().name)) if parent_method.is_protected() and method.is_private(): raise ClassDeclarationError( "Access level to %s must be protected (as in class %s) or " "weaker" % (method.repr(), parent_method.getclass().name)) if method.is_static() and not parent_method.is_static(): raise ClassDeclarationError( "Cannot make non static method %s static in class %s" % (parent_method.repr(), method.getclass().name)) if not method.is_static() and parent_method.is_static(): raise ClassDeclarationError( "Cannot make static method %s non static in class %s" % (parent_method.repr(), method.getclass().name)) def _inherit_property(self, prop): if prop.is_private(): return name = prop.name if name not in self.properties: self.properties[name] = prop.copy_to(self) return derived = self.properties[name] if derived.is_static() and not prop.is_static(): raise ClassDeclarationError( "Cannot redeclare non static %s as static %s" % (prop.repr(), derived.repr())) if not derived.is_static() and prop.is_static(): raise ClassDeclarationError( "Cannot redeclare static %s as non static %s" % (prop.repr(), derived.repr())) if prop.is_public() and not derived.is_public(): raise ClassDeclarationError("Access level to %s must be public " "(as in class %s)" % (derived.repr(), prop.klass.name)) if prop.is_protected() and derived.is_private(): raise ClassDeclarationError("Access level to %s must be protected " "(as in class %s) or weaker" % (derived.repr(), prop.klass.name)) def _init_magic_methods(self): for name in magic_methods_unrolled: if name in self.methods: method = self.methods[name] else: continue if name == '__call': if (method.getclass() == self and (method.is_static() or not method.is_public())): self.space.ec.warn( "The magic method __call() must have " "public visibility and cannot be static") setattr(self, 'method' + name, method) def _create_initial_storage(self, space): l = [] for p in self.property_decl: if not p.is_static() and not p.is_special: w_val = p.value.eval_static(space) self.base_map = self.base_map.add_attribute(p.mangle_name()) assert self.base_map.index == len(l) l.append(w_val) if self.parentclass is not None: parent = self.parentclass if parent.initial_storage_w is None: parent._create_initial_storage(space) allattrs = parent.base_map.get_all_attrs() allmykeys = self.base_map.get_all_keys() d = {} for key in allmykeys: d[key] = None for parent_attr in allattrs: key = parent_attr.name w_value = parent.initial_storage_w[parent_attr.index] if (key.startswith('\x00*\x00') and key[3:] in d): continue if key in d: continue self.base_map = self.base_map.add_attribute(key) l.append(w_value) self.initial_storage_w = l[:] # make it non-resizable def get_identifier(self): return self.identifier def lookup_property_name(self, TYPE, interp, this, name, contextclass, w_newvalue=None, givenotice=True): # Returns the name to use for accessing '$a->name' on instances: # usually just 'name', but may be mangled in case it's a non-static # protected or private attribute. This also does access checks and # raises if forbidden. Note that a static member has an effect here # too, even though it's not actually used later, as in PHP. # (All lookup() methods may raise a VisibilityError.) property = self._lookup_property(name, contextclass) if property is None: return name elif givenotice and property.is_static(): self.space.ec.strict("Accessing static property %s as non static" % property.repr()) # the following can raise SpecialPropertyReturn, otherwise does # nothing in case it's a builtin property property.special_lookup(TYPE, interp, this, w_newvalue) return property.mangle_name() def can_access_protected_properties_from(self, contextclass): return contextclass is not None and (self.is_parent_of(contextclass) or contextclass.is_parent_of(self)) def check_access_and_demangle_property(self, name, contextclass): if not name.startswith('\x00'): return name i = name.find('\x00', 1) if i < 0: return name # malformed name? prvname = name[1:i] # protected property if prvname == "*": if self.can_access_protected_properties_from(contextclass): return name[i + 1:] return None # private property if (contextclass is not None and contextclass.is_parent_of(self) and prvname == contextclass.name): return name[i + 1:] else: return None def lookup_staticmember(self, name, contextclass): try: property = self._lookup_property(name, contextclass) except VisibilityError, e: raise e.reraise_property(self.space.ec.interpreter) if property is not None and property.is_static(): return property return None
class ClassBase(AbstractFunction): access_flags = 0 extends_name = None base_interface_names = None constructor_method = None method__clone = None method__get = None method__set = None method__unset = None method__isset = None method__call = None method__callstatic = None method__tostring = None method__invoke = None custom_instance_class = None is_iterator = False immediate_parents = None parentclass = None def __init__(self, name): self.name = name self.identifier = name.lower() self.properties = OrderedDict() self.property_decl = [] self.methods = OrderedDict() self.constants_w = {} self.all_parents = {self.identifier: None} #classes and intfs self.base_map = Terminator() self.initial_storage_w = None def __repr__(self): if self.is_interface(): cat = 'Interface' else: cat = 'Class' return '<%s %r>' % (cat, self.name) def is_interface(self): return (self.access_flags & consts.ACC_INTERFACE) != 0 def is_final(self): return bool(self.access_flags & consts.ACC_FINAL) def is_abstract(self): return bool(self.access_flags & consts.ACC_ABSTRACT) def _init_constructor(self): if '__construct' in self.methods: method = self.methods['__construct'] elif self.identifier in self.methods: method = self.methods[self.identifier] else: return if method.is_static(): raise CompilerError("Constructor %s cannot be static" % (method.repr())) self.constructor_method = method def _make_property(self, prop, w_initial_value): if isinstance(prop, tuple): name, access_flags = prop p = Property(name, self, access_flags) self.properties[name] = p self.property_decl.append(p) if not p.is_static(): p.value = w_initial_value else: if not isinstance(w_initial_value, W_Object): r_value = w_initial_value else: r_value = W_Reference(w_initial_value) p.r_value = r_value elif not we_are_translated(): # compile time only prop = prop.build(self) self.properties[prop.name] = prop self.property_decl.append(prop) def _collect_all_methods(self): all_methods = [] seen = {} seenclasses = {} seenclasses[None] = None pending = [self] while len(pending) > 0: fromclass = pending.pop(0) if fromclass in seenclasses: continue seenclasses[fromclass] = None for meth_id, m in fromclass.methods.iteritems(): if meth_id not in seen: seen[meth_id] = None all_methods.append(m) pending.extend(fromclass.immediate_parents) return all_methods def _check_abstract_methods(self): if self.is_abstract(): return all_methods = self._collect_all_methods() abstract_methods = [] for m in all_methods: if m.is_abstract(): abstract_methods.append("%s::%s" % ( m.getclass().name, m.get_name())) if abstract_methods: msg = _msg_abstract(self.name, abstract_methods) raise ClassDeclarationError(msg) def _inherit_method(self, parent_method): meth_id = parent_method.get_identifier() if meth_id not in self.methods: self.methods[meth_id] = parent_method else: method = self.methods[meth_id] self._check_inheritance(method, parent_method) def _check_inheritance(self, method, parent_method): if parent_method.is_final(): raise ClassDeclarationError( "Cannot override final method %s" % parent_method.repr()) if parent_method.is_public() and not method.is_public(): raise ClassDeclarationError( "Access level to %s must be public (as in class %s)" % (method.repr(), parent_method.getclass().name)) if parent_method.is_protected() and method.is_private(): raise ClassDeclarationError( "Access level to %s must be protected (as in class %s) or " "weaker" % (method.repr(), parent_method.getclass().name)) if method.is_static() and not parent_method.is_static(): raise ClassDeclarationError( "Cannot make non static method %s static in class %s" % (parent_method.repr(), method.getclass().name)) if not method.is_static() and parent_method.is_static(): raise ClassDeclarationError( "Cannot make static method %s non static in class %s" % (parent_method.repr(), method.getclass().name)) def _inherit_property(self, prop): if prop.is_private(): return name = prop.name if name not in self.properties: self.properties[name] = prop.copy_to(self) return derived = self.properties[name] if derived.is_static() and not prop.is_static(): raise ClassDeclarationError("Cannot redeclare non static %s as static %s" % (prop.repr(), derived.repr())) if not derived.is_static() and prop.is_static(): raise ClassDeclarationError("Cannot redeclare static %s as non static %s" % (prop.repr(), derived.repr())) if prop.is_public() and not derived.is_public(): raise ClassDeclarationError("Access level to %s must be public " "(as in class %s)" % (derived.repr(), prop.klass.name)) if prop.is_protected() and derived.is_private(): raise ClassDeclarationError("Access level to %s must be protected " "(as in class %s) or weaker" % (derived.repr(), prop.klass.name)) def _init_magic_methods(self): for name in magic_methods_unrolled: if name in self.methods: method = self.methods[name] else: continue if name == '__call': if (method.getclass() == self and (method.is_static() or not method.is_public())): self.space.ec.warn("The magic method __call() must have " "public visibility and cannot be static") setattr(self, 'method' + name, method) def _create_initial_storage(self, space): l = [] for p in self.property_decl: if not p.is_static() and not p.is_special: w_val = p.value.eval_static(space) self.base_map = self.base_map.add_attribute(p.mangle_name()) assert self.base_map.index == len(l) l.append(w_val) if self.parentclass is not None: parent = self.parentclass if parent.initial_storage_w is None: parent._create_initial_storage(space) allattrs = parent.base_map.get_all_attrs() allmykeys = self.base_map.get_all_keys() d = {} for key in allmykeys: d[key] = None for parent_attr in allattrs: key = parent_attr.name w_value = parent.initial_storage_w[parent_attr.index] if (key.startswith('\x00*\x00') and key[3:] in d): continue if key in d: continue self.base_map = self.base_map.add_attribute(key) l.append(w_value) self.initial_storage_w = l[:] # make it non-resizable def get_identifier(self): return self.identifier def lookup_property_name(self, TYPE, interp, this, name, contextclass, w_newvalue=None, givenotice=True): # Returns the name to use for accessing '$a->name' on instances: # usually just 'name', but may be mangled in case it's a non-static # protected or private attribute. This also does access checks and # raises if forbidden. Note that a static member has an effect here # too, even though it's not actually used later, as in PHP. # (All lookup() methods may raise a VisibilityError.) property = self._lookup_property(name, contextclass) if property is None: return name elif givenotice and property.is_static(): self.space.ec.strict("Accessing static property %s as non static" % property.repr()) # the following can raise SpecialPropertyReturn, otherwise does # nothing in case it's a builtin property property.special_lookup(TYPE, interp, this, w_newvalue) return property.mangle_name() def can_access_protected_properties_from(self, contextclass): return contextclass is not None and ( self.is_parent_of(contextclass) or contextclass.is_parent_of(self)) def check_access_and_demangle_property(self, name, contextclass): if not name.startswith('\x00'): return name i = name.find('\x00', 1) if i < 0: return name # malformed name? prvname = name[1:i] # protected property if prvname == "*": if self.can_access_protected_properties_from(contextclass): return name[i+1:] return None # private property if (contextclass is not None and contextclass.is_parent_of(self) and prvname == contextclass.name): return name[i+1:] else: return None def lookup_staticmember(self, name, contextclass): try: property = self._lookup_property(name, contextclass) except VisibilityError, e: raise e.reraise_property(self.space.ec.interpreter) if property is not None and property.is_static(): return property return None