Prevent strings from being interpreted as a file handle
Asked Answered
S

2

14

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.
Sobersided answered 26/4, 2014 at 14:18 Comment(7)
@Zaid no, awesome Input behaves the same as Input->awesome. Interestingly, awesome 'Input' doesn't compile as it's taken to be a function call.Sobersided
@Zaid: awesome Class:: is equivalent to Class::->awesome.Sherrod
@Sobersided : And awesome Input:: doesn't seem to force Perl to interpret the bareword as a class name either, despite what the documentation indicates: You can force Perl to interpret the bareword as a class name by appending "::" to itRufe
Input::awesome worksRufe
@Zaid: It does not work with inheritance, though (Input inherits awesome from its parent class).Sherrod
@Rufe I don't generally have that option. This is irrelevant for the purposes of this question, but my real problem is centered around import which will be called implicitly when my package is used. This fails very subtly because IO::File->import gets called instead.Sobersided
I'm waiting for @ikegami to chime in with a detailed lesson about naming conventions and the many reasons why people should follow them. :) Seriously, I'd welcome such a post. Until then, not a big issue as it takes two people misbehaving for this to happen. First person should use a lexical filehandle or a typeglob that's in all caps. The second person shouldn't use a base level package name, should be scoped to the project. Therefore this isn't a big concern, although it is an interesting case. Thanks for sharing.Once
S
5

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;
Sherrod answered 26/4, 2014 at 15:14 Comment(1)
I think I'll accept this “don't do this” answer until an answer with a way to disambiguate the method call itself comes along. Using two separate packages (they don't even have to be separate files) works fine, provided that the file handle name is ordinary and not superglobal like the _ file handle, and that we don't do something like main::My1->awesome which should work but resolves to the file handle again.Sobersided
L
3

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

Lanceolate answered 26/4, 2014 at 15:25 Comment(3)
Though you might be able to run a check if the bareword was already insanely overloaded and throw an error. When your module is used, the source is evaled in the context of the caller even before import is called, that's where you might run such a check (before package).Lanceolate
Ah, I absolutely agree with your style recommendations. But the code in my question is just illustrative to reproduce the problem, while my real code does even worse things to invite issues. (I was trying to boobie-trap use _; – because what sick person would even consider doing that – which doesn't work if someone did -e _ before). I think I'll post an updated version to the monastery later today.Sobersided
Surely if you want use _ to blow up, the best way is to not have any file called _.pm in your @INC?Platinous

© 2022 - 2024 — McMap. All rights reserved.