WTF.js Obfuscator

28

6

Background

One of the commonly meme'd aspects of javascript is its incredibly loose type coercion that allows for +!![]+[+[]] == '10' this technique can also be used to create letters as seen in the following example:

[[][[]]+[]][+[]][-~[]] == 'n'
[undefined+[]][+[]][-~[]] // [][[]] -> undefined
['undefined'][+[]][-~[]]  // undefined+[] -> 'undefined'
['undefined'][0][-~[]]    // +[] -> 0
'undefined'[-~[]]         // ['undefined'][0] -> 'undefined'
'undefined'[1]            // -~[] -> -(-1) -> 1
'n'

Some examples of js coercions:

[][[]] = undefined        // out of bounds array access returns undefined
+[][[]] = NaN             // the unary + operator coerces to numbers +undefined is NaN
+[] = 0                   // empty arrays coerce to the number 0
~[]/[] = -Infinity        // ~ coerces any value to a number then does bitwise negation and / coerces the second value to a number
[]+{} = '[Object object]' // plus coerces both values into strings if it can't do numeric addition
![] = false               // ! coerces empty arrays to false
!![] = true               // negation of previous coercion
+!![] = 1                 // number coercion of true
-~[] = 1                  // ~ coerces [] to 0 and the bitwise negation of 0 is -1, -(-1)=1
true+[] = 'true'          // adding an empty array to a primitive coerces it to a string
'true'[+[]] = 't'         // strings are treated as arrays by js
'true'[-~[]] = 'r'

Here's a reference table of js coercions using different operators

Note: blue shaded cells in the table are strings while yellow cells are literals.

Task

Given a string with only the letters abcdefijlnorstuy, convert it to valid javascript containing only the characters []+-~/{}! that evaluates to the original string. Note that this is not JSFuck but is instead focused purely on a subset of strings that can be (relatively) concisely translated.

Input

Any lowercase string that can be written with the letters abcdefijlnorstuy (can include spaces)

Output

Valid javascript containing only the characters []+-~/{}! that will evaluate to the original word in a js environment.

Examples

f('u') -> [[][[]]+[]][+[]][+[]]

f('a') -> [+[][[]]+[]][+[]][-~[]]

f('t') -> [-~[]/[]+[]][+[]][-~[]-~[]-~[]-~[]-~[]-~[]]

f('o') -> [[]+{}][+[]][-~[]]

f('l') -> [![]+[]][+[]][-~[]-~[]]

f('r') -> [!![]+[]][+[]][-~[]]

f('defined') -> [[][[]]+[]][+[]][-~[]-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]]+[[][[]
]+[]][+[]][-~[]-~[]-~[]-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]-~[]-~[]]+[[][[]]+[]][
+[]][-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]]+[[][[]]+[]][+[]][-~[]-~[]]

f('aurora borealis') -> [+[][[]]+[]][+[]][-~[]]+[[][[]]+[]][+[]][+[]]+[!![]+[]][
+[]][-~[]]+[[]+{}][+[]][-~[]]+[!![]+[]][+[]][-~[]]+[+[][[]]+[]][+[]][-~[]]+[[]+{
}][+[]][-~[]-~[]-~[]-~[]-~[]-~[]-~[]]+[[]+{}][+[]][-~[]-~[]]+[[]+{}][+[]][-~[]]+
[!![]+[]][+[]][-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]]+[+[][[]]+[]][+[]][-~[]]+[![]
+[]][+[]][-~[]-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]-~[]-~[]]+[![]+[]][+[]][-~[]-~[
]-~[]]

f('code tennis') -> [[]+{}][+[]][-~[]-~[]-~[]-~[]-~[]]+[[]+{}][+[]][-~[]]+[[][[]
]+[]][+[]][-~[]-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]]+[[]+{}][+[]][-~[]-~[]-~[]-~[
]-~[]-~[]-~[]]+[-~[]/[]+[]][+[]][-~[]-~[]-~[]-~[]-~[]-~[]]+[[][[]]+[]][+[]][-~[]
-~[]-~[]]+[[][[]]+[]][+[]][-~[]]+[[][[]]+[]][+[]][-~[]]+[[][[]]+[]][+[]][-~[]-~[
]-~[]-~[]-~[]]+[![]+[]][+[]][-~[]-~[]-~[]]

