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 #38

Can you help the poor Exception to escape the Matrix ?
Beware, the Agents are nearby and will spot you if you attempt in any way to modify or remove the existing lines of code ! (but you may add new ones).

  1. public class Matrix {
  2. public static void getTheSpoon() {
  3. throw new java.lang.NoSuchMethodException("There is no spoon !");
  4. }
  5. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. try {
  4. Matrix.getTheSpoon();
  5. } catch (Exception ex) {
  6. System.out.println(ex instanceof java.lang.NoSuchMethodException ? "You passed the Quiz !" : "You failed !");
  7. }
  8. }
  9. }

Answer :

Several responses were possible for this quiz, and most have been provided in the comments. Some were very inventive, and some were almost cheating :)
But that's the most interesting part of the quiz isn't it ?

My favourite answer is the one that shamelessly exploits the way the compiler works with java Generics. Simple, effective, but deliciously disgusting :)

  1. public class Matrix {
  2.  
  3. public static void getTheSpoon() {
  4. try {
  5. throw new NoSuchMethodException("There is no spoon !");
  6. } catch (NoSuchMethodException e) {
  7. Matrix.<RuntimeException>escapeFromTheMatrix(e);
  8. }
  9. }
  10.  
  11. private static<T extends Throwable> void escapeFromTheMatrix(Exception ex) throws T {
  12. throw (T) ex; // D'oh !
  13. }
  14.  
  15. }

The trick here is to fool the compiler into thinking that our exception is a RuntimeException, so that it does not check the getTheSpoon() method signature for it.
To do that, we take advantage of the type erasure process that occurs during compilation, and cast our NoSuchMethodException to a mere RuntimeException - without any compatibility check !

No need to say : I don't recommend using this technique in production.
(But if you find interesting use-cases, please let me know !)


Commentaires

1. Le lundi 7 juin 2010, 10:57 par Arno

NoSuchMethodException extends RuntimeExption ?

2. Le lundi 7 juin 2010, 11:10 par Alexius Diakogiannis

Can you write the quiz in English too please???

3. Le lundi 7 juin 2010, 11:59 par jerome

Un petit coup de Class.newInstance() fait l'affaire :

public class Matrix {
public static void getTheSpoon() {
try {
Matrix.class.newInstance();
} catch (InstantiationException e) {
// OSEF
} catch (IllegalAccessException e) {
// OSEF
}
}

public Matrix() throws NoSuchMethodException {
throw new NoSuchMethodException("There is no spoon !");
}

}

(si y'a moyen de formatter ça je suis preneur)

4. Le lundi 7 juin 2010, 12:43 par Yall

Merci le quiz #36 !

Pour ne pas attirer l'attention des agents, je n'ai touché ni à la signature ni au corps de la méthode getTheSpoon(), en ajoutant une classe interne à Matrix :

static class NoSuchMethodException extends RuntimeException {

 public NoSuchMethodException() throws java.lang.NoSuchMethodException {
   throw new java.lang.NoSuchMethodException();
 }
 public NoSuchMethodException(String message) {
   try {
     NoSuchMethodException.class.newInstance();
   } catch (InstantiationException e) {
     // 
   } catch (IllegalAccessException e) {
     // 
   }
 }

}

5. Le lundi 7 juin 2010, 14:56 par Olivier Croisier

Réponses intéressantes, y'a de l'imagination :)

Maintenant, essayez de résoudre le quiz sans recourir à la Réflexion (il existe plusieurs techniques).

6. Le lundi 7 juin 2010, 15:19 par 5po

Euh un simple " throws NoSuchMethodException" sur la méthode getTheSpoon ça suffit pas ? (de toute façon ça compile pas sans je pense)

7. Le lundi 7 juin 2010, 15:22 par 5po

Ops désolé j'ai pas bien lu l'énoncé...

8. Le lundi 7 juin 2010, 15:41 par Jérôme

Voilà une autre technique sans introspection mais utilisant une API deprecated :

public class Matrix {

   public static void getTheSpoon() {
       Thread.currentThread().stop(new NoSuchMethodException("There is no spoon !"));
   }

}

9. Le lundi 7 juin 2010, 16:16 par Jérôme

Une autre version plus... funky :

public class Matrix {

   public static void getTheSpoon() {
       Matrix.<Error>kaboom(new NoSuchMethodException("There is no spoon !"));
   }
   public static <T extends Throwable> void kaboom(Throwable t) throws T {
       throw (T)t;
   }

}

Explications : le <Error> fait croire au compilo qu'on va lancer une Error (donc pas besoin de throws). Le cast (T) devrait en toute naïveté planter au runtime vu qu'on n'a pas une Error mais une Exception, sauf que par la magie des generics Java et de l'erasure, ce cast ne sert à rien et on reste avec notre Throwable.

