Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Java Quiz #45

Français :
Le pattern Singleton est bien connu ; son but est de garantir qu'une classe donnée ne possède qu'une seule instance.
Mais comment le protéger contre une réinstanciation par réflexion ?

Note: le but n'est pas de réimplémenter le Singleton sous la forme d'un enum, mais de protéger son implémentation classique, proposée ci-dessous.

English :
Singleton is a well-known design pattern ; its purpose is to guarantee that only one instance exists in the VM for that particular class.
But how can we protect the class from being instanciated again via reflection ?

(below : a basic Singleton implementation)


public class Singleton {
 
    private static final Singleton INSTANCE = new Singleton();
 
    public static Singleton getInstance() {
        return INSTANCE;
    }
 
    private Singleton() {
    }
 
    public void sayHello() {
        System.out.println("Hello World !");
    }
 
}

Réponse

(for the English version, please scroll down past the French one)

Dans ce quiz, la question était simplement de protéger le Singleton contre une réinstanciation par réflexion. Les solutions consistant à le réimplémenter sous la forme d'un enum ou à activer le SecurityManager étaient donc hors-sujet.
Je félicite en revanche mes amis lecteurs qui ont trouvé la bonne solution (voire même plus, comme Heinz Kabutz ou Wouter Coekaerts).

La question mystère

Pour trouver la bonne réponse, il fallait se poser la bonne question. Celle-ci était : "qu'est-ce qui différencie la première instanciatiation (valide) des instanciations suivantes (invalides), en particulier celles effectuées via le mécanisme de réflexion ?". La réponse est simple : leur contexte d'instanciation.

Le champ INSTANCE étant statique, il est initialisé lors du chargement de la classe par la VM ; comme il est valorisé immédiatement, le constructeur du Singleton est appelé également dans ce contexte, et sa toute première instance est créée. Pour s'en convaincre, il suffit de générer une stack trace dans le constructeur :

private Singleton() {
    new Exception().printStackTrace();
}
java.lang.Exception:
    at net.thecodersbreakfast.quiz.Singleton.<init>(Singleton.java:30)
    at net.thecodersbreakfast.quiz.Singleton.<clinit>(Singleton.java:8)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:186)
    (...)

Dans cette stack trace, on reconnaît la méthode <init> qui correspond au constructeur, et surtout une frame <clinit>, ("class initializer"), qui indique que la classe était en train d'être chargée par la VM.

Voyons maintenant ce qu'on obtient lorsque la classe est instanciée par réflexion :

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton newSingleton = constructor.newInstance();
}
java.lang.Exception
    at net.thecodersbreakfast.quiz.Singleton.<init>(Singleton.java:18)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    at net.thecodersbreakfast.quiz.Singleton.main(Singleton.java:40)
    (...)

Ici, on reconnaît bien l'appel du constructeur (<init>), mais pas de <clinit> en vue.

La solution

Nous pouvons donc nous servir de cette différence pour protéger notre Singleton :

private Singleton() {
    StackTraceElement[] stackTrace = new Exception().getStackTrace();
    if (!"<clinit>".equals(stackTrace[1].getMethodName())) {
        throw new IllegalStateException("You shall not instanciate the Singleton twice !");
    }
}

Voyons si cela fonctionne :

public static void main(String[] args) {
    Singleton.getInstance().sayHello();
}
 
// > Hello World !

Et par réflexion :

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton newSingleton = constructor.newInstance();
    newSingleton.sayHello();
}
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    at net.thecodersbreakfast.quiz.Singleton.main(Singleton.java:31)
    (...)
Caused by: java.lang.IllegalStateException: You shall not instanciate the Singleton twice !
    at net.thecodersbreakfast.quiz.Singleton.<init>(Singleton.java:19)
    (...) 

Bingo !

Ou presque...

Si cette technique fonctionne bien et protégera notre sympathique Singleton dans l'immense majorité des cas, il existe cependant des façons très exotiques de la contourner.

Heinz et Wouter ont notamment pointé du doigt une technique d'instanciation utilisant des classes propriétaires (sun.*), qui interviennent dans le processus de désérialisation pour, justement, se substituer à l'appel du constructeur de la classe en cours de désérialisation.
Heinz m'a également montré une seconde technique, encore plus pointue, mais que la morale m'interdit de reproduire ici - je ne voudrais pas choquer mes plus jeunes lecteurs :)

En tout cas, merci d'avoir participé à ce quiz ! A bientôt !

English

In this quiz, the goal was to protect the given Singleton from being instanciated by reflection. Reimplementing it as an enum or using the SecurityManager were therefore wrong solutions.
On the other hand, I must congratulate those of my readers who found the right answer (or even more, like Heinz Kabutz or Wouter Coekaerts).