f('js bad') -> [[]+{}][+[]][-~[]-~[]-~[]]+[![]+[]][+[]][-~[]-~[]-~[]]+[[]+{}][+[
]][-~[]-~[]-~[]-~[]-~[]-~[]-~[]]+[[]+{}][+[]][-~[]-~[]]+[+[][[]]+[]][+[]][-~[]]+
[[][[]]+[]][+[]][-~[]-~[]]

f('obfuscate') -> [[]+{}][+[]][-~[]]+[[]+{}][+[]][-~[]-~[]]+[[][[]]+[]][+[]][-~[
]-~[]-~[]-~[]]+[[][[]]+[]][+[]][+[]]+[![]+[]][+[]][-~[]-~[]-~[]]+[[]+{}][+[]][-~
[]-~[]-~[]-~[]-~[]]+[+[][[]]+[]][+[]][-~[]]+[-~[]/[]+[]][+[]][-~[]-~[]-~[]-~[]-~
[]-~[]]+[[][[]]+[]][+[]][-~[]-~[]-~[]]

Rules

Lowest byte count wins.

The solution can be written in any language, but the generated code must evaluate in either chrome, firefox, or a node.js repl.

Don't abuse any common loopholes.

begolf123

Posted 2020-01-24T18:35:17.007

Reputation: 531

2This is wonderful. Thanks. Are the other letters impossible to get with []+-~/{}!? – Eric Duminil – 2020-01-25T10:19:10.930

4

Yeah, adding () allows you to get all letters since it allows you to create and call functions to get new strings, this is the concept behind JSFuck. But that becomes even more dependent on JS knowledge than this question already is.

– begolf123 – 2020-01-25T14:44:41.487

1f('js bad') made me chuckle – Tvde1 – 2020-01-27T11:33:54.020

Answers

19

JavaScript (ES6),  139  135 bytes

s=>[...s].map(c=>`[{},!![,[][[],![,~[]/[`.split`,`.some(s=>i=~eval(S=`[${s}]+[]][+[]]`).search(c))&&S+`[${'-~[]'.repeat(~i)}]`).join`+`

Try it online!

How?

The following code snippets are used:

 code             | string            | used for
------------------+-------------------+-------------------------------------
 [[{}]+[]][+[]]   | '[object Object]' | space, 'b', 'c', 'e', 'j', 'o', 't'
 [!![]+[]][+[]]   | 'true'            | 'r', 'u'
 [[][[]]+[]][+[]] | 'undefined'       | 'd', 'f', 'i', 'n'
 [![]+[]][+[]]    | 'false'           | 'a', 'l', 's'
 [~[]/[]+[]][+[]] | '-Infinity'       | 'y'

We pick the appropriate character in the resulting string with [-~[]-...-~[]].

We can't access index \$0\$ this way, so we make sure that the snippets are ordered in such a way that this is never required. For instance, 'u' is taken from 'true' rather than from 'undefined'.

Arnauld

Posted 2020-01-24T18:35:17.007

Reputation: 111 334

I was sure to see your answer! Waiting for explanation – AZTECCO – 2020-01-24T20:21:56.727

Any other language would be more impressive :p – Jonathan Allan – 2020-01-24T20:22:38.740

@JonathanAllan Done. :) But my Python code is probably terribly golfed... – Arnauld – 2020-01-24T21:39:28.713

16

I have a solution which works if you have infinite time and memory. I'm not 100% sure whether that's allowed within the rules here; I searched on codegolf.meta.SE and didn't find anything saying that code-golf answers have to run in a reasonable length of time using a reasonable amount of memory. I also didn't find it forbidden in the "common loopholes" thread. If it makes my solution invalid, please let me know.

JavaScript (ES6), 112, 110, 107 106 bytes

