http://www.perlmonks.org?node_id=330784

This is a japh using duff's device. If you want to understand it, make sure you know what duff's device is (google it or see http://www.faqs.org/docs/jargon/D/Duff's-device.html).

This may fail if perl is compiled with long doubles or some 64bitness.

I run it successfully with perl5.{00503(if deleted "no warnings"),6.1,8.0,8.1} on i686-linux and perl5.6.1 on sun4-solaris (sparc).

# the usual declarations you use in evry program no warnings; no strict; # initialize arrays @a= ( 2.9230032746618539e+48, 3.7537584144024476e+255, 8.7398998774645058e+245, 5.7277807836951398e+250, 1.4916681462400519e-154, 1.7573882009934739e+159, 7.2294757342932134e+221, 4.7379092172264014e+226, 5.7277807836951398e+250, 9.1248812352446012e+192, 3.2418090381883485e+178, 1.3336028865760046e+241, 1.4916681462400519e-154, 2.315841784746365e+77, 3.2418090381883485e+178, 1.3336028865760046e+241, 1.6832434884955199e+212, 1.4916681462400519e-154, 9.1248812352446012e+192, 1.7573882009934739e+159, 7.5479248496432486e+168, 2.5684257331779778e+207, 3.2418090381883485e+178, 1.3336028865760046e+241, 1.6259745436952359e-260 ); tie @b, &^Px$^P; # with duff's device I can copy array very efficently goto ("case" . (@a % 8)); while ($a < @a) { case0: $b[$b++] = $a[$a++]; case7: $b[$b++] = $a[$a++]; case6: $b[$b++] = $a[$a++]; case5: $b[$b++] = $a[$a++]; case4: $b[$b++] = $a[$a++]; case3: $b[$b++] = $a[$a++]; case2: $b[$b++] = $a[$a++]; case1: $b[$b++] = $a[$a++]; }; # oh, I nearly forgot magical subs, so here they are sub DESTROY { } sub AUTOLOAD { ($#, %c)= qw(%c $#); print $_[2]; bless []; } __END__

I wonder if you can see how it works, especially what the two magic subs do. If you know, please reply me. (Is this too trivial?)

Replies are listed 'Best First'.
Re: Fun with duff's device and AUTOLOAD
by Abigail-II (Bishop) on Feb 24, 2004 at 09:59 UTC
    Copying? ;-) I don't think anything ends up in @b.

    Let's analyze the program. First you initialize @a with some large and small numbers. Then you tie @b - but to what, one may ask. Well, the result of &^Px$^P. It's not really relevant what &^P returns (an object - the result of AUTOLOAD), because $^P is essentially 0, leading to &^Px$^P being an empty string. Hence, it's tied into the class main. This causes main::TIEARRAY to be called, which is taken care off by AUTOLOAD - which returns an empty object blessed into the main class. Now Duff's device copies the elements of @a into @b one by one. However, since @b is tied, instead of storing the elements in @b, main::STORE is called. Again, this is taken care of by AUTOLOAD, but it doesn't store anything, it just returns another blessed object (which will be discarded right away).

    What remains is the two subs. First the empty DESTROY makes sure destruction of the many objects created doesn't trigger AUTOLOAD to be called. Then AUTOLOAD. The first line sets $# to the value %c. This influences the way how Perl stringifies numbers (basically by doing sprintf "%c", $number;). It also sets %c, but that hash is never accessed. Then it prints $_[2], which, in the case of STORE, contains the number to be stored. Due to the way stringification has been altered, it results into garbage on my screen. Perhaps with another printing device a sensible message appears (I suspect the numbers in @a not to be random).

    And that's it. Nothing is going to be stored in @b.

    Abigail

      Thanks for posting the solution.

