File Coverage

blib/lib/Eval/Safe.pm
Criterion Covered Total %
statement 49 98 50.0
branch 8 44 18.1
condition 3 5 60.0
subroutine 13 22 59.0
pod 7 7 100.0
total 80 176 45.4


line stmt bran cond sub pod time code
1             package Eval::Safe;
2              
3 3     3   131800 use 5.022;
  3         21  
4 3     3   12 use strict;
  3         6  
  3         50  
5 3     3   10 use warnings;
  3         4  
  3         78  
6              
7 3     3   13 use Carp;
  3         4  
  3         210  
8 3     3   1114 use Eval::Safe::Eval;
  3         6  
  3         289  
9 3     3   1005 use Eval::Safe::Safe;
  3         10  
  3         113  
10 3     3   18 use List::Util qw(none);
  3         4  
  3         277  
11 3     3   19 use Scalar::Util qw(reftype refaddr);
  3         5  
  3         1196  
12              
13             our $VERSION = '0.02';
14              
15             sub new {
16 4     4 1 143359 my ($class, %options) = @_;
17 4 50       14 croak "Eval::Safe->new called with invalid class name: $class" unless $class eq 'Eval::Safe';
18 4         11 my @known_options = qw(safe strict warnings debug package force_package);
19 4     8   9 my @unknown_options = grep {my $k = $_; none { $k eq $_ } @known_options } keys %options;
  3         5  
  3         16  
  8         18  
20 4 100       11 if (@unknown_options) {
21 1         165 croak "Unknown options: ".join(' ', @unknown_options);
22             }
23 3         12 $options{strict} = _make_pragma('strict', $options{strict});
24 3         10 $options{warnings} = _make_pragma('warnings', $options{warnings});
25 3 50 33     12 if ($options{package} and not $options{force_package}) {
26 0         0 $options{package} = Eval::Safe::_validate_package_name($options{package});
27 0 0       0 croak "Package $options{package} already exists" if eval "%$options{package}::";
28             }
29 3 100 100     58 if ($options{safe} // 0 > 0) {
30 1         8 return Eval::Safe::Safe->new(%options);
31             } else {
32 2         11 return Eval::Safe::Eval->new(%options);
33             }
34             }
35              
36             sub package {
37 0     0 1 0 my ($this) = @_;
38 0         0 return $this->{package};
39             }
40              
41             sub wrap {
42 0     0 1 0 my ($this, $code) = @_;
43 0         0 return $this->eval("sub { ${code} }");
44             }
45              
46             sub share {
47 0     0 1 0 my ($this, @vars) = @_;
48 0         0 my $calling_package = caller;
49 0         0 $this->share_from($calling_package, @vars);
50             }
51              
52             sub share_from {
53 0     0 1 0 my ($this, $package, @vars) = @_;
54 0         0 $package = _validate_package_name($package);
55 0 0       0 croak "Package $package does not exist" unless eval "%${package}::";
56 0         0 for my $v (@vars) {
57 0 0       0 croak "Variable has no leading sigil: $v" unless $v =~ m'^([&*$%@])(\w+)$';
58 0         0 my ($sigil, $symbol) = ($1, $2);
59             # There are only 5 different sigils, so we could skip the eval here and
60             # instead branch on the $sigil and use a syntax like the one on the left of
61             # the equal (e.g. \&{$package."::$symbol"}). See:
62             # https://metacpan.org/source/MICB/Safe-b2/Safe.pm
63 3     3   21 no strict 'refs';
  3         6  
  3         367  
64 0         0 *{($this->package())."::${symbol}"} = eval "\\${sigil}${package}::${symbol}";
  0         0  
65             }
66             }
67              
68             sub var_ref {
69 0     0 1 0 my ($this, $var) = @_;
70 0 0       0 croak "Variable has no leading sigil: $var" unless $var =~ m'^([&*$%@])(\w+)$';
71             # There are only 5 different sigils, so we could skip the eval here and
72             # instead branch on the $sigil. See:
73             # https://metacpan.org/source/MICB/Safe-b2/Safe.pm
74 3     3   16 no strict 'refs';
  3         7  
  3         1306  
75 0         0 return eval sprintf '\%s%s::%s', $1, $this->package(), $2;
76             }
77              
78             sub interpolate {
79 0     0 1 0 my ($this, $str) = @_;
80             # It's not clear if Text::Balanced could help here.
81 0         0 my $r = $this->eval("<<\"EVAL_SAFE_EOF_WORD\"\n${str}\nEVAL_SAFE_EOF_WORD\n");
82 0         0 $r =~ s/\n$//;
83 0         0 return $r;
84             }
85              
86             # _make_pragma('pragma', $arg)
87             # Returns a string saying "no pragma" if $arg is false, "use pragma" if arg is
88             # a `true` scalar, "use pragma $$arg" if arg is a scalar reference, and
89             # "use pragma @$arg" if arg is an array reference.
90             sub _make_pragma() {
91 6     6   14 my ($pragma, $arg) = @_;
92 6         13 my $reftype = reftype $arg;
93 6 50       10 if (not defined $reftype) {
    0          
    0          
    0          
94 6 50       9 if ($arg) {
95 0         0 return "use ${pragma};";
96             } else {
97 6         16 return "no ${pragma};";
98             }
99             } elsif ($reftype eq 'SCALAR') {
100 0           return "use ${pragma} '$arg';";
101             } elsif ($reftype eq 'ARRAY') {
102             # We should probably use Data::Dumper to format the arg list properly in
103             # case some of the args contain a space.
104 0           return ("use ${pragma} qw(".join(' ', @$arg).');');
105             } elsif ($reftype eq 'HASH') {
106 0           return ("use ${pragma} qw(".join(' ', %$arg).');');
107             } else {
108 0           croak "Invalid argument for '${pragma}' option, expected a scalar or array reference";
109             }
110             }
111              
112             # $safe->_wrap_code_refs('sub', @objects)
113             # will call $safe->sub($ref) for all code references found within @objects and
114             # store the result in place in @objects. The passed objects are crawled
115             # recursively.
116             # Finally, the modified array is returned.
117             #
118             # This is similar to the wrap_code_refs_within method in Safe.
119             sub _wrap_code_refs {
120 0     0     my ($this, $wrapper) = splice @_, 0, 2;
121             # We need to use @_ below (without giving it a new name) to retain its
122             # aliasing property to modify the arguments in-place.
123 0           my %seen_refs = ();
124             my $crawler = sub {
125 0     0     for my $item (@_) {
126 0           my $reftype = reftype $item;
127 0 0         next unless $reftype;
128 0 0         next if ++$seen_refs{refaddr $item} > 1;
129 0 0         if ($reftype eq 'ARRAY') {
    0          
    0          
130 0           __SUB__->(@$item); # __SUB__ is the current sub.
131             } elsif ($reftype eq 'HASH') {
132 0           __SUB__->(values %$item);
133             } elsif ($reftype eq 'CODE') {
134 0           $item = $this->$wrapper($item);
135             }
136             # We're ignoring the GLOBs for the time being.
137             }
138 0           };
139 0           $crawler->(@_);
140 0 0         if (defined wantarray) {
141 0 0         return (wantarray) ? @_ : $_[0];
142             }
143 0           return;
144             }
145              
146             # _validate_package_name('package::name')
147             # Croaks (dies) if the given package name does not look like a package name.
148             # Otherwise returns a cleaned form of the package name (trailing '::' are
149             # removed, and '' or '::' is made into 'main').
150             sub _validate_package_name {
151 0     0     my ($p) = @_;
152 0           $p =~ s/::$//;
153 0 0         $p = 'main' if $p eq '';
154 0 0         croak "${p} does not look like a package name" unless $p =~ m/^\w+(::\w+)*$/;
155 0           return $p;
156             }
157              
158             1;
159              
160             __DATA__