s=>{for(q=[''];;c=q.shift(),q.push(...[...'[]+-~/{}!'].map(x=>c+x)))try{return eval(c)===s?c:d}catch(e){}}

It isn't restricted to string inputs, and can find short solutions in reasonable times, but unfortunately it's not feasible to solve for non-empty strings, even just single-characters like 'o'. Here it is working on some inputs for which it is feasible.

> f(0)
"+[]"
> f(1)
"-~[]"
> f(-1)
"~[]"
> f(true)
"!+[]"
> f(false)
"![]"
> f('')
"[]+[]"

How?

It's a breadth-first search. q is the queue, c is the current string. The breadth-first search algorithm will iterate through every possible string formed from the given alphabet, in order of length, so if a solution exists, it will be found.

We try evaluating c with the eval function, and return it if it evaluates to s. This comparison is done with === to avoid e.g. returning +[] which evaluates to 0 when we're looking for an expression which evaluates to the empty string. The variable c is uninitialised on the first iteration of the loop, but that's OK because it's only used within the try block.

We need try/catch because most candidate strings have syntax errors; as a bonus, instead of guarding the return with an if statement, we can return the non-existent name d because the error will get caught, allowing the loop to continue searching. This saves one byte, but it means the function stops working if you declare a variable named d, which is mildly amusing.

kaya3

Posted 2020-01-24T18:35:17.007

Reputation: 261

1Yeah this is clever. I like it because it doesn't require the knowledge provided in the question of what gets coerced to what. You just brute force the allowed alphabet. You're essentially offloading all js knowledge to be dependant on "eval" – Cruncher – 2020-01-27T19:44:04.337

11

Python 2, 125 bytes

Using a single code snippet.

print[]
for c in input():print'+[{}+!![]+![]+~[]/[]+[][[]]][+[]]['+'-~[]'*(ord('#2??;"?4?9 &?/5%03!$B'[ord(c)%59%21])-31)+']'

Try it online!

Try the output in JS!


Python 2,  167  150 bytes

Saved 2 bytes thanks to @JonathanAllan

print[]
for c in input():i=ord(".@C1A83J42/K70DIY"[ord(c)*91%211%23]);print'+['+"[{} !![ [][[] ![ ~[]/[".split()[i/9-5]+']+[]][+[]]['+'-~[]'*(i%9)+']'

Try it online!

Try the output in JS!

How?

This is based on the same code snippets as my JS answer, but a lookup string is used to retrieve the snippet ID and the position of the character.

Given the ASCII code \$c\$ of the character to be converted, the position in the lookup string is given by:

$$i=((c\times91)\bmod211)\bmod23$$

Given the ASCII code \$n\$ of the \$i\$-th character in the lookup string ".@C1A83J42/K70DIY":

  • the snippet ID is \$\lfloor(n-45)/9\rfloor=\lfloor n/9\rfloor-5\$
  • the position of the character is \$(n-45)\bmod9=n\bmod9\$

This is summarized in the following table.

 char. | ASCII code | -45 | //9 | mod 9 | character lookup     | output
-------+------------+-----+-----+-------+----------------------+--------
  '.'  |     46     |   1 |  0  |   1   | '[object Object]'[1] |  'o'
  '@'  |     64     |  19 |  2  |   1   | 'undefined'[1]       |  'n'
  'C'  |     67     |  22 |  2  |   4   | 'undefined'[4]       |  'f'
  '1'  |     49     |   4 |  0  |   4   | '[object Object]'[4] |  'e'
  'A'  |     65     |  20 |  2  |   2   | 'undefined'[2]       |  'd'
  '8'  |     56     |  11 |  1  |   2   | 'true'[2]            |  'u'
  '3'  |     51     |   6 |  0  |   6   | '[object Object]'[6] |  't'
  'J'  |     74     |  29 |  3  |   2   | 'false'[2]           |  'l'
  '4'  |     52     |   7 |  0  |   7   | '[object Object]'[7] |  ' '
  '2'  |     50     |   5 |  0  |   5   | '[object Object]'[5] |  'c'
  '/'  |     47     |   2 |  0  |   2   | '[object Object]'[2] |  'b'
  'K'  |     75     |  30 |  3  |   3   | 'false'[3]           |  's'
  '7'  |     55     |  10 |  1  |   1   | 'true'[1]            |  'r'
  '0'  |     48     |   3 |  0  |   3   | '[object Object]'[3] |  'j'
  'D'  |     68     |  23 |  2  |   5   | 'undefined'[5]       |  'i'
  'I'  |     73     |  28 |  3  |   1   | 'false'[1]           |  'a'
  'Y'  |     89     |  44 |  4  |   8   | '-Infinity'[8]       |  'y'

