JavaScript, ou les origines des langages ésotériques

On a tous tenté un jour ou l’autre de fermer une pop-up avec ces smileys dedans, avant de se rendre compte que c’était peine perdue. Merci, JavaScript (et Internet Explorer).

On connait tous le JavaScript, du moins son existence. Utilisé depuis les débuts de l’Internet dynamique afin de rendre les pages plus vivantes, le langage a fait l’objet de multiples révisions, évolutions, et… abus.

Malgré tous ces déboires et utilisations néfastes – que ce soit le cauchemar que je viens de vous rappeler ou non, environ 70% des virus de l’époque et les jeux vidéos que l’on pouvait charger avec le 56K (avant que le tout soit supplanté par cette merveilleuse chose qu’est le Flash), il reste le langage le plus populaire et potentiellement le plus utilisé à ce jour.

Mais pourquoi donc ?!

Cet article est aussi une pseudo-introduction aux langages dits ésotériques. Ce sont des langages informatiques créés majoritairement pour le plaisir (ou la torture, selon le point de vue et le langage) et dont une de leurs caractéristiques est d'être très difficile à relire post-écriture, un peu comme le JavaScript tel que je vais vous le présenter.

L’inférence de type et sa soeur maléfique, la coercition de type

Rien que le mot coercition fait peur, hein ?

L'inférence de type, ou la flemme de décrire une variable

L’une des caractéristiques les plus connues de JavaScript est sa capacité à inférer les types des valeurs avec lesquelles il travaille. Peu importe ce qu’on lui passe, il sera capable de lui coller un type parmi ceux-là :

  • Object ;
  • number ;
  • string ;
  • boolean ;
  • null ;
  • undefined.