Pfiou, j'ai mal à la tête moi...

10. Le lundi 7 juin 2010, 17:26 par Jérôme

Une variante qui n'utilise ni introspection, ni deprecated, ni warning, et qui répond à la lettre à l'énonce :

public class Matrix {

   public static void getTheSpoon() {
       System.setOut(new java.io.PrintStream(System.out) {
           @Override
           public void println(String x) {
               super.println("You passed the Quiz !");
           }
       });
       throw new RuntimeException("just kidding");
   }

}

11. Le lundi 7 juin 2010, 22:10 par HollyDays

Ma préférée entre toutes : la dernière de Jérôme. Son auteur ne manque ni de finesse ni de rouerie... ;-)))

12. Le mardi 8 juin 2010, 08:17 par Emmanuel Bourg

Comme il ne fait aucun doute que la classe Matrix fait partie du système on peut tout simplement écrire :

public class Matrix {

  public static void getTheSpoon() {
      sun.misc.Unsafe.getUnsafe().throwException(new NoSuchMethodException("There is no spoon !"));
  }

}

Cela demande d'ajouter la classe au bootclasspath avec le paramètre -Xbootclasspath/a. On pourrait faire sans mais ça ferait appel à java.lang.reflect.

13. Le mardi 8 juin 2010, 08:40 par Jérôme

@Emmanuel Bourg : ajouter le paramètre -Xbootclasspath/a impose de rebooter la matrice ;-)

14. Le mardi 8 juin 2010, 11:16 par Jérôme

Une dernière variante. C'est pas vraiment de l'introspection :D

public class Matrix {

   public static void getTheSpoon() {
       ResourceBundle.getBundle(
               Neo.class.getName(),
               Locale.US,
               new NeoClassLoader()
               ).containsKey("spoon");
   }

}

public class Neo extends java.util.ListResourceBundle {

   @Override
   protected Object[][] getContents() {
       throw new IllegalStateException();
   }

}

Et le gros morceau :

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;

public class NeoClassLoader extends ClassLoader {

   @Override
   protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
       if (Neo.class.getName().equals(name)) {
           try {
               // get the .class
               InputStream input = getResourceAsStream(name.replace('.', '/')
                       + ".class");
               ArrayList<Integer> binaryKlass = new ArrayList<Integer>();
               int b;
               while ((b = input.read()) != -1) {
                   binaryKlass.add(b);
               }
               input.close();
               // modify IllegalStateException ==> NoSuchMethodException
               // (same length)
               ArrayList<Integer> illegal = new ArrayList<Integer>();
               for (byte c : "IllegalStateException".getBytes("UTF-8")) {
                   illegal.add((int) c);
               }
               ArrayList<Integer> method = new ArrayList<Integer>();
               for (byte c : "NoSuchMethodException".getBytes("UTF-8")) {
                   method.add((int) c);
               }
               int index = Collections.indexOfSubList(binaryKlass, illegal);
               for (int i = 0; i < method.size(); i++) {
                   binaryKlass.set(index + i, method.get(i));
               }
               // to byte array
               byte[] brainDead = new byte[binaryKlass.size()];
               for (int i = 0; i < binaryKlass.size(); i++) {
                   brainDead[i] = (byte) (binaryKlass.get(i).intValue() & 0xff);
               }
               Class<?> klass = defineClass(name, brainDead, 0,
                       brainDead.length);
               if (resolve)
                   resolveClass(klass);
               return klass;
           } catch (Exception e) {
               e.printStackTrace();
               throw new ClassCastException(e.getMessage());
           }
       } else {
           return super.loadClass(name, resolve);
       }
   }

}

15. Le mardi 8 juin 2010, 11:22 par Olivier Croisier

Jérôme, tu es officiellement déclaré Fumeur de Moquette en Chef :D
Tes solutions sont intéressantes et pour le moins inventives ! Si tu n'es pas déjà abonné, je te recommande vivement le Club JavaSpecialist (http://javaspecialists.eu/club), tu y seras comme un poisson dans l'eau !

16. Le mardi 8 juin 2010, 14:48 par Jérôme

@Olivier Croisier

Cool, je suis Chef \o/

Je suis abonné à la mailing list de Heinz Kabutz depuis bien longtemps, mais je ne suis pas convaincu de la valeur ajoutée du Java Specialists Club. Si on exclut les webinars qui sont trop preneurs de temps à mon goût, tu y trouves quoi qu'il n'y ait pas ailleurs ?

17. Le dimanche 15 février 2015, 08:03 par Anon-sama

You could just throw comment block start and end around the throwing of the exception also.

Ajouter un commentaire

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