Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IOCCC 1984/decot.c - can it be compiled in the 21st century?

This fascinating piece of code was featured in the very first (1984) edition of the International Obfuscated C Code Contest:

http://www.ioccc.org/years.html#1984 (decot)

After clearing through the debris of preprocessor abuse and unused code caused by a goto and some sneaky comments, you end up with the following surviving code (please correct me if I am wrong!):

#include <stdio.h> //used to suppress warnings
#include <math.h> //used to suppress warnings
extern int fl00r; //renamed to not clash with floor from math.h - unless it's part of the trickery???
int b, k['a'] = {
    sizeof(int(*)()),
    };
struct tag {int x0,*xO;}

*main(int i, int dup, int signal) { //int added to suppress warnings
  for(signal=0;*k *= * __FILE__ *i;) {
   printf(&*"'\",=);    /*\n\\", (*((int(*)())&fl00r))(i)); //see line 3
   if(b&&k+sin(signal)/ * ((main) (b)-> xO));
  }
}

There is a single compiler error left to conquer:

decot.c: In function 'main':
decot.c:12:28: error: too few arguments to function 'main'
   12 |    if(b&&k+sin(signal)/ * ((main) (b)-> xO));
      |                            ^
decot.c:9:2: note: declared here
    9 | *main(int i, int dup, int signal) {
      |  ^~~~

I suspect that the way compilers worked back in the day meant that you could somehow call main with just 1 argument even though it was specifically defined with 3 as in this case.

Is this accurate? Am I missing something? What are the least changes necessary to enable this code to compile nowadays?

I used GCC 9.2.0 with the suggested build command in the Makefile.

Thanks in advance and apologies if I have missed something very obvious!

like image 512
ChemicalDruid Avatar asked Sep 30 '20 22:09

ChemicalDruid


3 Answers

tl;dr; your mistake was to give an ANSI C prototype to the main function (i.e. changing i to int i, etc), which directed the compiler to check its arguments where it was called and cause that too few arguments error.

Example:

echo 'int foo(a,b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
# K&R C function OK, no errors

echo 'int foo(int a, int b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
...
<stdin>:1:54: error: too few arguments to function ‘foo’

That code should be preprocessed with a traditional C preprocessor, not with an "ANSI C" one. Using a standard preprocessor will result in some artifacts, like << = instead of <<=, * = instead of *=, etc.

cpp -traditional-cpp -P decot.c > decot1.c

After adding correct function declarations, and adding a cast --see the diff & result at the end of this answer-- you get something that compiles with a single warning in c89 (and a couple of them in c99), and, as described, prints some garbage to stdout:

$ cc -std=c89 decot1.c -lm -o decot1
decot1.c: In function ‘main’:
decot1.c:13:33: warning: function called through a non-compatible type
    (printf(&*"'\",x); /*\n\\", (*((int(*)())&floor))(i)));
                                ~^~~~~~~~~~~~~~~~~~~~
$ ./decot1
'",x);  /*
\

Which is exactly the same thing I get when compiling & running the original on V7 Unix, so it should be just right ;-)

decot1.c

double floor(double);
double sin(double);
int printf(const char*, ...);
int b,
k['a'] = {sizeof(
    int(*)())
,};
struct tag{int x0,*xO;}

*main(i, dup, signal) {
{
  for(signal=0;*k *= * "decot.c" *i;) do {
   (printf(&*"'\",x);   /*\n\\", (*((int(*)())&floor))(i)));
        goto _0;

_O: while (!(k['a'] <<= - dup)) {
        static struct tag u ={4};
  }
}


while(b = 3, i); {
k['a'] = b,i;
  _0:if(b&&k+
  (int)(sin(signal)             / *    ((main) (b)-> xO)));}}}

diff

