2005 Competition

In conjunction with the 11th International Erlang User Conference, an Erlang Obfuscated Programming Competition was held. The goal of this competition was to write the most obfuscated Erlang program, providing a safe forum for poor coding practices and programming styles. Through this competition, we hope to illustrate some of the subtleties of Erlang and how they can best be used and abused.


A report from the judges

This year's "Subject Matter Experts" were

Mike Williams, Ericsson, Sweden
Thomas Arts, IT University, Sweden
Francesco Cesarini, Erlang Solutions, UK

A call for participation to the competition was made at the Erlang workshop in Tallinn. A total of 8 submissions were handed in on time. All were ingenious, making it very hard to pick a winner. The judges individually gave every entry a grade between 1 - 5 in the following categories:

  • Obfuscation
  • Style
  • Innovation
  • Functionality

When adding up the grades, 90% of the submissions were just points from each other! Luckily, there were two distinct winners. We had to take it a step further, however, and introduce a third prize, The Judge's Prize, as one of the submissions had actually been used in a live system!

When submissions from the competitions started coming in, we quickly realized we had omitted an important rule. To tell us what the code actually does!!!! In most cases, we were able to figure it out, but it was not easy. We will not do the same mistake next year.

Winners


First Prize: Martin Bjorklund


First prize goes to Martin for his program which "exemplifies the worst mis-design in Erlang". This program really is counter intuitive, even after reading it several times. It demonstrates that Erlang has indeed an error in its design, namely the binding order of logical operands. Here is the source code:

-module(obf2).
-export([g/0]).

g() -> h(str()).

str() -> "'erlang is da most beautiful language there is'".

h(X) ->
ListIsInteger = is_integer([]),
AnAtom = an_atom(),
One = one(),
P = (1 == 1 orelse is_integer(1)),
if
P == (1 == 1 or is_integer(1)) ->
erlang:display("orelse works as or"),
erlang:display(X);
1 == 1 or is_integer(1) ->
erlang:display("is_integer"),
erlang:display(X);
%% whoops, compiler didn't like this one
% 1 == 1 orelse is_integer(1) ->
% erlang:display("is_integer"),
% erlang:display(X);
ListIsInteger == true, is_integer(AnAtom) ->
erlang:display("ListIsInteger"),
erlang:display(X);
ListIsInteger == true and is_integer(AnAtom) ->
erlang:display(f(3,"sa|tde_h{rwdjgdeqauzshihu",X));
1 == One; is_integer(One) ->
erlang:display("1 == 1 or 1 is_integer"),
erlang:display(X);
true ->
erlang:display("one of them _must_ be true!?!")
end.

an_atom() -> foo.

one() -> 1.

f(3,[],_) ->
f(9,"akf","?hdd>-[ic]hsj!uryskeuih");
f(A,Q,X) when erlang:is_integer(Q) ->
if
- Q < - 1 ->
f(A + 1,Q - 1,erlang:tl(X));
1 == 1; is_integer(1) ->
erlang:hd(X)
end;
f(9,[],_) ->
[];
f(A,Q,X) ->
[f(A - 1,erlang:HD(Q) - $a + A,X)|f(A,erlang:TL(Q),X)].

This is what happens when you run the program:

Eshell V5.4.8  (abort with ^G)
1> c(obf2).
./obf2.erl:22: Warning: this expression would cause a 'badarg'
exception at run-time
./obf2.erl:25: Warning: this expression would cause a 'badarg'
exception at run-time
{ok,obf2}
2> obf2:g().
"erlang's binding rules suck!"
true



Second Prize: Urban Boquist


Second prize goes to Urban Urban Boquist who wrote a solver for Sudoku. Sudoku is a puzzle in which you are given a matrix with some numbers in it. One needs to complete the matrix with numbers in such a way that all rows, all columns and all 3x3 blocks contain the digits 1..9 exactly once. We were impressed by the small program solving this task. One of the judges found on open source contribution with 44 Java files, 0.5MB in it, doing approximately the same!

:

                             -module(e).
