Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

A mystery of conditional loading

by Llew_Llaw_Gyffes (Scribe)
on Mar 23, 2009 at 00:59 UTC ( [id://752462]=perlquestion: print w/replies, xml ) Need Help??

Llew_Llaw_Gyffes has asked for the wisdom of the Perl Monks concerning the following question:

Brethren, I seek the benefit of your experience.

I have a Perl application, a multithreaded client for the ICB chat service, to which I recently added client-to-client encryption as a feature.

Now, the base client requires only about two modules that are not normally part of the default Perl5 install. This, I considered acceptable. But adding encryption required the addition of about another eight modules. Since I did not know whether users would wish to install the additional eight modules for a feature they might not use, I sought a way to make use of those modules optional. And indeed, eventually I found such a means: Module::Load::Conditional.

After some study, I wrote the following code:

use Module::Load::Conditional qw[can_load check_install requires]; $Module::Load::Conditional::VERBOSE = 1; my $crypt_mods = {'Crypt::CBC' => undef, 'Crypt::Blowfish' => undef, 'Digest::SHA1' => undef, 'MIME::Base64' => undef, 'Compress::Zlib' => undef, 'Crypt::DH' => undef}; if (( can_load(modules=> {'Math::BigInt::GMP' => undef}, verbose => 1 +) || can_load(modules=> {'Math::BigInt' => undef}, verbose => 1)) && can_load(modules => $crypt_mods, verbose => 1)) { $encryption_avail = 1; } print $encryption_avail ? "All required crypto modules found and loaded: Encryption avai +lable\n" : "Encryption disabled: required modules not found\n";

And lo, this code would reportedly load all the necessary modules if available, or, if one or more modules were not available, it would disable the encryption feature - but continue execution anyway.

Except that later on, a call to decode_base64() would fail.

Thread 3 terminated abnormally: Undefined subroutine &main::decode_base64 called at ./icbm.conditional-load line 2071.

That line is in the middle of the function below:

sub decrypt { my ($message, $cipher, $key) = @_; my ($plaintext, $comptext, $ciphertext, $armortext, $cbc); $cbc = Crypt::CBC->new( -key => $key, -cipher => $cipher); $armortext = $$message; $ciphertext = decode_base64($armortext) || out ("Unarmor failed!") +; $comptext = $cbc->decrypt($ciphertext) || out ("Decrypt failed!"); $plaintext = Compress::Zlib::memGunzip($comptext) || out ("Decompr +ess failed!"); $$message = $plaintext; return (1); }

But lo, did not Module::Load::Conditional say all modules had loaded? Doubting its word, I sought a means to verify that they were indeed loaded. I discovered that Module::Loaded, while it did not contain any function to directly tell me whether a module was loaded, would — if I instructed it to mark those modules as loaded — carp if it found them already loaded.

Thus I added the following code, right after the Module::Load::Conditional invocation above:

use Module::Loaded; foreach my $mk ('Compress::Zlib', 'Crypt::Blowfish', 'Crypt::CBC', 'Cr +ypt::DH', 'Digest::SHA1', 'Math::BigInt::GMP', 'Math::BigInt', ' +MIME::Base64') { mark_as_loaded($mk); }

And lo, it reported all these modules were, indeed, already loaded, just as Module::Load::Conditional had said:

All required crypto modules found and loaded: Encryption available 'Compress::Zlib' already marked as loaded ('/usr/lib/perl5/5.10.0/i686 +-linux-thread-multi-64int-ld/Compress/Zlib.pm') at ./icbm.conditional +-load line 225 'Crypt::Blowfish' already marked as loaded ('/usr/lib/perl5/site_perl/ +5.10.0/i686-linux-thread-multi-64int-ld/Crypt/Blowfish.pm') at ./icbm +.conditional-load line 225 'Crypt::CBC' already marked as loaded ('/usr/lib/perl5/site_perl/5.10. +0/Crypt/CBC.pm') at ./icbm.conditional-load line 225 'Crypt::DH' already marked as loaded ('/usr/lib/perl5/site_perl/5.10.0 +/Crypt/DH.pm') at ./icbm.conditional-load line 225 'Digest::SHA1' already marked as loaded ('/usr/lib/perl5/site_perl/5.1 +0.0/i686-linux-thread-multi-64int-ld/Digest/SHA1.pm') at ./icbm.condi +tional-load line 225 'Math::BigInt::GMP' already marked as loaded ('/usr/lib/perl5/site_per +l/5.10.0/i686-linux-thread-multi-64int-ld/Math/BigInt/GMP.pm') at ./i +cbm.conditional-load line 225 'Math::BigInt' already marked as loaded ('/usr/lib/perl5/5.10.0/Math/B +igInt.pm') at ./icbm.conditional-load line 225 'MIME::Base64' already marked as loaded ('/usr/lib/perl5/5.10.0/i686-l +inux-thread-multi-64int-ld/MIME/Base64.pm') at ./icbm.conditional-loa +d line 225

Brothers, I am perplexed. I am assured, doubly, that MIME::Base64 is loaded. And the other modules loaded with it are clearly loaded, or my code would fail much sooner (not least at the invocation of Crypt::CBC two lines earlier). Indeed, this is not even the first use of MIME::Base64 in the code.

So why, then, can I not call decode_base64(), when that code worked perfectly when I was loading it with a simple "Use MIME::Base64;"?

Replies are listed 'Best First'.
Re: A mystery of conditional loading
by bellaire (Hermit) on Mar 23, 2009 at 01:31 UTC
    My initial suspicion was that this had to do with the imports, if only because this module does something different than the vanilla use and Exporter stuff. Sure enough, the documentation for the module which does the actual work of loading other modules (Module::Load) states the following:
    Module::Load cannot do implicit imports, only explicit imports. (in other words, you always have to specify explicitly what you wish to import from a module, even if the functions are in that modules' @EXPORT)
    Therefore, for you to have access to decode_base64, you have to explicitly specify that you want that sub imported from MIME::Base64. Unfortunately, the code which does the load in Module::Load::Conditional doesn't specify any explicit exports, and it isn't structured to allow this (see line 481 of the source of Module::Load::Conditional):
    eval { load $mod };
    The explicit imports would be a second argument to load, but none is passed here. Therefore, this solution as it stands won't work for you. You'll either need to patch the module to support explicit exports, submit the bug to the module maintainer, or use the fully qualified sub name MIME::Base64::decode_base64 in your code.

      Ah, I'd missed the detail of no implicit imports.

      Well, I can work with that. I'm still curious why some calls were working and others weren't. encode_base64 gets called before, in execution sequence, decode_base64 does, yet it worked.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://752462]
Approved by linuxer
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (4)
As of 2024-04-25 14:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found