Arnauld

Posted 2020-01-24T18:35:17.007

Reputation: 111 334

You can get rid of the print[] and put '[]\n' as an argument for input to save 2 bytes. – mypetlion – 2020-01-25T00:29:24.340

1How the hell does one come up with such lookup methods? – RGS – 2020-01-25T01:02:45.533

@RGS I'd be interested too. I suppose brute-forcing is doable since all integers are relatively small, and there aren't many of them. There might be more elegant solutions though. – Eric Duminil – 2020-01-25T22:58:04.573

2@RGS This was brute-forced.I tried the following patterns: N*p%m0, N*p%m0%m1, N%m0%m1%m2, N%m0%m1, N%m0, x%N%m0%m1%m2, x%N%m0%m1, x%N%m0 with limited ranges for each parameter. – Arnauld – 2020-01-25T23:01:31.180

@Arnauld Ty for your reply, and quite interesting actually! – RGS – 2020-01-25T23:37:58.010

8

Prolog (SWI), 269 bytes

It's not a particularly short solution, but made this challenge interesting because it is able to use backtracking to automatically select which string and index into that string to use for each character.

`fals`^`![]+[]`.
`[object `^`[]+{}`.
`und`^`[][[]]+[]`.
`-Infinity`^`~[]/[]+[]`.
`tr`^`!![]+[]`.
[]-`[]`.
[H|T]-S:-B^C,nth0(I,B,H),length(L,I),foldl([_,V]>>append(`-~[]+`,V),L,` +[]`,J),T-U,append([`[`,C,`][+[]][`,J,`]+`,U],S).
S/W:-S+A,A-B,W+B.
A+B:-string_codes(A,B).

Try it online!

Ungolfed Code

% Declaration of strings we can generate with their corresponding JS.
base_str(`undefined`,`[[][[]]+[]][+[]]`).
base_str(`[object Object]`, `[]+{}`).
base_str(`Infinity`, `[-~[]/[]+[]][+[]]`).
base_str(`NaN`, `[+[][[]]+[]][+[]]`).
base_str(`true`, `!![]+[]`).
base_str(`false`, `![]+[]`).

% Construct JS used to index into strings
wtf_index(0,`+[]`).
wtf_index(1,`-~[]`).
wtf_index(N, S0) :-
  N > 1,
  wtf_index(1, S1),
  M is N - 1,
  wtf_index(M, S2),
  append([S1,`+`,S2],S0).

% Construct JS used for single char
wtf_char(C, WC) :-

  % Find known string with desired character at some index
  base_str(S, WS),
  nth0(I, S, C),

  % JS for this index
  wtf_index(I, WI),

  % JS to select index from string
  append([`(`,WS,`)[`,WI,`]`],WC).