-export([main/1]).
-import(lists,[all/2]).
-define(t(A,B),B
andalso A).

f(E,R,L,A,N,G) ->

if E>G->{c,N};L>G->f(E+1,R,R,R,N,G);A>G->c(
R,N,G);true->case g(E,L,N)of g->f(E,R,L+1,R,N,G
);_->b(G,E,A,R,N,L)end end. d(A,B)->h(3#21,B,A). h(
A,B,C)->case f(C, C,C,C,[{C-1,C-1,a}|B],A
+2)of{c,D}->e(A+ 1,TL(lists:sort((D))));
_->fail end. main (A)->d(1,A). b(E,R,L,A,
N,G)when L>E->f( R,A,G,L,N,E);b(L,A,N,G,
E,R)->V=round(math:sqrt(L)),C=fun(I)->(I-1)div V*V end,D=fun
({_,_,Q})->N/=Q end,case?t(all(D,[{I,O,S}||{I,O,S}<-E,A==I]),
?t(all(D,[{I,S,O}||{I,S,O}<-E,R==S]),all(D,lists:filter(fun({
O,S,_})->?t(C(A)
<O,?t(O=<C(A)+V,
?t(C(R)<S,S=<C(R
)+V)))end,E))))of true
->f(A,G,R+1,G,[{A ,R,N}|E
],L);_->b(L,A,N+1, G,E,R)end.
a(D,{C,A})->[[B|| {_,_,B}<-C]|
e(D-1,A)]. e(_,[ ])->[];e(E,L)
->a(E+1,lists:split(E+1,L)). c(_,[{_,_,a}|_],_)->b
;c(B,[{R,K,N}|S],A)->f(R,B,K,N+1,S,A);c(_,[],_)
->b. g(B,A,[{B,A,_}|_])->g;g(_,_,[])->h;g(B
,A,[_|C])->g(B,A,C).



Judge's Prize: Mats Cronqvist


A special mention goes to a one liner which "has actually been used on a live system (Can't tell you which)! Mats Cronqvist has a great example of a real live obfuscated, ugly, and non understandable piece of code. We showed it at EUC just after ensuring that no one if responsible for Quality Assurance in his project was present. It is a one-line implementation of a safe(r) subset of dbg (it had to be pasted into an erlang shell).