(Il existe aussi Symbol maintenant, mais il n'est pas encore compatible partout)

Ceci est une caractéristique partagée avec une grande majorité des langages interprétés, l’exemple le plus commun étant le Python pour n’en citer qu’un. On appelle ces langages des langages dynamiquement typés.

De fait, il n’est pas nécessaire de préciser le type des variables lors de leur déclaration :

var tralala = 'lalala'; // Va être inféré en String
var tralale = true; // Boolean
var tralalo = 42; // Number

// Qui, en TypeScript, équivaut à:
var tralala: String = 'lalala';
var tralale: Boolean = true;
var tralalo: Number = 42;

Ceci permet d'alléger la charge d'écriture de code pour le développeur et, dans certains cas, rendrait le code plus facile ou agréable à lire.

Mais, en JavaScript, le type d'une variable n'est pas quelque chose de "rigide" comme ce serait le cas en Java : on peut réassigner une variable avec une valeur d'un autre type et celui-ci ne réagira pas :

var tralala = 'lalala'; // Actuellement tralala contient un String
tralala = 42; // Maintenant, tralala contient un Number !

Là est la grande différence entre un langage dynamiquement typé et un langage statiquement typé : une variable ne porte pas de type, c'est la valeur contenue qui porte cette information dans un langage dynamiquement typé. Ceci est partagé avec le Python, le Perl, le Groovy...

Au contraire, les langages statiquement typés imposent que les variables déclarées aient un type fixe tout au long de leur cycle de vie. Ceci est le cas pour des langages comme Java, C, C++...

Par exemple, en Java, comme on ne peut pas changer le type d'une variable en cours de route, il sera donc impossible de faire quelque chose du genre :

// Ce code ""marche"" aussi en C,C++,C#...
// ... A ceci près qu'il ne fonctionne pas du tout
int tralala = 42;
tralala = "lalala";

Cependant, les langages statiquement typés sont parfaitement capables de faire de l'inférence de type malgré tout. En Java, depuis la version 10, il est possible d'utiliser le mot-clé var pour lui dire de déduire le type tout seul, de la même manière que auto en C et C++ :

var tralala = "tralala"; // Va être inféré en String    

Mais, malgré tout, il ne sera pas possible de réassigner ces variables avec une valeur d'un autre type que la valeur initiale.

Charge dans tous les cas aux développeur·euse·s de faire attention aux types de leurs variables dans ces cas, car comme nous allons le voir par la suite, en JavaScript, il y a un petit truc qui le rend encore plus spéciaaaaaaaal...

La coercition de type, quand on fait passer quelque chose pour ce qu'il n'est pas vraiment

Vous avez demandé un cheval de Troie ?

La coercition de type est un mécanisme utilisé par la majorité des langages (en tous genres, cette fois) pour "forcer" le type de certaines valeurs au sein d'un programme, en fonction de ce qui est demandé par une certaine ligne de code.
Nous avons probablement tous à un moment ou un autre écrit quelque chose du type (exemple en Java) :

double d = 3.5;
int i = 3;

System.out.println(d - i);

Bien que les types de d et de i soient différents, Java est bien capable de faire la soustraction comme il faut et d'afficher le résultat attendu, c'est-à-dire 0.5 alors que i est un entier ?!

C'est parce qu'au moment de lancer le code, le type a été forcé pour être compatible au maximum avec le résultat à calculer. Ceci ressemble à du cast, mais la différence est que ce changement de type est explicite dans le cadre de ce dernier, alors qu'ici il est fait de manière implicite.

Tous les langages en sont capables, mais ont des restrictions quant aux possibilités : il n'est par exemple pas possible en Java, comme dans les majorités des langages proposant cela, de faire une "somme" ou une concaténation d'un tableau et d'un String -- ça n'aurait effectivement pas de sens, hein ?

var lalala = ['li'] + 'lalala';
console.log(lalala); // Ca affiche 'lilalala' ??

... Effectivement. JavaScript permet de coercer tous ses types entre eux, d'une certaine manière, selon les opérateurs utilisés à cet effet. Il y a certaines règles de conversion qui sont un tantinet obscures en JavaScript, qui font que certaines choses apparemment inexplicables ou dénuées de sens peuvent être faites... pour le meilleur et pour le pire.

Un des exemples les plus connus étant d'écrire banana sans faire apparaître la lettre n :

console.log(('b' + 'a' + +'a' + 'a').toLowerCase());

Cela marche aussi avec "ananas"... Le pourquoi arrive !

Le JavaScript et la coercition de type, la porte vers les abus

Du fait de l'utilisation détournée de la coercition de type en JavaScript, on peut effectivement tout faire avec des types qui ne sont, a priori, pas les types auxquels on s'attend. Tout part des tableaux, c'est-à-dire []. Les tableaux sont un Object à la base, mais on peut les forcer à être n'importe quoi. Par exemple, on sait que ! suivi d'un booléen renvoie la négation logique de la valeur, donc !true vaut false. Nous allons voir que l’on peut écrire des programmes entiers juste avec des tableaux et quelques autres symboles -- une possible liste de caractères suffisants est :

[, ], (, ), +, !, /, =, >

(Il existe une liste encore plus réduite à 6 caractères...)

Les booléens... Avec des tableaux

On arrive donc à…

  • ![] (qui se lirait comme étant la négation logique d'un tableau vide) renvoie false ;
  • !![] (qui est donc la négation de la proposition précédente) renvoie true

L'arithmétique... Avec des tableaux

De la même manière, un + précédant une valeur va la coercer en un Number. Ceci est généralement utilisé en conjonction avec une String pour passer une chaîne de caractères étant un nombre en un Number. S'avère que cela fonctionne aussi avec les tableaux !

  • +[] donne donc 0 : le tableau vide est coercé vers 0 ;
  • +!![] donne 1 : comme !![] correspond à true et que true est coercé vers 1 en Number, on obtient bien 1 dans ce cas ;
  • 2 et plus peuvent être obtenus en "additionnant" des 1 : +!![]+!![].

On peut aussi régler vite fait le cas du NaN, undefined et Infinity :

  • +[![]] renvoie NaN (on essaye de faire de [false] un nombre, ce qui ne fonctionne pas vraiment) ;
  • [][+!![]] renvoie undefined (c'est un tableau vide auquel on tente d'accéder à l'indice 1).
  • 1/0 renvoie Infinity, donc on peut écrire +!![]/+[].

Ceci explique notamment pourquoi on peut écrire "banana" sans le n : comme on oblige le type de 'a' à être un Number, mais que 'a' n'a pas de sens en tant que chiffre (en base 10 par défaut), il ne peut pas en faire un Number correct, et donc retourne NaN.

Mais l'arithmétique ne serait pas complète sans les opérations classiques, que sont l'addition, la soustraction, la multiplication et la division... Mais comment les avoir ?

Le + peut être obtenu via la notation scientifique, comme JavaScript va automatiquement ajouter un + à l'exposant. Le plus petit nombre permettant d'avoir cela est 1e21, donc on peut abuser de cela allègrement :

((+((+!![])+([][+!![]]+[])[+!![]+!![]+!![]]+(+!![]+!![])+(+!![])))+[])[+!![]+!![]]

Pour avoir le -, il faut aussi passer par la notation scientifique... Mais pour les petits nombres -- pour lesquels il nous faut le . pour la décimale ! Heureusement, on est en capacité d'écrire 1.1e21, car égal à 11e20:

(+(+(+!![])+[]+(+!![])+([][+!![]]+[])[+!![]+!![]+!![]]+(+!![]+!![])+(+![]))+[])[+!![]]

Du coup, il suffit d'écrire .0000001, ce qui donnera 1e-7, et donc on récupère le -:

(+((+(+(+!![])+[]+(+!![])+([][+!![]]+[])[+!![]+!![]+!![]]+(+!![]+!![])+(+![]))+[])[+!![]]+(+[])+(+[])+(+[])+(+[])+(+[])+(+[])+(+!![]))+[])[2]

Etc, etc. On peut le faire effectivement pour tous les symboles, à partir du moment où on a le set minimal de caractères nécessaires, que je vais détailler plus bas, pour faire le "Hello, World!"

Hello World, avec des tableaux

Les bases pas si basiques

Pour pouvoir tout écrire avec cet ensemble de caractères, il nous faut traduire au préalable tous ces caractères en leur équivalent... obfusqué :

astrueocnSghlmpC,[espace]

En effet, grâce à ces lettres, on peut avoir accès au constructor de chaque Object (ce qui nous permettra de récupérer certaines choses), à la fonction toString qui sera utile pour certaines de ces lettres et, enfin, à la fonction fromCharCode qui nous permettra d'avoir tous les autres caractères qui ne sont pas présents dans cette liste.

Allons-y !

Avec false, on peut avoir les lettres a et s :

a => (![]+[])[+!![]]
s => (![]+[])[+!![]+!![]+!![]]

Et avec true, on peut prendre toutes ses lettres comme on en a besoin :

t => (!![]+[])[+[]]
r => (!![]+[])[+!![]]
u => (!![]+[])[+!![]+!![]]
e => (!![]+[])[+!![]+!![]+!![]] // N'est pas utile dans l'immédiat, mais va l'être plus tard

Similairement, on peut obtenir o et c en utilisant le fait que :

  • {}+[] va renvoyer un tableau avec un élément de type Object dedans ;
  • En y mettant des parenthèses autour, le tout va être coercé en String, et cela s'affichera comme [object Object] (que l'on a tous vu un moment ou un autre pendant une séance de debugging...).

On a donc :

o => ({}+[])[+!![]]
c => ({}+[])[+!![]+!![]+!![]+!![]+!![]]

Et, "enfin", on peut avoir le n comme c'est la deuxième lettre de Infinity :

n => ((+!![]/+[])+[])[+!![]]

Les "classes" en JavaScript, où tout est un membre

Maintenant, on peut utiliser constructor ! Mais à quoi cela va nous servir ?

Il s'avère que les types primitifs en JavaScript sont généralement encapsulés dans leurs équivalents objets, c'est-à-dire number sera encapsulé dans un objet Number, qui aura donc un constructeur et des méthodes de classe. Et une des particularités de ces méthodes est qu'elles sont accessibles comme pour un objet classique ! On peut alors faire des bizarreries du type :

40['constructor'] // Renvoie le constructeur de Number
40['constructor'].toString() // Renvoie "function Number() { [native code] }"

On veut donc avoir le S et le g, qui se trouvent dans String, donc on peut les obtenir via le constructeur de String... Et c'est là que ça commence à être compliqué :

S => (([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+((+!![]/+[])+[])[+!![]]+(![]+[])[+!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]

g => (([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+((+!![]/+[])+[])[+!![]]+(![]+[])[+!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]

Maintenant, on a toutes les lettres pour avoir toString, qu'on peut accéder et appeler... Et cela va nous servir pour le h et le m.
Pour ces lettres, on va utiliser le fait que, par exemple, en base 16, on compte jusqu'à f, donc en base 18, on va jusqu'à la lettre h, et en base 23 pour le m !

Donc, en JavaScript normal, cela s'écrirait :

(17)['toString'](18); // h
(21)['toString'](22); // l
(22)['toString'](23); // m

Ce qui donne, en... obfusqué :

h => (+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])[({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]](+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])
l => (+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])[({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]](+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])

m => (+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])[({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]](+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])

Il nous manque aussi le p, qui lui est notamment présent dans le constructeur d'une expression régulière, un objet RegExp ! Pour cela, il nous faut d'abord construire une expression régulière -- la plus simple possible étant /[]/ dans notre cas. À partir de celle-là, on peut récupérer le constructeur, puis le p :

p => ((/[]/)[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]

Les fonctions, aussi une classe...

Il ne manque plus que le C ! Le problème est que rien ne donne a priori un C majuscule : on pourrait penser que passer par l'hexadécimal fonctionnerait, mais les lettres sont en minuscules dans ce cas... Et il n'existe pas de type avec un C majuscule. Et on ne peut pas utiliser le code ASCII du caractère, comme il nous faut fromCharCode pour faire la conversion... qui nécessite ce C majuscule.

Mais il existe, en JavaScript, pour les String, une méthode statique qui s'appelle escape, qui permet de convertir certains caractères en leur représentation compatible avec une URL. Cela est fait en prenant le code ASCII en hexadécimal du caractère en question, et en le préfixant par un %. Par exemple, un espace est encodé en %20 dans une URL. L'avantage de cette méthode est que les caractères sont échappés en hexadécimal, avec les lettres en majuscule, avec par exemple / qui est converti en %2F ! Et, pour ce qui nous arrange le plus, la virgule , est encodée par %2C ! Il nous reste donc à récupérer une virgule.

Pour une virgule, on peut se servir du fait que :

  • Un tableau a ses éléments séparés par une virgule quand elle est convertie en String ;
  • On peut épeler split avec nos lettres actuelles, qui permet de scinder une String en un tableau.

Donc, il suffit de créer une String de deux caractères, la scinder en un tableau de deux éléments, repasser le tableau en String et récupérer la virgule !

Cela donnerait, en JavaScript "correct" :

(('aa'['split'](''))+'')[1] // C'est une virgule !

Et en ésotérique :

',' => ((((+[![]]+[])[+!![]]+(+[![]]+[])[+!![]])[(![]+[])[+!![]+!![]+!![]]+((/[]/)[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+(+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])[({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]](+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(+!![]/+[]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]]([]+[])+[]))[+!![]]

Et maintenant, on peut encoder cette virgule. Enfin, presque. Il faut d'abord réussir à récupérer la fonction escape comme on ne peut pas l'écrire, et l'appeler avec la virgule en argument. Cependant, on peut créer une fonction, en récupérer son constructeur pour créer une fonction qui renvoie la fonction escape, et l'utiliser...

(()=>{})['constructor']('return escape')()(',') // Renvoie %2C !

Ce qui donne, ultimement, pour la lettre C :

C => ((()=>{})[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+!![]]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]+!![]]+(!![]+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+!![]]+[])[+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+(+[![]]+[])[+!![]]+((/[]/)[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+!![]]+[])[+!![]+!![]+!![]])()(((((+[![]]+[])[+!![]]+(+[![]]+[])[+!![]])[(![]+[])[+!![]+!![]+!![]]+((/[]/)[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]+[])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+(+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])[({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+([]+([]+[])[({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(+!![]/+[]+[])[+!![]+!![]+!![]+!![]]+(![]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]](+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(+!![]/+[]+[])[+!![]+!![]+!![]]+({}+[])[+!![]+!![]+!![]+!![]+!![]+!![]]]([]+[])+[]))[+!![]]))[+!![]+!![]]

Et maintenant, c'est gagné ! On a tous les caractères nécessaires pour écrire fromCharCode, ce qui permet de récupérer tous les caractères ASCII selon leur numéro (on les a tous, de fait, en faisant 1+1+...+1), donc on peut tout écrire !

Pour en faire un code fonctionnel, il suffit d'encoder le code avec tous les caractères précédents, et d'utiliser la même syntaxe que l'on a utilisé pour retrouver escape.

(()=>{})['constructor']('le code ici')();

Ce qui donne ce magnifique code que j'ai mis dans un Gist pour écrire 'Salut les terriens !' dans la console...

Vous vous doutez bien que je n’ai pas tapé tout cela à la main… Voici donc, pour votre plus grand plaisir, un code pour générer tout ça !

Cela vous rappelle peut-être quelque chose, il s'agit d'une version allégée de JSF**k !

En conclusion...

Une bonne raison de préférer le TypeScript

Je pense que vous l'aurez compris : on peut tout faire avec JavaScript, et on est surtout capable du pire avec le JavaScript. L'absence de typage fort et la possibilité de "forcer" les types des valeurs à la volée font que ce langage est susceptible de provoquer des comportements inattendus si on ne fait pas attention.

Là est tout l'avantage du TypeScript, qui permet entre autres le typage statique des variables, ce qui permet d'éviter de les réassigner à la volée avec des valeurs de types incompatibles entre elles à l'écriture via la coercition. Bien que le TypeScript soit transpilé en JavaScript au moment du package et du déploiement, cela permet d'éviter de manipuler des variables aux types inconnus (pour peu que le any soit proscrit, ce qui est une bonne pratique !)

Il serait théoriquement faisable de faire le même exercice avec du TypeScript, mais la coercition de type en TypeScript n'existe pas : seul le type casting existe. Il faut donc vraiment vouloir changer le type d'une valeur plutôt qu'"accidentellement", comme cela pourrait arriver en JavaScript.

Comme dit, cela est sans doute déjà arrivé à tout le monde de travailler avec des String en voulant travailler avec des nombres suite à un retour d'appel API par accident, et tomber sur des erreurs qui peuvent être très obscures... Et perdre du temps à la fois pour comprendre, savoir avec quoi on travaille et éventuellement débugger le code suite à une erreur.

Les langages ésotériques

Pourquoi mentionner les langages ésotériques ? Ces derniers partagent une caractéristique avec le JavaScript tel que décrit dans cet article : ils sont globalement difficiles à relire. Ils font partie de cette famille officieuse de langages dits write-only (bonjour le Perl !) : on écrit un programme, et après on ne peut plus le relire tellement la syntaxe est difficile à comprendre.

Ils sont notamment utilisés dans le cadre de jeux/concours tels que le Code Golfing, où il s'agit de résoudre certains problèmes avec le langage de votre choix en utilisant le moins de caractères possibles, et où certains langages ésotériques excellent :

  • Le Pyth, langage basé sur le Python ;
  • Le Retina, langage intégralement basé sur les expressions régulières ;
  • Le Malbolge-98, le langage le plus dur à apprendre, écrire et comprendre ;
  • Le Brainf**k, un langage qui a une grande ressemblance avec ce que je montre précédemment.

Ce sont des langages à usage récréatif, dont le but est de vous faire réfléchir un peu différemment quant à la résolution de problèmes et qui peuvent donner des idées pour vos travaux, vu que ça peut vous aider à trouver des approches différentes pour écrire des algorithmes.

Ces langages ne sont pas à utiliser en production, bien entendu !