Re: Fun with duff's device and AUTOLOAD
by ambrus (Abbot) on Feb 24, 2004 at 21:30 UTC

    Now I've got 3 different reports that the script does not work. Someone said it even did not work on Solaris. I can not reproduce the bad behaivior so it will be difficult to track down the bug, especially because it seems to run on most machines.

    (Update @ 2006 may 24: it seems I could finally reproduce the error on a machine. I'll try to investigate it if I have time.)

    I'll however explain where I've got the numbers and why they translate to a sensible message. I've got the basic idea after the thread $#="%c"; possible bug.

    The trick is that $#="%c"; print 3.14; is entirely unlike printf "%c", 3.14;. Just try $#="%d"; print 10;! It does not print 10, rather, it prints 0 (i386) or some stupid number depending on the endianndess of your cpu. This is because when formatting numbers a la $#, perl calls sprintf (or some equivalent) with $# as a format and the number as a floating point number to the stack. I don't know where this is in the source, probably hidden somewhere in sv.c, but I'm quite sure it works like this.

    Update: I'd be glad if someone could point me to the guilty code.

    The floating-point numbers are given so that when represented as a double, their upper and lower 4 bytes are both the same small integer, the ascii code of a character in the message. I had to put the same integer twice, so that the code would work in both intel-endian and sparc-endian cpu's. Really, if you consider only one kind of cpus, half as much numbers would have been enough. Let me show this.

    Type perl -we 'print join ", ", unpack "d*", pack "l*", unpack "c*", "perl"; print $/;'. This prints (on intel) 2.14321574942828e-312, 2.29175545480573e-312. Now using this numbers you can write perl -we '$#="%c%c"; print 2.14321574942828e-312, 2.29175545480573e-312; print $/;', which prints perl. I created the obfu in a very similar way.

    The problem is that all this will fail if your perl was compiled with long double support, as the floating point format is quite different. There may be other problems why the script did not run for some people, which I currently don't know. Please check that your perl is not compiled with long doubles. Just run use Config; print qq[@Config{"nvtype","nvsize"}\n]; if it prints double 8, that is good. If it prints long double 12, the obfu will not work. There may also be issues about 64-bitness, I can not check that. Note that I have not tried the obfu from Windows, but in theory it should work.

      I get "double 8" on ActiveState under win2k, yet the obfu doesn't work.

      It does seem to have some issue with %c not working the same as printf, though. Printing 65 gives a nul character output, not 'A'. Printing 3.14 gives the bytes FD 91 BA B8 94 9F. Your print 2.14321574942828e-312, 2.29175545480573e-312; example prints "perl" as expected!

        Maybe I wasn't clear in the explanation.

        $#="%c";print 65; should not print an A. It pushes 65.0 (a double) and "%x" on the C stack, and calls sprintf. Then sprintf sees "%c", so tries to pop an int from the stack. 65.0 is represented as the bytes (00 00 00 00 00 40 50 40)hex, but an int is just 4 bytes so it pops (00 00 00 00)hex which is 0 as an int so it prints a nul char. (On sparc, however, it will get (40 50 40 00)hex, which is 67437568, so it will either print a null character or a multibyte character sequence corresponding to unicode chr 67437568, depending on the version of Perl.)

        Dlugosz, I guess your cpu is an x86, correct me if it isn't.

Re: Fun with duff's device and AUTOLOAD
by John M. Dlugosz (Monsignor) on Feb 24, 2004 at 16:06 UTC
    I tried it under Win2k, and got unreadable output.

    I wonder, what is printf("%c", 2.92e48) supposed to do? There is no character with that number, so I would hope for an error.

    I'm not sure where it would "naturally" wrap, if that's what it's doing, since the range of legal Unicode ordinals is not a neat power of two. (I'm getting Unicode code points displayed for something like printf '%c',0x434 even without a use utf8 or other preparation.)

    I'd be interested to know just what yours is doing.

Re: Fun with duff's device and AUTOLOAD
by Skylark (Beadle) on Feb 25, 2004 at 16:59 UTC
    In case you're curious, as no one has mentioned what the erroneous output is yet, here's the output on my Windows NT4 machine:
    output : Jwuv borvjgt Pgtn jbdmgt expected: Just Another Perl Hacker
    Though the spaces in the output are not spaces, they are little black arrows pointing up (▲ = HTML entity code #9650)

    So I can see you're getting at it, but it doesn't seem to give the right results for each letter... Some seem to work (J in Just, P in Perl) but most do not.

    I would like to see it work though... I'll try it on my Linux machine at home.

    J-S

      This is the output you get if the floating point valuse are given to too few decimal places. I thought this should not happen to the obfu I've given, but I'll check it (when I'll have time).