Pi = fun(P) when pid(P) -> case process_info(P, registered_name) of
[] -> case process_info(P, initial_call) of {_, {proc_lib,init_p,5}}
-> proc_lib:translate_initial_call(P); {_,MFA} -> MFA; undefined ->
unknown end; {_,Nam} -> Nam; undefined -> unknown end; (P) when
port(P) -> {name,N} = erlang:port_info(P,name), [Hd|_] =
string:tokens(N," "), Tl =
lists:reverse(hd(string:tokens(lists:reverse(Hd),"/"))),
list_to_atom(Tl); (R) when atom(R) -> R; ({R,Node}) when atom(R),
Node == node() -> R; ({R, Node}) when atom(R), atom(Node) -> {R,
Node} end, Ts = fun(Nw) -> {_,{H,M,S}} =
calendar:now_to_local_time(Nw), {H,M,S,element(3,Nw)} end, Munge =
fun(I) -> case string:str(I, "Return addr") of 0 -> case
string:str(I, "cp = ") of 0 -> []; _ -> [_, C|_] =
string:tokens(I,"()+"), list_to_atom(C) end; _ -> case string:str(I,
"erminate process normal") of 0 -> [_, C|_] =
string:tokens(I,"()+"), list_to_atom(C); _ -> [] end end end, Stack
= fun(Bin) -> L = string:tokens(binary_to_list(Bin),"\n"),
{stack,lists:flatten(lists:map(Munge,L))} end, Prc = fun(all) ->
all; (Pd) when pid(Pd) -> Pd; ({pid,P1,P2}) when integer(P1),
integer(P2) -> c:pid(0,P1,P2); (Reg) when atom(Reg) -> case
whereis(Reg) of undefined -> exit({rdbg, no_such_process, Reg}); Pid
when pid(Pid) -> Pid end end, MsF = fun(stack, [{Head,Cond,Body}])
-> [{Head,Cond,[{message,{process_dump}}|Body]}]; (return,
[{Head,Cond,Body}]) -> [{Head,Cond,[{return_trace}|Body]}]; (Head,
[{_,Cond,Body}]) when tuple(Head)-> [{Head,Cond,Body}]; (X,_) ->
exit({rdbg,bad_match_spec,X}) end, Ms = fun(Mss) -> lists:foldl(MsF,
[{'_',[],[]}], Mss) end, ChkTP = fun({M,F}) when atom(M), atom(F),
M/='_', F/='_' -> {{M,F,'_'},[],[global]}; ({M,F,MS}) when atom(M),
atom(F), M/='_', F/='_' -> {{M,F,'_'},Ms(MS),[global]};
({M,F,MS,local}) when atom(M), atom(F), M/='_', F/='_' ->
{{M,F,'_'},Ms(MS),[local]}; ({M,F,MS,global}) when atom(M), atom(F),
M/='_', F/='_' -> {{M,F,'_'},Ms(MS),[global]}; (X) ->
exit({rdbg,unrec_trace_pattern,X}) end, ChkTPs = fun(TPs) when
list(TPs) -> lists:map(ChkTP,TPs); (TP) -> [ChkTP(TP)] end, SetTPs =
fun({MFA,MS,Fs}) -> erlang:trace_pattern(MFA,MS,Fs) end, DoInitFun =
fun(Time) -> erlang:register(rdbg, self()),
erlang:start_timer(Time,self(),{die}),
erlang:trace_pattern({'_','_','_'},false,[local]),
erlang:trace_pattern({'_','_','_'},false,[global]) end, InitFun =
fun(Time,all,send) -> exit({rdbg,too_many_processes});
(Time,all,'receive') -> exit({rdbg,too_many_processes}); (Time,P,
send) -> DoInitFun(Time),
erlang:trace(Prc(P),true,[send,timestamp]); (Time,P,'receive') ->
DoInitFun(Time), erlang:trace(Prc(P),true,['receive',timestamp]);
(Time,P,TPs) -> CTPs = ChkTPs(TPs), DoInitFun(Time),
erlang:trace(Prc(P),true,[call,timestamp]),
lists:foreach(SetTPs,CTPs) end, LoopFun = fun(G,N,Out) when N < 1
-> erlang:trace(all,false,[call,send,'receive']),
erlang:trace_pattern({'_','_','_'},false,[local]),
erlang:trace_pattern({'_','_','_'},false,[global]), io:fwrite("**
rdbg, ~w msgs **~n", [length(Out)]),
io:fwrite("~p~n",[lists:reverse(Out)]), io:fwrite("~p~n",
[process_info(self(),message_queue_len)]); (G,Cnt,Out) -> case
process_info(self(),message_queue_len) of {_,N} when N > 100 ->
exit({rdbg,msg_queue,N}); _ -> ok end, receive {timeout,_,{die}} ->
G(G,0,Out); {trace_ts,Pid,send,Msg,To,TS} ->
G(G,Cnt-1,[{send,Ts(TS),Pi(To),Msg}|Out]);
{trace_ts,Pid,'receive',Msg,TS} ->
G(G,Cnt-1,[{'receive',Ts(TS),Msg}|Out]);
{trace_ts,Pid,return_from,MFA,V,TS} ->
G(G,Cnt-1,[{return,MFA,V}|Out]); {trace_ts,Pid,call,MFA,B,TS} when
binary(B) -> G(G,Cnt-1,[{Pi(Pid),Ts(TS),{Stack(B),MFA}}|Out]);
{trace_ts,Pid,call,MFA,TS} -> G(G,Cnt-1,[{Pi(Pid),Ts(TS),MFA}|Out])
end end, Rdbg = fun(Time,Msgs,Proc,Trc) when integer(Time),
integer(Msgs) -> Start = fun() ->
InitFun(Time,Proc,Trc),LoopFun(LoopFun,Msgs,[]) end,
erlang:spawn_link(Start) end.