From 3a6a8d5c48d9b1f7e58ef14bfda2223182822503 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 15 Jun 2014 12:27:48 +0200 Subject: [PATCH] Made objects clear more handlers on deletion, also scramble some methods and all database access wrappers to avoid an object memory instance being accessed after it has been deleted. See #509. --- src/commands/default/building.py | 2 +- src/typeclasses/models.py | 26 ++++++++++++++++++++++++-- src/utils/idmapper/base.py | 10 +++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/commands/default/building.py b/src/commands/default/building.py index 6dc15be231..f040ba843c 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -542,7 +542,7 @@ class CmdDestroy(MuxCommand): # do the deletion okay = obj.delete() if not okay: - string += "\nERROR: %s not deleted, probably because at_obj_delete() returned False." % objname + string += "\nERROR: %s not deleted, probably because delete() returned False." % objname else: string += "\n%s was destroyed." % objname if had_exits: diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 26ea351bc9..1f96286329 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -32,6 +32,7 @@ import traceback import weakref from django.db import models +from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.utils.encoding import smart_str from django.contrib.contenttypes.models import ContentType @@ -1183,15 +1184,36 @@ class TypedObject(SharedMemoryModel): if hperm in perms and hpos > ppos) return False + # + # Deletion methods + # + + def _deleted(self, *args, **kwargs): + "Scrambling method for already deleted objects" + raise ObjectDoesNotExist("This object was already deleted!") + + _is_deleted = False # this is checked by db_* wrappers + def delete(self): "Cleaning up handlers on the typeclass level" global TICKER_HANDLER if not TICKER_HANDLER: from src.scripts.tickerhandler import TICKER_HANDLER - TICKER_HANDLER.remove(self) # removes all ticker subscriptions - _GA(self, "permissions").clear() + TICKER_HANDLER.remove(self) # removes objects' all ticker subscriptions + if not isinstance(_GA(self, "permissions"), LazyLoadHandler): + _GA(self, "permissions").clear() + if not isinstance(_GA(self, "attributes"), LazyLoadHandler): + _GA(self, "attributes").clear() + if not isinstance(_GA(self, "aliases"), LazyLoadHandler): + _GA(self, "aliases").clear() + if not isinstance(_GA(self, "nicks"), LazyLoadHandler): + _GA(self, "nicks").clear() _SA(self, "_cached_typeclass", None) _GA(self, "flush_from_cache")() + + # scrambling properties + self.delete = self._deleted + self._is_deleted = True super(TypedObject, self).delete() # diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 2a163692b8..3a868125d1 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -86,9 +86,13 @@ class SharedMemoryModelBase(ModelBase): def _get(cls, fname): "Wrapper for getting database field" #print "_get:", fieldname, wrappername,_GA(cls,fieldname) + if _GA(cls, "_is_deleted"): + raise ObjectDoesNotExist("Cannot access %s: Hosting object was already deleted." % fname) return _GA(cls, fieldname) def _get_foreign(cls, fname): "Wrapper for returing foreignkey fields" + if _GA(cls, "_is_deleted"): + raise ObjectDoesNotExist("Cannot access %s: Hosting object was already deleted." % fname) value = _GA(cls, fieldname) #print "_get_foreign:value:", value try: @@ -100,6 +104,8 @@ class SharedMemoryModelBase(ModelBase): raise FieldError("Field %s cannot be edited." % fname) def _set(cls, fname, value): "Wrapper for setting database field" + if _GA(cls, "_is_deleted"): + raise ObjectDoesNotExist("Cannot set %s to %s: Hosting object was already deleted!" % (fname, value)) _SA(cls, fname, value) # only use explicit update_fields in save if we actually have a # primary key assigned already (won't be set when first creating object) @@ -107,6 +113,8 @@ class SharedMemoryModelBase(ModelBase): _GA(cls, "save")(update_fields=update_fields) def _set_foreign(cls, fname, value): "Setter only used on foreign key relations, allows setting with #dbref" + if _GA(cls, "_is_deleted"): + raise ObjectDoesNotExist("Cannot set %s to %s: Hosting object was already deleted!" % (fname, value)) try: value = _GA(value, "dbobj") except AttributeError: @@ -348,7 +356,7 @@ def flush_cached_instance(sender, instance, **kwargs): # XXX: Is this the best way to make sure we can flush? if not hasattr(instance, 'flush_cached_instance'): return - sender.flush_cached_instance(instance) + sender.flush_cached_instance(instance, force=True) pre_delete.connect(flush_cached_instance)