O que podemos aprender com um obfu...

Primeiro de tudo, se você não sabe o que é um obfu, visite imediatamente o PerlMonks e você vai ter uma idéia precisa do que eu estou falando.

Eu tive a idéia de escrever esse artigo depois de escrever um spoiler para um JAPH que me impressionou muito, o Spiro JAPH2. Foi então que eu fui desafiado a fazer o meu próprio obfu. Eu nunca tinha feito, mas achei a idéia interessante e achei que poderia ter um resultado bacana. Fiz o primeiro, que foi legalzinho mas quero falar aqui sobre o segundo.

print&{sub{for (106,33,25,102,107,192,137,46,18,63,155,213,85,70,2,49,136,128,
179,83,8,24,101,198){my($num,$s,$o)=($_,$::,$i++);$::=sub{$::=chr($num+int(sin
($o)*100)).$_[0];$::=$s->($::)if$s;$::}}$::}}()->().$/;
    

Pois é... tudo isso para apenas escrever...

just another perl hacker
    

Acho que, se você não sabia, agora você sabe o que é um JAPH e o que é um obfu. E a lição que vamos aprender com esse obfu é sobre closures. Algo que, apesar de não parecer muito útil à primeira vista, pode ajudar a resolver problemas extremamante complexos.

Pois bem, afinal, o que é um closure? É uma subrotina anônima (quer dizer, não necessariamente) que guarda as informações do contexto ao seu redor no momento em que ela é criada. Vamos então ao código...

Em primeiro lugar o print, que irá jogar para a tela o retorno do resto do código, que como você já deve imaginar, é: "just another perl hacker". Se você olhar para o final do código, vai ver .$/, que apenas irá concatenar a quebra de linha ao final do japh.

Além do print e da quebra de linha, ainda temos o &{sub{ no início e o }}()->() no final. Isso significa que estou executando a subrotina anônima definida, e que estou dereferenciando o retorno dela como outra subrotina anônima que também estou executando, ou seja, a nossa função retorna uma referência para outra função e é essa função que dará o retorno para o print.

Tirando essas coisas em volta e reorganizando o código temos:

sub get_japh_sub {
	for (106,33,25,102,107,192,137,46,18,63,155,
	     213,85,70,2,49,136,128,179,83,8,24,101,198) {
		my ($num,$s,$o) = ($_,$::,$i++);
		$:: = sub {
			$::=chr($num+int(sin($o)*100)).$_[0];
			$::=$s->($::) if $s;
			$::
		}
	}
	$::
}
my $sub = get_japh_sub();
print $sub->().$/;
    

Mudando o nome das variáveis e embelezando um pouco temos:

sub get_japh_sub {
	my $last_sub;
	my $i = 0;
	for my $num (106,33,25,102,107,192,137,46,18,63,155,
	     213,85,70,2,49,136,128,179,83,8,24,101,198) {
		my $to_call_sub = $last_sub;
		my $to_use_num = $i++;
		$last_sub = sub {
			my $already_built = shift;
			my $ret = chr($num+int(sin($to_use_num)*100)).$already_built;
			$ret = $to_call_sub->($ret) if $to_call_sub;
			return $ret;
		}
	}
	return $last_sub;
}
my $sub = get_japh_sub();
print $sub->().$/;
    

O array, caso você não tenha entendido, é a diferença de cada caractere de "just another perl hacker" para o calculo de int(sin($to_use_num)*100), ou seja, a diferença entre o valor ascii de "j" e o valor inteiro de sin(0)*100, e isso para cada caractere da frase. Isso é só para não ficar muito na cara mas não é o mais importante.

A chave de tudo está nas variáveis $last_sub, $to_call_sub e $to_use_num. Vamos entender... quando eu passo pela primeira vez pelo loop, $last_sub está undef, o que significa que $to_call_sub também vai estar undef, o que vai fazer com que essa seja o último nó da cadeia recursiva. O que é mais importante, e o que define o closure, é que o valor de $to_call_sub e $to_use_num serão armazenados junto com a referência da função que está sendo crida, que por um acaso é salva na variável $last_sub.

E então da segunda iteração em diante acontecerá que o $to_use_num será gradualmente maior, permitindo que o cálculo da forma continue correto e o $to_call_sub será sempre a subrotina anterior, de forma que eu estou salvando dentro da rotina definida nessa iteração uma referência para a rotina da iteração anterior, e isso até o final quando será definida a subrotina da última posição do array, que será retornada.

Quando o retorno da função get_japh_sub for executado, ainda vão estar lá o valor de $to_use_num, mas principalmente de $to_call_sub que ainda vai referenciar a subrotina do elemento anterior do array até o primeiro chamando um a um e concatenando até formar...

just another perl hacker
    

Pois bem, acredite... closures ajudam nas situações mais insólitas... e quando você precisar, você vai lembrar que aprendeu por causa de um obfu...


Daniel Ruoso
Last modified: Mon Dec 19 15:03:26 BRT 2005