<!-- category: hidden-gems -->
B::Deparse - What Perl Actually Compiles Your Code Into
You write Perl. Perl reads your Perl. Then Perl compiles it into something that may look nothing like what you wrote.Want to see what Perl actually thinks your code says?
$ perl -MO=Deparse -e 'print "hello" if $x'
Okay, that one was boring. Try something weirder:print 'hello' if $x; -e syntax OK
$ perl -MO=Deparse -e 'print "hello" unless !$x'
Perl just simplifiedprint 'hello' if $x; -e syntax OK
unless !$x into if $x. It saw through the
double negative and normalized it. The code you wrote and the code
Perl compiled are different. B::Deparse shows you the compiled
version.
Part 1: BASIC USAGE
B::Deparse is a backend module that takes Perl's compiled op-tree (the internal representation after parsing) and converts it back into Perl source code. The canonical form. The way Perl understood it.That is the basic invocation.$ perl -MO=Deparse script.pl
-MO=Deparse tells Perl to compile
the script, pass the op-tree to B::Deparse, and print the result
instead of running the program.
Your script does not execute. It only compiles. B::Deparse shows you what the compiler produced.
$ perl -MO=Deparse -e 'for (1..10) { print }'
Perl filled in the implicitforeach $_ (1 .. 10) { print $_; } -e syntax OK
$_ that you left out. It made the
default variable explicit. B::Deparse shows you what was always
there but never written.
Part 2: REVEALING IMPLICIT BEHAVIORS
Perl is full of defaults. Default variables, default filehandles, default behaviors. B::Deparse makes them all visible.$ perl -MO=Deparse -e 'while (<>) { chomp; print if /foo/ }'
Look at everything Perl added:while (defined($_ = <ARGV>)) { chomp $_; print $_ if /foo/; } -e syntax OK
<>became<ARGV>(the diamond reads from ARGV)- The readline result is assigned to
$_ - A
defined()check wraps the assignment chompgets an explicit$_argumentprintgets an explicit$_argument
Every implicit default is now explicit. This is what Perl was actually doing all along. You just did not have to type it.
Part 3: DEOBFUSCATING CODE
Found some obfuscated Perl on the internet? Someone's JAPH that looks like line noise? Feed it to B::Deparse.$ perl -MO=Deparse -e '$_="Just another Perl hacker,";print'
B::Deparse normalizes the code. Adds whitespace. Makes implicit arguments explicit. Turns clever tricks into readable code.$_ = 'Just another Perl hacker,'; print $_; -e syntax OK
It will not fully deobfuscate runtime string eval tricks (because those happen at runtime, not compile time), but it handles syntactic obfuscation beautifully.
$ perl -MO=Deparse -e 'print+("hello"x3)'
Theprint 'hello' x 3; -e syntax OK
+ after print (which resolves the ambiguity between print
as a function and print as a list operator) disappears. B::Deparse
understood the intent and produced the cleaner form.
Part 4: UNDERSTANDING OPERATOR PRECEDENCE
Not sure how Perl parses a complex expression? B::Deparse shows you where the parentheses go.$ perl -MO=Deparse -e 'print 1 + 2 * 3'
Perl computed the constant expression at compile time. It knewprint 7; -e syntax OK
1 + 2 * 3 is always 7, so it folded it into a constant. B::Deparse
shows you the optimized result.
For non-constant expressions:
$ perl -MO=Deparse -e 'print $a + $b * $c'
Not super helpful here. Use theprint $a + $b * $c; -e syntax OK
-p flag for explicit parentheses:
$ perl -MO=Deparse,-p -e 'print $a + $b * $c'
Now you can see thatprint(($a + ($b * $c))); -e syntax OK
$b * $c happens first. The -p flag wraps
every subexpression in parentheses, making the precedence tree
visible.
Part 5: THE -p FLAG FOR EXTRA PARENTHESES
The-p flag is your best friend for understanding precedence in
complex expressions:
$ perl -MO=Deparse,-p -e '$x = $a || $b && $c'
($x = ($a || ($b && $c))); -e syntax OK
&& binds tighter than ||. The parentheses prove it. No more
guessing, no more checking the operator precedence table. B::Deparse
shows you the actual parse tree.
Another good one:
$ perl -MO=Deparse,-p -e 'not $a and $b or $c'
(((not $a) and $b) or $c); -e syntax OK
not binds first (high precedence unary), then and, then or.
Without the parentheses, you might have expected different grouping.
B::Deparse does not lie.
Part 6: SEEING DEFAULT VARIABLES
Perl's$_ is everywhere, and B::Deparse makes every implicit use
visible:
$ perl -MO=Deparse -e 'for (@a) { chomp; s~foo~bar~; print }'
Notice thatforeach $_ (@a) { chomp $_; s/foo/bar/; print $_; } -e syntax OK
chomp and print got explicit $_ arguments, but the
substitution did not. That is because s~~~ always operates on $_
by default and B::Deparse does not bother making that one explicit.
It is already in the canonical form.
This is useful for learning which builtins default to $_ and which
do not. If B::Deparse adds $_, the builtin uses it as a default.
Part 7: UNDERSTANDING ONE-LINERS
Perl one-liners pack a lot of implicit behavior into very little syntax. B::Deparse unpacks them.$ perl -MO=Deparse -ne 'print if /error/i'
TheLINE: while (defined($_ = <ARGV>)) { print $_ if /error/i; } -e syntax OK
-n flag becomes a while loop. The diamond operator reads
from ARGV. The implicit $_ appears. The LINE label shows up.
All the hidden machinery is visible.
$ perl -MO=Deparse -ape 's~\t~,~g'
TheLINE: while (defined($_ = <ARGV>)) { s/\t/,/g; } continue { die "-p destination: $!\n" unless print $_; } -e syntax OK
-p flag adds a continue block that prints $_ after every
iteration. And it wraps the print in a die for error checking.
That is a lot of hidden behavior from a single flag.
Part 8: DEBUGGING WEIRD SYNTAX
Sometimes you write something and are not sure if Perl parsed it the way you intended. B::Deparse settles the question.$ perl -MO=Deparse -e 'print (1 + 2) * 3'
Surprise. Perl parsedprint(3); '???' * 3; -e syntax OK
print(1 + 2) as a function call, then tried
to multiply the return value by 3. The (1 + 2) was treated as the
argument list to print, not as grouping parentheses. The * 3
became a separate, useless statement.
The ??? is B::Deparse's way of saying "this value is the return of
print, but I cannot represent it cleanly." The code is technically
valid but almost certainly not what you intended.
This is why print +(1 + 2) * 3 or print((1 + 2) * 3) exist.
B::Deparse would have shown you the problem before you spent an hour
debugging wrong output.
Part 9: THE ROUND-TRIP TEST
B::Deparse is useful for testing your understanding of Perl syntax. Write something, deparse it, and see if the result matches your expectations. If it does not, you learned something.$ perl -MO=Deparse -e 'my $x = 5 if 0'
That'???' if 0; -e syntax OK
my $x = 5 if 0 trick (sometimes used for static variables in
old code) deparses into something that makes it clear Perl treats the
my declaration as happening but the assignment as conditional. The
??? shows B::Deparse cannot cleanly represent this edge case.
Another round-trip:
$ perl -MO=Deparse -e 'use 5.010; say for sort reverse @a'
Perl expandeduse feature 'say'; say $_ foreach (sort reverse @a); -e syntax OK
use 5.010 into use feature 'say'. The chain of
list operations is preserved. The postfix for became foreach.
All normalizations that show you the canonical interpretation.
Part 10: A TOOL FOR UNDERSTANDING
B::Deparse is not a tool you use every day. It is a tool you use when Perl surprises you. When your code does something unexpected. When someone else's code looks like nonsense. When you are not sure how an expression is parsed..--. |o_o | "I wrote one thing. |:_/ | Perl compiled another. // \ \ B::Deparse showed me (| | ) the truth." /'\_ _/`\ \___)=(___/
It shows you the canonical form. The way Perl understood your code after parsing but before execution. No ambiguity. No implicit defaults. No syntactic sugar. Just the raw, normalized Perl that the interpreter actually compiled.
The output is valid Perl. You can run it. You can diff it against your original. You can use it to understand code you inherited from someone who thought clever was a compliment.$ perl -MO=Deparse your_script.pl > deparsed.pl
B::Deparse ships with Perl. No installation. No CPAN. It has been there since Perl 5.005. Twenty-plus years of being the answer to "what does Perl think my code means?"
Ask it. It will tell you the truth.
perl.gg