% Map wtf_char over string, reducing to single string
wtf_list([], `[]`).
wtf_list([H|T], WS) :-
  wtf_char(H, WH),
  wtf_list(T, WT),
  append(WH, [0'+|WT], WS).

wtf_str(S, W) :-
  string_codes(S, SC),
  wtf_list(SC, WC),
  string_codes(W, WC).

ankh-morpork

Posted 2020-01-24T18:35:17.007

Reputation: 1 350

5

JavaScript (Node.js), 104 bytes

s=>[...s].map(c=>(p=`[{}+!![]+![]+~[]/[]+[][[]]][+[]]`)+`[${'-~[]'.repeat(eval(p).search(c))}]`).join`+`

Try it online!

Explanation

Each letter is converted to a string of the form

[{}+!![]+![]+~[]/[]+[][[]]][+[]][x]

Where x is a number of -~[]

[{}+!![]+![]+~[]/[]+[][[]]][+[]] evaluates to the string [object Object]truefalse-Infinityundefined, which contains all of our letters. We then index into it by using this snippet:

`[${'-~[]'.repeat(eval(p).search(c))}]`

It evaluates [{}+!![]+![]+~[]/[]+[][[]]][+[]] (stored in variable p), then finds the first index of the current letter we are processing. We repeat the string +~-[] (evaluates to 1) that number of times. That is wrapped around with square brackets, and appended to p.

Finally, everything is joined with +.

Embodiment of Ignorance

Posted 2020-01-24T18:35:17.007

Reputation: 7 014

3

C# (Visual C# Interactive Compiler), 156 bytes

x=>string.Join('+',x.Select(l=>"[{}+!![]+![]+~[]/[]+[][[]]][+[]]["+new StringBuilder().Insert(0,"-~[]",$"[object {0,8}ru fals{0,9}y nd fi".IndexOf(l))+"]"))

Try it online!

Explanation

Each letter is converted to a string of the form

[{}+!![]+![]+~[]/[]+[][[]]][+[]][x]

Where x is a number of -~[]

[{}+!![]+![]+~[]/[]+[][[]]][+[]] evaluates to [object Object]truefalse-Infinityundefined, which contains all the letters. We index into the string $"[object {0,8}ru fals{0,9}y nd fi", which expands to

[object        0ru fals        0y nd fi

We take the current letter and get the first index of it in that string. We then repeat the string -~[] that number of times, and replace the x with that.

Repeating a string is rather verbose in C#, as there is no builtin for it. Instead, a StringBuilder is created, and taking advantage of the StringBuilder.Insert(int, string, int) overload (which inserts the string n2 times at index n1) to created a StringBuilder containing a repeated string. It is implicitly converted to a string due to the magic of the + operator.

new StringBuilder().Insert(0,"+-~[]",$"[object {0,8}ru fals{0,9}y nd fi".IndexOf(l))

Finally, the converted strings are joined together with +.

Embodiment of Ignorance

Posted 2020-01-24T18:35:17.007

Reputation: 7 014

You can save 3 bytes by removing the unnecessary +[]. – Neil – 2020-01-25T11:14:04.870

2

Charcoal, 95 bytes

≔⪪“<≦ω≕ε…(Bς´B⌈YWγ№Φξ⌕IY¦l ”,ηFS«≔⊟Φ⁵№§ηκιζ⊞υ⪫[]⁺⁺§⪪“"∧|⌊AuüQ⁷~⊙<⁷≕u?ψμ”,ζ×+[]][²×-~[]⌕§ηζι»⪫υ+

Try it online! Link is to verbose version of code. Explanation:

≔⪪“<≦ω≕ε…(Bς´B⌈YWγ№Φξ⌕IY¦l ”,η

Split the compressed string "\n\nd,\nals,\nru,\\nnfi\n\n\ny,\nobject " on commas. This is the array ["undefined", "false", "true", "Infinity", "[object Object]"] keeping only the space and unique letters and truncated at the last remaining letter to improve the compression.

FS«

Loop over the input string.

≔⊟Φ⁵№§ηκιζ

Find the entry that contains the current input character.

⊞υ⪫[]⁺⁺§⪪“"∧|⌊AuüQ⁷~⊙<⁷≕u?ψμ”,ζ×+[]][²×-~[]⌕§ηζι

Split the compressed string "[][[]],![],!![],~[]/[],{}" on commas and take the matching entry, then concatenate it with "+[]][" doubled and a suitable number of repetitions of "-~[]" before finally wrapping everything inside a final set of [].

»⪫υ+

Join everything together with +s.

Neil

Posted 2020-01-24T18:35:17.007

Reputation: 95 035

1@Arnauld Thanks for pointing that out; luckily that means that there's some duplication which means it only costs 1 byte. – Neil – 2020-01-25T15:14:01.303