Prevent strings from being interpreted as a file h

2020-06-09 03:38发布

问题:

Perl has the feature that strings named like a file handle are taken to be a filehandle:

# let this be some nice class I wrote
package Input {
    sub awesome { ... }
}

So when we do Input->awesome or extra-careful: 'Input'->awesome, the method will get called. Unless:

# now somewhere far, far away, in package main, somebody does this:
open Input, "<&", \*STDIN or die $!;  # normally we'd open to a file

This code doesn't even have to be executed, but only be seen by the parser in order to have Perl interpret the string 'Input' as a file handle from now on. Therefore, a method call 'Input'->awesome will die because the object representing the file handle doesn't have awesome methods.

As I am only in control of my class but not of other code, I can't simply decide to only use lexical filehandles everywhere.

Is there any way I can force Input->awesome to always be a method call on the Input package, but never a file handle (at least in scopes controlled by me)? I'd think there shouldn't be any name clash because the Input package is actually the %Input:: stash.

Full code to reproduce the problem (see also this ideone):

use strict;
use warnings;
use feature 'say';

say "This is perl $^V";

package Input {
    sub awesome {
        say "yay, this works";
    }
}

# this works
'Input'->awesome;

# the "open" is parsed, but not actually executed
eval <<'END';
    sub red_herring {
        open Input, "<&", \*STDIN or die $!;
    }
END
say "eval failed: $@" if $@;

# this will die
eval {
    'Input'->awesome;
};
say "Caught: $@" if $@;

Example output:

This is perl v5.16.2
yay, this works
Caught: Can't locate object method "awesome" via package "IO::File" at prog.pl line 27.

回答1:

Using the same identifier for two different things (a used class and filehandle) begs for problems. If your class is used from a different class that's used in the code that uses the filehandle, the error does not appear:

My1.pm

package My1;

use warnings;
use strict;

sub new { bless [], shift }
sub awesome { 'My1'->new }

__PACKAGE__

My2.pm

package My2;

use warnings;
use strict;
use parent 'My1';

sub try {
    my $self = shift;
    return ('My1'->awesome, $self->awesome);
}

__PACKAGE__

script.pl

#!/usr/bin/perl
use warnings;
use strict;

use My2;
open My1, '<&', *STDIN;
my $o = 'My2'->new;
print $o->awesome, $o->try;


回答2:

Using the bareword Input as a filehandle is a breach of the naming convention to have only uppercase barewords for FILEHANDLEs and Capitalized/CamelCased barewords for Classes and Packages.

Furthermore lexcial $filehandles have been introduced and encouraged already a very long time ago.

So the programmer using your class is clearly misbehaving, and since namespaces are per definition global this can hardly be addressed by Perl (supporting chorobas statement about begging for problems).

Some naming conventions are crucial for all (dynamic) languages.

Thanks for the interesting question though, the first time I see a Perl question in SO I would preferred to see on perlmonks! :)

UPDATE: The discussion has has been deepened here: http://www.perlmonks.org/?node_id=1083985