$ diff -u decot1.c~ decot1.c
--- decot1.c~
+++ decot1.c
@@ -1,4 +1,6 @@
-extern int floor;
+double floor(double);
+double sin(double);
+int printf(const char*, ...);
 int b,
 k['a'] = {sizeof(
     int(*)())
@@ -20,4 +22,4 @@
 while(b = 3, i); {
 k['a'] = b,i;
   _0:if(b&&k+
-  sin(signal)          / *    ((main) (b)-> xO));}}}
+  (int)(sin(signal)            / *    ((main) (b)-> xO)));}}}
like image 113
user414777 Avatar answered Oct 09 '22 23:10

user414777


You cannot compile the original code anymore as it is. The earliest C standard GCC claims to support is C89, and the code from this contest is from before that. You did already a good job at porting it to more modern compilers. But there are more issues left than just the number of arguments of main():

  • Clang and GCC both know that sin() returns a double, even if you don't #include <math.h>, and refuse to do add a double to an int * in the subexpression k+sin(signal). TCC seems to accept it though.
  • The variable fl00r is declared but not defined, the linker will complain about an undefined reference to it.

Note that the original code can be compiled by TCC by just avoiding using x in combinations such as <<x (the preprocessor nowadays only substitutes complete tokens, and <<x counts as one token), and by calling main() with three arguments.

Your version of the code can be compiled by GCC by removing the #include statements, going back to using floor(), but forward declaring it as following:

floor();

To avoid complaints about sin(), use the -fno-builtin compiler flag. Then fix the issue with main() by calling it like (main) (b,b,b).

like image 23
G. Sliepen Avatar answered Nov 06 '22 05:11

G. Sliepen


tl;dr; your mistake was to give an ANSI C prototype to the main function (i.e. changing i to int i, etc), which directed the compiler to check its arguments where it was called and cause that too few arguments error.

Example:

echo 'int foo(a,b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
# K&R C function OK, no errors

echo 'int foo(int a, int b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
...
<stdin>:1:54: error: too few arguments to function ‘foo’

That code should be preprocessed with a traditional C preprocessor, not with an "ANSI C" one. Using a standard preprocessor will result in some artifacts, like << = instead of <<=, * = instead of *=, etc.

cpp -traditional-cpp -P decot.c > decot1.c

After adding correct function declarations, and adding a cast --see the diff & result at the end of this answer-- you get something that compiles with a single warning in c89 (and a couple of them in c99), and, as described, prints some garbage to stdout:

$ cc -std=c89 decot1.c -lm -o decot1
decot1.c: In function ‘main’:
decot1.c:13:33: warning: function called through a non-compatible type
    (printf(&*"'\",x); /*\n\\", (*((int(*)())&floor))(i)));
                                ~^~~~~~~~~~~~~~~~~~~~
$ ./decot1
'",x);  /*
\

Which is exactly the same thing I get when compiling & running the original on V7 Unix, so it should be just right ;-)

decot1.c

double floor(double);
double sin(double);
int printf(const char*, ...);
int b,
k['a'] = {sizeof(
    int(*)())
,};
struct tag{int x0,*xO;}

*main(i, dup, signal) {
{
  for(signal=0;*k *= * "decot.c" *i;) do {
   (printf(&*"'\",x);   /*\n\\", (*((int(*)())&floor))(i)));
        goto _0;

_O: while (!(k['a'] <<= - dup)) {
        static struct tag u ={4};
  }
}


while(b = 3, i); {
k['a'] = b,i;
  _0:if(b&&k+
  (int)(sin(signal)             / *    ((main) (b)-> xO)));}}}

diff

$ diff -u decot1.c~ decot1.c
--- decot1.c~
+++ decot1.c
@@ -1,4 +1,6 @@
-extern int floor;
+double floor(double);
+double sin(double);
+int printf(const char*, ...);
 int b,
 k['a'] = {sizeof(
     int(*)())
@@ -20,4 +22,4 @@
 while(b = 3, i); {
 k['a'] = b,i;
   _0:if(b&&k+
-  sin(signal)          / *    ((main) (b)-> xO));}}}
+  (int)(sin(signal)            / *    ((main) (b)-> xO)));}}}
like image 5
Placeholder Name Avatar answered Nov 06 '22 07:11

Placeholder Name