Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Beware of global! And bless the local!

by alexander_lunev (Pilgrim)
on Dec 12, 2019 at 08:40 UTC ( [id://11110005]=perlmeditation: print w/replies, xml ) Need Help??

Hello monks.

Today I found that a little negligence can cause a week-long debugging.

I wrote a program that uses Crypt::OpenPGP module, and for a strange reason module works great when I simply initialize it with keyrings from file, but it crashes when i first read keyrings from files and combines them for my needs and then initialize module with constructed keyring objects. Terrible crashes of the Perl interpreter itself (sic!) was accompanied by cryptic messages like these:

Win32::API::parse_prototype: WARNING unknown parameter type 'PVOID' at + C:/Strawberry32/perl/vendor/lib/Win32/API.pm line 568. Win32::API::parse_prototype: WARNING unknown parameter type 'ULONG' at + C:/Strawberry32/perl/vendor/lib/Win32/API.pm line 568. Win32::API::parse_prototype: WARNING unknown output parameter type 'IN +T' at C:/Strawberry32/perl/vendor/lib/Win32/API.pm line 600. Argument "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0..." + isn't numeric in subroutine entry at C:/Strawberry32/perl/vendor/lib +/Crypt/Random/Seed.pm line 247.

I was wondering - what have i done to bring this punishment upon me? I compared objects that come out from keyring-file initialization and from keyring-objects initializations with Data::Dumper and couldn't find a difference. I even saved constructed keyring objects to files and initialize Crypt::OpenPGP with this newly created files - still all was crashing. The holy fear seized me and I decided to start my journey to the depth of module forest to find deliverance. I walk through Crypt::OpenPGP to Bytes::Random::Secure, to know that road leads - as it was told to me in the cryptic omen - to Crypt::Random::Seed, line 247, which was a Call to Win32::API function:

my $rtlgenrand = Win32::API->new( 'advapi32', <<'_RTLGENRANDOM_PROTO_' +); INT SystemFunction036( PVOID RandomBuffer, ULONG RandomBufferLength ) _RTLGENRANDOM_PROTO_ return unless defined $rtlgenrand; return ('RtlGenRand', sub { my $nbytes = shift; my $buffer = chr(0) x $nbytes; my $result = $rtlgenrand->Call($buffer, $nbytes); # <= 247

My journey comes to a dead end, for I didn't find deliverance there. And I started from the beginning of my code, turning lines of code off one by one. And I found it.

This is the code that was a root of all misfortunes.

open my $fh, "<", $file or die "can't open $file"; $/ = undef; my $key_string = <$fh>; close $fh;

If you're enlightened enough, you will see my sin right away. I'm not enlightened enough to see it right away, but a doubt crawls into my mind - could it be the line $/ = undef;? I stared at this line for a minute and go search wisdom on the Internet. And then I found the Truth. And as always the Truth was under my nose all the time, but I couldn't see it. It should be local $/ = undef;!

I don't know if it is clean and right way to read all file in a string, but even the great Gabor Szabo blesses slurp mode by setting $/ = undef;. But beware if you read his great article not thoroughly! The great misfortune awaits those who forget about local in a rush! Like me for example.

You see? Setting $/ = undef; globally make things broken all the way up to the Perl interpreter itself, which was casting strange messages on his way to crash.

But why would anyone write about it again, and again, and again? Because, as it said in every language:

Repetitio mater studiorum est.

Repetition is the mother of all learning.

Wiederholen ist die Mutter des Studierens.

La répétition est la mère de la science.

Повторение - мать учения.

Let my mistake will be a lesson to others. Using global variables is always a risky and erroneous path, local variables is the only way to enlightenment. But this is the Truth that we were told from the beginning and we are still making this silly mistakes. And while we code simple programs, errors are simple and debuggable. But when we become more mature - so are the errors that we cause by violation of a simple rules that we doesn't learned well from the start. Beware of global! And bless the local! Amen.

Replies are listed 'Best First'.
Re: Beware of global! And bless the local!
by jdporter (Paladin) on Dec 12, 2019 at 14:06 UTC

    Here's a neat little idiom:

    my $key_string = do { use warnings qw(FATAL); local(@ARGV,$/) = ($file +); <> };
      use warnings qw(FATAL);

      Note that requires Perl 5.20 and up, in earlier versions it's use warnings FATAL => 'all'; (<brag> I know cause I wrote the patch, my first in the Perl core ;-) </brag>)

Re: Beware of global! And bless the local!
by kcott (Archbishop) on Dec 12, 2019 at 12:11 UTC

    G'day alexander_lunev,

    A lesson well learned — thanks for posting.

    "But why would anyone write about it again, and again, and again?"

    I wouldn't be concerned with that. I've certainly urged people to use local in many posts; and not just for $/ but for other special variables which can have a global effect.

    I actually take this a step further and use an anonymous block along the lines of

    { open my $fh, ... local $/; # do whatever with $fh here }

    From the local documentation:

    "A local modifies the listed variables to be local to the enclosing block, ..."

    And from "perlvar: Variables related to filehandles":

    "Usually when a variable is localized you want to make sure that this change affects the shortest scope possible."

    Also note that the "= undef" is unnecessary; and the "close $fh" is implicit at the end of the anonymous block (see open for details). I'm not saying don't use them: just advising that they're optional (at least in the type of code you show in your example).

    — Ken

      Hello, kcott!

      I should adopt anonymous blocks as a every-day programming technique, thank you. As with the simple but powerful Truth of local, it's also always under my nose, and still I'm not using it - but I will!

      As for unnecessary things like "= undef" and "close $fh" - I like it this way, it's more informative and illustrative this way. It takes more chars to write, but less mind time-clocks to comprehend the meaning. I like to write explicit "return $string;" instead of laconic "$string" in the end of the sub. I think that a programming language should stay language that we speak and understand, and I don't want to short-cut to the point in expense of explanation. In the end I'm speaking this language with myself-in-the-future, and it's for me I write those many words that could be omitted.

        "I should adopt anonymous blocks as a every-day programming technique"

        Anonyblocks are just another tool in the toolset. I use them primarily in unit test files to separate out tests of a feature or method where I need to instantiate a new object for a test sequence.

        For situations such as yours, I'd probably opt for a function instead of a block that's inline with the code. A subroutine provides the same scoping as the block does:

        sub slurp_file { my ($fname) = @_; local $/; open my $fh, '<', $fname or die "Can't open damned '$fname' file: +$!"; my $data = <$fh>; close $fh or die $!; return $data; }

        To each their own, there's more than one way to do it!

        "As for unnecessary things like "= undef" and "close $fh" ..."

        That's absolutely fine and your reasoning is sound. As I said: "I'm not saying don't use them: just advising that they're optional".

        "I think that a programming language should stay language that we speak and understand, ..."

        The first time I ever saw a Perl script — a very long time ago; I'm pretty sure it was Perl 3 — I was absolutely amazed that I was able to understand most of it at a glance.

        — Ken

Re: Beware of global! And bless the local!
by haukex (Archbishop) on Dec 12, 2019 at 20:40 UTC

      Perl::Critic is a tool that I should adopt. On the one side there's laziness - oh, instead of just write this little thing I would spend it on this and that to make sure it's all right. But on the other side - there's so much ahead in this never-ending journey!

        Perl::Critic is a tool that I should adopt. On the one side there's laziness

        I find the --gentle and --stern settings useful to reveal excessive laziness, and the kind of cruft that accumulates during refactoring like stray whitespaces or the wrong type of quote chars. While --harsh --cruel and --brutal are useful as training wheels for advanced techniques, or enforcing code standards on hired guns, and as a form of pure pedantic entertainment for anyone who loves TIMTOWTDI.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://11110005]
Approved by Corion
Front-paged by kcott
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (5)
As of 2024-03-28 15:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found