The trick question

To find the right solution, you had to ask the right question : "what's the difference between the first Singleton instanciation and the following ones ?". The answer is : the context in which they happen.

The INSTANCE field being static, it is initialized during the class loading process. And because it is assigned immediately, the Singleton constructor is also called in this context, and the first Singleton instance is created. Let's generate a stack trace in the constructor to see this in action :

java.lang.Exception:
    at net.thecodersbreakfast.quiz.Singleton.<init>(Singleton.java:30)
    at net.thecodersbreakfast.quiz.Singleton.<clinit>(Singleton.java:8)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:186)
    (...)

In this stack trace, we can identify the <init> method, which is the class' constructor, and more noteworthily, a frame labelled <clinit> (for "class initializer"), which tells us that our class was in the process of being loaded into the VM.

Let's now see what happens when we instanciate the class using reflection :

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton newSingleton = constructor.newInstance();
}
java.lang.Exception
    at net.thecodersbreakfast.quiz.Singleton.<init>(Singleton.java:18)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    at net.thecodersbreakfast.quiz.Singleton.main(Singleton.java:40)
    (...)

We still see the <init> method, but the <clinit> is not there anymore, as expected.

The solution

Now, we should be able to use this difference to our advantage, to protect our Singleton :

private Singleton() {
    StackTraceElement[] stackTrace = new Exception().getStackTrace();
    if (!"<clinit>".equals(stackTrace[1].getMethodName())) {
        throw new IllegalStateException("You shall not instanciate the Singleton twice !");
    }
}

First, check that the initial instanciation still works :

public static void main(String[] args) {
    Singleton.getInstance().sayHello();
}
 
// > Hello World !

All good. Now, with reflection :

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton newSingleton = constructor.newInstance();
    newSingleton.sayHello();
}
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    at net.thecodersbreakfast.quiz.Singleton.main(Singleton.java:31)
    (...)
Caused by: java.lang.IllegalStateException: You shall not instanciate the Singleton twice !
    at net.thecodersbreakfast.quiz.Singleton.<init>(Singleton.java:19)
    (...) 

Great ! We have our solution !

Or not.

This technique will work nicely in most contexts and will prevent your Singleton from duplication ; but it can still be bypassed in a few mysterious and exotic ways.

First, Heinz and Wouter pointed out an instanciation technique that uses some of the sun.* proprietary classes involved in the deserialization process. That was unexpected !
Then Heinz sent me an even more advanced code snippet that I cannot publish here, to avoid traumatizing my yougest readers :)

But those should not considered standard production code, so the stacktrace solution can still be considered valid in most cases.

Thank you for participating, and stay tuned for more happy days !


Commentaires

1. Le lundi 23 juillet 2012, 06:51 par mimah

Tester la non nullité de INSTANCE dans le constructeur et lever une exception si non null.

2. Le lundi 23 juillet 2012, 07:48 par Corwin

You can protect your class by enabling SecurityManager in your JVM (with option -Djava.security.manager) this solution implies no code change or you can add it programmaticaly with
"System.setSecurityManager(new SecurityManager());"
With a security manager set up, you won't be able to override access permission with reflection.

3. Le lundi 23 juillet 2012, 08:18 par Alex

private Singleton() {

       if (INSTANCE != null) {
           throw new IllegalAccessError();
       }
   }
4. Le lundi 23 juillet 2012, 08:22 par Benoît

On peut jeter une exception dans le constructeur, dès que INSTANCE n'est pas null.

5. Le lundi 23 juillet 2012, 08:25 par GroselyneBachelot

private Singleton() {

   	if( Singleton.INSTANCE != null ) {
           throw new InstantiationError( "Creating of this object is not allowed." );
       }

}

6. Le lundi 23 juillet 2012, 09:50 par fcamblor

Adding in the constructor :
if(INSTANCE != null){

   throw new IllegalStateException("Beware : instantiating twice a singleton !");

}

Would it make the deal ?

I don't really like throwing exceptions in constructors since we don't really know if object is effectively created or not... but it could be an indication for the black wizard willing to instantiate several singletons !

7. Le lundi 23 juillet 2012, 10:29 par xav

en rajoutant le lancement d'une exception dans le constructeur ?

8. Le lundi 23 juillet 2012, 13:47 par Mathieu

Une méthode peut-être en supprimant le nullary constructor, comme indiqué dans la javadoc :

public class Singleton {

   private static final Singleton INSTANCE = new Singleton(null);
   public static Singleton getInstance() {
       return INSTANCE;
   }
   private Singleton(Void v) {
   }
   public void sayHello() {
       System.out.println("Hello World !");
   }

}

Ce qui provoque une java.lang.InstantiationException lors de l'instanciation par introspection.

