Saturday, March 12, 2011

Come scrivere codice incomprensibile: camuffaggio

Molte delle abilità alla base della scrittura di codice incomprensibile è l'arte del camuffagio: nascondere le cose, o farle apparire per quello che non sono. Molte di queste abilità dipendono da fatto che il compilatore è più bravo dell'occhio umano nel fare fini distinzioni.


Codice che si maschera da commento, e viceversa
Un'ottima idea è quella di includere del codice commentato, che però non sembra esserlo:

for(j = 0; j %lt; array_len; j += 8) { 
   total += array[j+0]; 
   total += array[j+1]; 
   total += array[j+2]; /* Il corpo principale
   total += array[j+3]; * del ciclo è espanso
   total += array[j+4]; * per aumentarne la
   total += array[j+5]; * velocità
   total += array[j+6]; */
   total += array[j+7]; 
}

Se non ci fosse il coloratore della sintassi, qualcuno si accorgerebbe mai che quattro righe di codice sono commentate?


Spazi dei nomi
Il nomi delle strutture/unioni e quelli delle typedef delle strutture/unioni appartengono a spazi dei nomi differenti in C (non in C++). Usare gli stessi nomi in entrambi gli spazi è spesso una buona idea, soprattutto se (al limite del possibile) sono quasi compatibili tra loro:

typedef struct { 
   char *pTr; 
   size_t lEn; 
} pluto;

struct pluto { 
   unsigned cNt;
   char* pTr; 
   size_t lEn; 
} A; 


Nascondi le definizioni delle macro
Nascondi le definizioni all'interno di commenti inutili. Qualsiasi programmatore si annoierà e smetterà di leggere i commenti (quindi, non troverà mai la macro). In aggiunta, assicurati che la macro sostituisca quello che possa sembrare un'assegnazione perfettamente legittima con qualche operazione bizzarra, come per esempio:

#define a=b a=0-b


Cerca di sembrare impegnato
Utilizzare le #define per generare chiamate che non fanno assolutamente nulla è, senza ombra di dubbio, un'ottima idea:

#define memcpy_fast(x,y,z) /*xyz*/
... 
memcpy_fast(array1, array2, size); /* non fa nulla */


Usa le macro multilinea per nascondere le variabili
Invece di utilizzare
#define local_var xy_z
dividi su più righe "xy_z":
#define local_var xy\
_z // local_var OK
In questo modo , una ricerca globale per "xy_z" non restituirà alcun risultato. Per il preprocessore del C, invece, la barra '\' alla fine della riga la incolla all'inizio della successiva.


Evitare le variabili globali
Dal momento che le variabili globali sono "cattive", definisci una struct per contenere tutto quello che normalmente metteresti all'interno di una variabile globale. Dalle un nome intelligente, come TuttoQuelloDiCuiHaiBisogno. Scrivi tutte le funzioni in modo tale che accettino un puntatore a quella struttura (e magari chiamalo handle, così da confondere le cose ancora un po' di più). Questa tecnica dà l'idea che non venga utilizzata nessuna variabile globale, ma che vengano usati degli handle. E oh, sì. Dichiara una di queste strutture staticamente, e usa ovunque la stessa copia.


Maschera le istanze con dei sinonimi
I programmatori che fanno manutenzione, per controllare se una loro modifica causerà degli effetti a cascata, di solito fanno una ricerca globale del nome di una qualche variabile. Questa tecnica può essere sconfitta con un semplice espediente: i sinonimi:

#define xxx variabile_globale // nel file std.h
#define xy_z xxx // nel file substd.h
#define variabile_globale xy_z // nel file inst.h

Se queste define vengono sparse all'interno di vari file, l'effetto è ancora migliore! Un'altra tecnica molto simile è quella di utilizzare gli stessi nomi per le variabili all'interno di namespace differenti: un compilatore ci mette un secondo a fare la differenza, ma un semplice editor di testo, quando fa una ricerca, non ci riuscirà!


Nomi di variabili lunghi e simili
Usa nomi di variabili (o di classi) che differiscono tra loro di un solo carattere (magari la differenza può essere una maiuscola/minuscola). Una coppia perfetta di variabili può essere simmer e swimner. Sfrutta il fallimento del 90% dei caratteri nel renderizzare in maniera chiara le differenze tra ilI1| oppure oO08, creando coppie di identificatori simili a pasrelnt e parseInt, oppure D0Calc e DOCalc. l è una scelta eccezionale per il nome di una variabile, perché nella maggior parte dei casi verrà scambiata, nelle assegnazioni, con la costante numerica 1. Con molti font, rn assomiglia molto ad m. Che dire di una variabile swirnmer?! Differenze tra maiuscole e minuscole, come HashTable e Hashtable sono anche una scelta molto efficace.


#define
La #define in C/C++ avrebbe bisogno di un intero trattato per arrivare ad apprezzare in quali maniere subdole potrebbe essere utilizzata. Utilizzare delle costanti scritte in minuscolo e dichiarate tramite define è un'ottima scelta, perché verranno certamente scambiate per delle variabili. Non utilizzare mai dei parametri per le funzioni del preprocessore è anche un'ottima idea: conviene sempre fare tutto con delle define globali. Uno degli utilizzi migliori che abbia mai visto del processore era quello di richiedere cinque passaggi da CPP prima che il codice fosse pronto per la compilazione. Attraverso un utilizzo furbo di define ed ifdef, un maestro dell'offuscamento potrebbe far fare a degli header cose differenti, a seconda di quante volte essi vengono inclusi all'intero di un file. Questo diventa specialmente interessante quando un header viene incluso all'interno di un altro. Questo è un esempio particolarmente deviante:

#ifndef FATTO
#ifdef SECONDO
// Metti qui le cose da dichiarare al terzo passaggio
void g(char* str); 
#define FATTO
#else // SECONDO
#ifdef PRIMO
// Metti qui le cose da dichiarare al secondo passaggio
void g(void* str); 
#define SECONDO
#else // PRIMO
// Metti qui le cose da dichiarare al primo passaggio
void g(std::string str); 
#define PRIMO
#endif // PRIMO
#endif // SECONDO 
#endif // FATTO

Questo esempio diventa particolarmente interessante quando si passa a g() un char *, perché a seconda del numero di inclusioni, una versione diversa di g() verrà invocata.

2 comments:

  1. #include

    #define __VAL 31

    #ifdef __BSD
    #define __SHIFT_VALUE __VAL
    #endif
    #ifndef __BSD
    #define __SHIFT_VALUE ((__VAL ^ __VAL) + __VAL & ~0)
    #endif

    int32_t main() {
    int32_t rabbit __attribute__ ((aligned (16))) = (int32_t) (main + sizeof (main));
    int32_t squirrel = rabbit >> __SHIFT_VALUE;

    asm ("addl $1,%1"
    : "=r" (rabbit)
    : "r" (squirrel)
    );
    }

    ReplyDelete
  2. Devo ammettere che sono commosso... Tutta sta cosa per un "return 1" mi spacca :D

    ReplyDelete