[perl #72942] Can't perform unicode operations in Safe compartment
Tim Bunce [Sun, 21 Feb 2010 16:39:55 +0000 (17:39 +0100)]
The fix is to make Safe load utf8.pm (and ensure utf8_heavy.pl is run)
so it can always share utf8::SWASHNEW.

MANIFEST
dist/Safe/Safe.pm
dist/Safe/t/safeutf8.t [new file with mode: 0644]

index 65d13ce..1c34d01 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -2755,6 +2755,7 @@ dist/Safe/t/safeload.t            Tests that some modules can be loaded by Safe
 dist/Safe/t/safeops.t          Tests that all ops can be trapped by Safe
 dist/Safe/t/safesort.t         Tests Safe with sort
 dist/Safe/t/safeuniversal.t    Tests Safe with functions from universal.c
+dist/Safe/t/safeutf8.t         Tests Safe with utf8.pm
 dist/SelfLoader/lib/SelfLoader.pm      Load functions only on demand
 dist/SelfLoader/t/01SelfLoader.t       See if SelfLoader works
 dist/SelfLoader/t/02SelfLoader-buggy.t See if SelfLoader works
index e8a16ae..8af4310 100644 (file)
@@ -41,6 +41,23 @@ use Opcode 1.01, qw(
 
 *ops_to_opset = \&opset;   # Temporary alias for old Penguins
 
+# Regular expressions and other unicode-aware code may need to call
+# utf8->SWASHNEW (via perl's utf8.c).  That will fail unless we share the
+# SWASHNEW method.
+# Sadly we can't just add utf8::SWASHNEW to $default_share because perl's
+# utf8.c code does a fetchmethod on SWASHNEW to check if utf8.pm is loaded,
+# and sharing makes it look like the method exists.
+# The simplest and most robust fix is to ensure the utf8 module is loaded when
+# Safe is loaded. Then we can add utf8::SWASHNEW to $default_share.
+require utf8;
+# we must ensure that utf8_heavy.pl, where SWASHNEW is defined, is loaded
+# but without depending on knowledge of that implementation detail.
+# This code (//i on a unicode string) ensures utf8 is fully loaded
+# and also loads the ToFold SWASH.
+# (Swashes are cached internally by perl in PL_utf8_* variables
+# independent of being inside/outside of Safe. So once loaded they can be)
+do { my $unicode = pack('U',0xC4).'1a'; $unicode =~ /\xE4/i; };
+# now we can safely include utf8::SWASHNEW in $default_share defined below.
 
 my $default_root  = 0;
 # share *_ and functions defined in universal.c
@@ -60,6 +77,7 @@ my $default_share = [qw[
     &utf8::downgrade
     &utf8::native_to_unicode
     &utf8::unicode_to_native
+    &utf8::SWASHNEW
     $version::VERSION
     $version::CLASS
     $version::STRICT
diff --git a/dist/Safe/t/safeutf8.t b/dist/Safe/t/safeutf8.t
new file mode 100644 (file)
index 0000000..28441da
--- /dev/null
@@ -0,0 +1,46 @@
+#!perl -w
+$|=1;
+BEGIN {
+    require Config; import Config;
+    if ($Config{'extensions'} !~ /\bOpcode\b/ && $Config{'osname'} ne 'VMS') {
+        print "1..0\n";
+        exit 0;
+    }
+}
+
+use Test::More tests => 7;
+
+use Safe 1.00;
+use Opcode qw(full_opset);
+
+pass;
+
+my $safe = Safe->new('PLPerl');
+$safe->permit(qw(pack));
+
+# Expression that triggers require utf8 and call to SWASHNEW.
+# Fails with "Undefined subroutine PLPerl::utf8::SWASHNEW called"
+# if SWASHNEW is not shared, else returns true if unicode logic is working.
+my $trigger = q{ my $a = pack('U',0xC4); $a =~ /\\xE4/i };
+
+ok $safe->reval( $trigger ), 'trigger expression should return true';
+is $@, '', 'trigger expression should not die';
+
+# return a closure
+my $sub = $safe->reval(q{sub { warn pack('U',0xC4) }});
+
+# define code outside Safe that'll be triggered from inside
+my @warns;
+$SIG{__WARN__} = sub {
+    my $msg = shift;
+    # this regex requires a different SWASH digit data for \d)
+    # than the one used above and by the trigger code in Safe.pm
+    $msg =~ s/\(eval \d+\)/XXX/i; # uses IsDigit SWASH
+    push @warns, $msg;
+};
+
+is eval { $sub->() }, 1, 'warn should return 1';
+is $@, '', '__WARN__ hook should not die';
+is @warns, 1, 'should only be 1 warning';
+like $warns[0], qr/at XXX line/, 'warning should have been edited';
+