9. Le lundi 23 juillet 2012, 15:24 par christophe

J'ai rajouté une exception dans le constructeur s'il était déjà initialisé.

private Singleton() {

   synchronized (this.getClass()) {
       if (INSTANCE != null) {
           throw new IllegalStateException();
       }
   }

}

Par contre je doute qu'on puisse lutter contre de l'injection avec cglib et compagnie.

10. Le lundi 23 juillet 2012, 20:05 par Wouter Coekaerts

1. Make it an enum. Enum singletons FTW. (You didn't translate the note about it not being allowed in English; so for me it's still allowed because I don't understand Fren... oh, oops).

2. -Djava.security.manager

3. Check your caller:

   private Singleton() {
       if (! Singleton.class.getName().equals(new Throwable().getStackTrace()[1].getClassName())) {
           throw new SecurityException();
       }
   }

4. Rely on it that classes only get loaded once, and failing to load a class throws a different exception the first time (ExceptionInInitializerError) than attempts to use it afterwards (NoClassDefFoundError).

public class Singleton {

   ....
   
   private Singleton() {
       try {
           new Meh();
       } catch (ExceptionInInitializerError e) {
       }
   }

}

class Meh {

   static int i = 1 / 0;

}

11. Le lundi 23 juillet 2012, 20:48 par Wouter Coekaerts

If you try to prevent this by only making the constructor of Singleton throw an exception,
then it can be circumvented with the SilentObjectCreator from http://www.javaspecialists.eu/archi... .

Here's an attempt at protecting against that.

We make the Singleton class abstract so no instances of it can be created directly.
There is one subclass (SingletonImpl) that just creates one instance of itself, and then deliberately fails to load by
throwing an exception so it cannot be accessed using reflection anymore either.
That puts us in the awkward situation that the (only) instance of the singleton is of a class that failed to load.

public abstract class Singleton {

   static class SingletonImpl extends Singleton {
       private static Singleton unused = new SingletonImpl();
       private static int failToLoad = 1/0;
   }
   private static Singleton INSTANCE;
   public static Singleton getInstance() {
       if (INSTANCE == null) {
           try {
               new SingletonImpl();
           } catch (Throwable e) {
           }
       }
       return INSTANCE;
   }
   private Singleton() {
       INSTANCE = this;
   }
   public void sayHello() {
       System.out.println("Hello World !");
   }

}

12. Le lundi 23 juillet 2012, 21:55 par Laymain

C'est sale, très sale... Mais c'est une possiblité
https://gist.github.com/3166085

13. Le lundi 23 juillet 2012, 22:47 par Olivier Croisier

Il est possible de mettre INSTANCE à null par réflexion avant d'appeler le constructeur ; la solution qui coniste à vérifier la nullité du singleton est donc invalide.
En ce qui concerne le SecurityManager et les Enums, ils ne répondent pas à la question, qui consiste à sécuriser le Singleton existant et pas à modifier son environnement.

It is possible to set INSTANCE to null before calling a constructor ; therefore, checking that the instance is null is not enough.
About the SecurityManager and reimplemening the Singleton as an Enum : that's not a valid answer, as it changes the whole environment instead of securing the given Singleton.

14. Le lundi 23 juillet 2012, 22:58 par Adrien

J'ai ajouté une variable statique initialisée après la première instanciation. Le constructeur vérifie que la variable n'est pas encore initialisée.

   private static final Singleton INSTANCE = new Singleton();
   private static Boolean check= true;

(...)

   private Singleton() {
   	if (check==Boolean.TRUE) throw new RuntimeException("Merci de passer par getInstance"); 
   }
15. Le lundi 23 juillet 2012, 23:06 par Olivier Croisier

@Wouter and @Laymain : well done :)
Heinz found the answer too, and objected that one can create instances without calling a constructor (see the mentioned Javaspecialist's Newsletter), or by injecting a whole new constructor into the class (I didn't even think that could be done. This is both very ugly and exciting !)

Anyway, this quiz was meant to be a refresher about when static variables are instanciated, so Heinz's and Wouter's solutions will be considered as statistical aberrations and not common code, that do not invalidate the point.

16. Le lundi 30 juillet 2012, 12:45 par Wouter Coekaerts

