ELTE-Soft / txtUML

Textual, eXecutable, Translatable UML
http://txtuml.inf.elte.hu
Eclipse Public License 1.0
26 stars 9 forks source link

Checking whether objects are used after deletion (at runtime)? #103

Open kovacsgabor opened 9 years ago

kovacsgabor commented 9 years ago

We should somehow check whether any deleted model object is accessed, as in Java, the users will get no error or warning messages if they do so.

kovacsgabor commented 9 years ago

Proposed solution: (1)

Introduce a generic wrapper type to the language that could be used to monitor the life cycle of the contained model object.

kovacsgabor commented 9 years ago

Proposed solution: (2)

Use proxies instead of the model objects to monitor their life cycle. Action.create could return these proxy instances.

This functionality could be switched on and off with a global setting (in ModelExecutor.Settings) to improve runtime performance when checks are not needed.

nboldi commented 9 years ago

As agreed I will check the technology available for creating type-safe dynamic proxies.

nboldi commented 9 years ago

Updates: cglib seems promising. The following code subclasses the given class and overrides its default behavior (for any method). In the combination with a deleted flag, could be used to check if an object is deleted.

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;

class SampleClass {
    public String test(String input) {
        return "Hello world!";
    }
}

public class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if (method.getName().equals("test")) {
                    return "Hello cglib!";
                } else {
                    throw new RuntimeException("Do not know what to do.");
                }
            }
        });
        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test(null));
    }
}
nboldi commented 9 years ago

Long-term performance is within 110% of the uninstrumented version, so enabling checks will not result in a substantial slowdown.

nboldi commented 9 years ago

A little example:

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class BaseClass {

    private boolean deleted = false;

    public void delete() {
        deleted = true;
    }

    public boolean isDeleted() {
        return deleted;
    }
}

class SampleClass extends BaseClass {

    public int test() {
        return 0;
    }

}

public class Test {
    public static void main(String[] args) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new MethodInterceptor() {

            @Override
            public Object intercept(Object obj, Method method, Object[] args,
                    MethodProxy proxy) throws Throwable {
                if (!method.getName().equals("isDeleted")
                        && ((BaseClass) obj).isDeleted()) {
                    throw new RuntimeException(
                            "calling method on a deleted object");
                }
                return proxy.invokeSuper(obj, args);
            }
        });

        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test());
        proxy.delete();
        System.out.println(proxy.test());
    }
}
nboldi commented 9 years ago

@kovacsgabor, is this the kind of thing you imagined?

kovacsgabor commented 9 years ago

Yes, it is, thanks.

@devaigergely81, you worried about performance and user-friendliness here. Do you think this could work?

nboldi commented 9 years ago

Creation using the proxy could be done inside Action.create, for example.

There are some inconveniences, for example, methods declared as final could not be overridden. That means that getClass(), notify(), notifyAll(), and wait() will work as it would in the original class. getClass() returns something like class SampleClass$$EnhancerByCGLIB$$e04b1a01 that is the direct subclass of SampleClass

kovacsgabor commented 9 years ago

Can it use different constructors?

nboldi commented 9 years ago

Sure it can:

        SampleClass proxy = (SampleClass) enhancer.create(
                new Class<?>[] { Integer.TYPE }, new Object[] { 3 });

will call

    public SampleClass(int i) {
        this.i = i;
    }
kmate commented 9 years ago

Another option: we may use Java Instrumentation along with a bytecode manipulation library, like Javassist. I will create some experiments basend on these tools and link them here.

kmate commented 9 years ago

You can check my solution here

nboldi commented 8 years ago

I pass back the ownership to @kovacsgabor, for he is in charge of API changes.