Sorry if this traumatizing your yougest readers, but this breaks your Singleton: You can change the content of the String "newInstance0" to be "<clinit>". (When it's been running for some time stuff gets optimized, so also need to do this for "newInstance"). This needs to happen at just the right time: after that part of the stacktrace is created, but before the Singleton constructor checks it. So doing this concurrently in a separate thread. It needs a number of attempts; on my machine average +/- 250 or so.

import java.lang.reflect.*;

public class SingletonBreaker {

   public static void main(String[] args) throws Exception {
       Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
       constructor.setAccessible(true);
       // let the code warm up first. without this hacking the strings may make some class loading fail
       for (int i = 0; i < 10000; i++) {
           try {
               constructor.newInstance();
           } catch (InvocationTargetException e) {
           }
       }
       // in a background thread change the content of the Strings "newInstance0" and "newInstance" to "<clinit>"
       new Thread() {
           @Override
           public void run() {
               while (true) {
                   hackIt((Character.toString('n') + "ewInstance0").intern());
                   hackIt((Character.toString('n') + "ewInstance").intern());
               }
           }
       }.start();
       // and in parallel keep trying to call the constructor
       for (int attempt = 0; ;attempt++) {
           try {
               constructor.newInstance();
               Singleton newSingleton = constructor.newInstance();
               newSingleton.sayHello();
               System.out.println("Succeeded in attempt " + attempt);
               System.exit(0);
           } catch (InvocationTargetException e) {
           }
       }
   }
   public static void hackIt(String str) {
       try {
           final Field valueField = String.class.getDeclaredField("value");
           valueField.setAccessible(true);
           final Field countField = String.class.getDeclaredField("count");
           countField.setAccessible(true);
           valueField.set(str, "<clinit>".toCharArray());
           countField.set(str, 8);
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
   }

}

17. Le lundi 30 juillet 2012, 20:35 par Olivier Croisier

Haha, nicely done :)

18. Le mercredi 1 août 2012, 15:13 par HollyDays

Perhaps I'm missing something, but it looks to me that the following Singleton is properly protected against both Wouter's nice'n'tricky trick and Heinz's deserialization ruse:

public class Singleton {

   private static final Singleton INSTANCE   = new Singleton();
   private static final String    CLASS_NAME = Singleton.class.getName();

   public static Singleton getInstance() {
       return INSTANCE;
   }

   private Singleton() {
       if (CLASS_NAME != null) {
           throw new IllegalStateException("You shall not instantiate the Singleton twice!");
       }
  }

   public void sayHello() {
       System.out.println("Hello World!");
   }

}

And if indeed I'm not mistaken, here's some sugar on the cake: the code looks much cleaner to me than the one, which tries and checks the method name of the constructor caller using an ad hoc exception!

19. Le jeudi 2 août 2012, 10:50 par HollyDays

Here's a funny thing by the way: after posting the above text yesterday, I realized while waking up this morning (no comment please ;-) ) that there was a way to set static final fields after their initialization (via reflection). Heinz published it here: http://www.javaspecialists.eu/archi... (see his class ReflectionHelper)

So I updated Wouter's SingletonBreaker class to have it try and change my above static final field CLASS_NAME (setting to null, obviously), hoping it would break my protected Singleton. I put the additional instruction directly in the main() code, not in the inner thread code, of course (right before the endless loop). And you know what? It looks like working, but actually it does not at all!

I make myself clearer: printing out the CLASS_NAME field in the SingletonBreaker shows that calling the ReflectionHelper does indeed reset this static final field to null. So the Singleton must be broken, mustn't it? But if you make the Singleton constructor print out the CLASS_NAME field value as well, you'll see that it never changes and is always non-null! So the Singleton is still not broken! And the program shows two different values for the same static field of the same class at the same time (Oops! Is Schrödinger's ghost getting closer?)

I thought at first it could be due to some weird memory-barrier side effects on accessing the same field (cf. memory implications of the Java synchronization), but trying to synchronize access to the CLASS_NAME field (on the Singleton class) led me nowhere so far. I also tried to get the object IDs of the Singleton class and its static field in memory, but I could not get any evidence that there were two different Singleton classes and/or two different CLASS_NAME fields in memory at the same time. Any more ideas?

20. Le dimanche 5 août 2012, 13:18 par Heinz Kabutz

Just a point of clarification. My "trick" does not really have anything to do with deserialization, although the deserialization uses the same trick that I do. You can also create a new constructor at run time for your class. Exercise left as an exercise to the reader. The trick with creating a new constructor instance for your class also works in Android, btw.

21. Le mercredi 8 août 2012, 14:55 par Sébastien Lorber

We can also create a different classloader with no default parent which will reload the class.

22. Le jeudi 9 août 2012, 16:16 par Heinz Kabutz

Actually, Sébastien, that's a good point. I did that once, many years ago, to enable us to invoke the JavaDoc tool several times from within one JVM. It was not designed like that, so I invoked it each time in its own classloader. Worked like a charm.

23. Le jeudi 9 août 2012, 22:24 par Olivier Croisier

Technically, using another classloader would produce a Singleton of a different class, the class' ID being comprised of the class' fully qualified name and its classloader.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.