Add Opcode extension
Perl 5 Porters [Tue, 18 Jun 1996 02:12:05 +0000 (02:12 +0000)]
ext/Opcode/Makefile.PL [new file with mode: 0644]
ext/Opcode/Opcode.pm [new file with mode: 0644]
ext/Opcode/Opcode.xs [new file with mode: 0644]
ext/Opcode/ops.pm [new file with mode: 0644]

diff --git a/ext/Opcode/Makefile.PL b/ext/Opcode/Makefile.PL
new file mode 100644 (file)
index 0000000..cfc8246
--- /dev/null
@@ -0,0 +1,6 @@
+use ExtUtils::MakeMaker;
+WriteMakefile(
+    NAME => 'Opcode',
+    VERSION_FROM => 'Opcode.pm',
+    MAN3PODS   => ' '
+);
diff --git a/ext/Opcode/Opcode.pm b/ext/Opcode/Opcode.pm
new file mode 100644 (file)
index 0000000..c2dd414
--- /dev/null
@@ -0,0 +1,564 @@
+package Opcode;
+
+require 5.002;
+
+use vars qw($VERSION @ISA @EXPORT_OK);
+
+$VERSION = "1.01";
+
+use strict;
+use Carp;
+use Exporter ();
+use DynaLoader ();
+@ISA = qw(Exporter DynaLoader);
+
+BEGIN {
+    @EXPORT_OK = qw(
+       opset ops_to_opset
+       opset_to_ops opset_to_hex invert_opset
+       empty_opset full_opset
+       opdesc opcodes opmask define_optag
+       opmask_add verify_opset opdump
+    );
+}
+
+use subs @EXPORT_OK;
+
+bootstrap Opcode $VERSION;
+
+_init_optags();
+
+
+*ops_to_opset = \&opset;       # alias for old name
+
+
+sub opset_to_hex ($) {
+    return "(invalid opset)" unless verify_opset($_[0]);
+    unpack("h*",$_[0]);
+}
+
+sub opdump (;$) {
+       my $pat = shift;
+    # handy utility: perl -MOpcode=opdump -e 'opdump File'
+    foreach(opset_to_ops(full_opset)) {
+        my $op = sprintf "  %12s  %s\n", $_, opdesc($_);
+               next if defined $pat and $op !~ m/$pat/i;
+               print $op;
+    }
+}
+
+
+
+sub _init_optags {
+    my(%all, %seen);
+    @all{opset_to_ops(full_opset)} = (); # keys only
+
+    local($/) = "\n=cut"; # skip to optags definition section
+    <DATA>;
+    $/ = "\n=";                # now read in 'pod section' chunks
+    while(<DATA>) {
+       next unless m/^item\s+(:\w+)/;
+       my $tag = $1;
+
+       # Split into lines, keep only indented lines
+       my @lines = grep { m/^\s/    } split(/\n/);
+       foreach (@lines) { s/--.*//  } # delete comments
+       my @ops   = map  { split ' ' } @lines; # get op words
+
+       foreach(@ops) {
+           warn "$tag - $_ already tagged in $seen{$_}\n" if $seen{$_};
+           $seen{$_} = $tag;
+           delete $all{$_};
+       }
+       # opset will croak on invalid names
+       define_optag($tag, opset(@ops));
+    }
+    close(DATA);
+    warn "Untagged opnames: ".join(' ',keys %all)."\n" if %all;
+}
+
+
+1;
+
+__DATA__
+
+=head1 NAME
+
+Opcode - Disable named opcodes when compiling perl code
+
+=head1 SYNOPSIS
+
+  use Opcode;
+
+
+=head1 DESCRIPTION
+
+Perl code is always compiled into an internal format before execution.
+
+Evaluating perl code (e.g. via "eval" or "do 'file'") causes
+the code to be compiled into an internal format and then,
+provided there was no error in the compilation, executed.
+The internal format is based on many distinct I<opcodes>.
+
+By default no opmask is in effect and any code can be compiled.
+
+The Opcode module allow you to define an I<operator mask> to be in
+effect when perl I<next> compiles any code.  Attempting to compile code
+which contains a masked opcode will cause the compilation to fail
+with an error. The code will not be executed.
+
+=head1 NOTE
+
+The Opcode module is not usually used directly. See the ops pragma and
+Safe modules for more typical uses.
+
+=head1 WARNING
+
+The authors make B<no warranty>, implied or otherwise, about the
+suitability of this software for safety or security purposes.
+
+The authors shall not in any case be liable for special, incidental,
+consequential, indirect or other similar damages arising from the use
+of this software.
+
+Your mileage will vary. If in any doubt B<do not use it>.
+
+
+=head1 Operator Names and Operator Lists
+
+The canonical list of operator names is the contents of the array
+op_name defined and initialised in file F<opcode.h> of the Perl
+source distribution (and installed into the perl library).
+
+Each operator has both a terse name (its opname) and a more verbose or
+recognisable descriptive name. The opdesc function can be used to
+return a list of descriptions for a list of operators.
+
+Many of the functions and methods listed below take a list of
+operators as parameters. Most operator lists can be made up of several
+types of element. Each element can be one of
+
+=over 8
+
+=item an operator name (opname)
+
+Operator names are typically small lowercase words like enterloop,
+leaveloop, last, next, redo etc. Sometimes they are rather cryptic
+like gv2cv, i_ncmp and ftsvtx.
+
+=item an operator tag name (optag)
+
+Operator tags can be used to refer to groups (or sets) of operators.
+Tag names always being with a colon. The Opcode module defines several
+optags and the user can define others using the define_optag function.
+
+=item a negated opname or optag
+
+An opname or optag can be prefixed with an exclamation mark, e.g., !mkdir.
+Negating an opname or optag means remove the corresponding ops from the
+accumulated set of ops at that point.
+
+=item an operator set (opset)
+
+An I<opset> as a binary string of approximately 43 bytes which holds a
+set or zero or more operators.
+
+The opset and opset_to_ops functions can be used to convert from
+a list of operators to an opset and I<vice versa>.
+
+Wherever a list of operators can be given you can use one or more opsets.
+See also Manipulating Opsets below.
+
+=back
+
+
+=head1 Opcode Functions
+
+The Opcode package contains functions for manipulating operator names
+tags and sets. All are available for export by the package.
+
+=over 8
+
+=item opcodes
+
+In a scalar context opcodes returns the number of opcodes in this
+version of perl (around 340 for perl5.002).
+
+In a list context it returns a list of all the operator names.
+(Not yet implemented, use @names = opset_to_ops(full_opset).)
+
+=item opset (OP, ...)
+
+Returns an opset containing the listed operators.
+
+=item opset_to_ops (OPSET)
+
+Returns a list of operator names corresponding to those operators in
+the set.
+
+=item opset_to_hex (OPSET)
+
+Returns a string representation of an opset. Can be handy for debugging.
+
+=item full_opset
+
+Returns an opset which includes all operators.
+
+=item empty_opset
+
+Returns an opset which contains no operators.
+
+=item invert_opset (OPSET)
+
+Returns an opset which is the inverse set of the one supplied.
+
+=item verify_opset (OPSET, ...)
+
+Returns true if the supplied opset looks like a valid opset (is the
+right length etc) otherwise it returns false. If an optional second
+parameter is true then verify_opset will croak on an invalid opset
+instead of returning false.
+
+Most of the other Opcode functions call verify_opset automatically
+and will croak if given an invalid opset.
+
+=item define_optag (OPTAG, OPSET)
+
+Define OPTAG as a symbolic name for OPSET. Optag names always start
+with a colon C<:>.
+
+The optag name used must not be defined already (define_optag will
+croak if it is already defined). Optag names are global to the perl
+process and optag definitions cannot be altered or deleted once
+defined.
+
+It is strongly recommended that applications using Opcode should use a
+leading capital letter on their tag names since lowercase names are
+reserved for use by the Opcode module. If using Opcode within a module
+you should prefix your tags names with the name of your module to
+ensure uniqueness and thus avoid clashes with other modules.
+
+=item opmask_add (OPSET)
+
+Adds the supplied opset to the current opmask. Note that there is
+currently I<no> mechanism for unmasking ops once they have been masked.
+This is intentional.
+
+=item opmask
+
+Returns an opset corresponding to the current opmask.
+
+=item opdesc (OP, ...)
+
+This takes a list of operator names and returns the corresponding list
+of operator descriptions.
+
+=item opdump (PAT)
+
+Dumps to STDOUT a two column list of op names and op descriptions.
+If an optional pattern is given then only lines which match the
+(case insensitive) pattern will be output.
+
+It's designed to be used as a handy command line utility:
+
+       perl -MOpcode=opdump -e opdump
+       perl -MOpcode=opdump -e 'opdump Eval'
+
+=back
+
+=head1 Manipulating Opsets
+
+Opsets may be manipulated using the perl bit vector operators & (and), | (or),
+^ (xor) and ~ (negate/invert).
+
+However you should never rely on the numerical position of any opcode
+within the opset. In other words both sides of a bit vector operator
+should be opsets returned from Opcode functions.
+
+Also, since the number of opcodes in your current version of perl might
+not be an exact multiple of eight, there may be unused bits in the last
+byte of an upset. This should not cause any problems (Opcode functions
+ignore those extra bits) but it does mean that using the ~ operator
+will typically not produce the same 'physical' opset 'string' as the
+invert_opset function.
+
+
+=head1 TO DO (maybe)
+
+    $bool = opset_eq($opset1, $opset2) true if opsets are logically eqiv
+
+    $yes = opset_can($opset, @ops)     true if $opset has all @ops set
+
+    @diff = opset_diff($opset1, $opset2) => ('foo', '!bar', ...)
+
+=cut
+
+# the =cut above is used by _init_optags() to get here quickly
+
+=head1 Predefined Opcode Tags
+
+=over 5
+
+=item :base_core
+
+    null stub scalar pushmark wantarray const defined undef
+
+    rv2sv sassign
+
+    rv2av aassign aelem aelemfast aslice av2arylen
+
+    rv2hv helem hslice each values keys exists delete
+
+    preinc i_preinc predec i_predec postinc i_postinc postdec i_postdec
+    int hex oct abs pow multiply i_multiply divide i_divide
+    modulo i_modulo add i_add subtract i_subtract
+
+    left_shift right_shift bit_and bit_xor bit_or negate i_negate
+    not complement
+
+    lt i_lt gt i_gt le i_le ge i_ge eq i_eq ne i_ne ncmp i_ncmp
+    slt sgt sle sge seq sne scmp
+
+    substr vec stringify study pos length index rindex ord chr
+
+    ucfirst lcfirst uc lc quotemeta trans chop schop chomp schomp
+
+    match split
+
+    list lslice splice push pop shift unshift reverse
+
+    cond_expr flip flop andassign orassign and or xor
+
+    warn die lineseq nextstate unstack scope enter leave
+
+    rv2cv anoncode prototype
+
+    entersub leavesub return method -- XXX loops via recursion?
+
+    leaveeval -- needed for Safe to operate, is safe without entereval
+
+=item :base_mem
+
+These memory related ops are not included in :base_core because they
+can easily be used to implement a resource attack (e.g., consume all
+available memory).
+
+    concat repeat join range
+
+    anonlist anonhash
+
+Note that despite the existance of this optag a memory resource attack
+may still be possible using only :base_core ops.
+
+Disabling these ops is a I<very> heavy handed way to attempt to prevent
+a memory resource attack. It's probable that a specific memory limit
+mechanism will be added to perl in the near future.
+
+=item :base_loop
+
+These loop ops are not included in :base_core because they can easily be
+used to implement a resource attack (e.g., consume all available CPU time).
+
+    grepstart grepwhile
+    mapstart mapwhile
+    enteriter iter
+    enterloop leaveloop
+    last next redo
+    goto
+
+=item :base_io
+
+These ops enable I<filehandle> (rather than filename) based input and
+output. These are safe on the assumption that only pre-existing
+filehandles are available for use.  To create new filehandles other ops
+such as open would need to be enabled.
+
+    readline rcatline getc read
+
+    formline enterwrite leavewrite
+
+    print sysread syswrite send recv eof tell seek
+
+    readdir telldir seekdir rewinddir
+
+=item :base_orig
+
+These are a hotchpotch of opcodes still waiting to be considered
+
+    gvsv gv gelem
+
+    padsv padav padhv padany
+
+    rv2gv refgen srefgen ref
+
+    bless -- could be used to change ownership of objects (reblessing)
+
+    glob
+
+    pushre regcmaybe regcomp subst substcont
+
+    sprintf prtf -- can core dump
+
+    crypt
+
+    tie untie
+
+    dbmopen dbmclose
+    sselect select
+    pipe_op sockpair
+
+    getppid getpgrp setpgrp getpriority setpriority localtime gmtime
+
+    entertry leavetry -- can be used to 'hide' fatal errors
+
+=item :base_math
+
+These ops are not included in :base_core because of the risk of them being
+used to generate floating point exceptions (which would have to be caught
+using a $SIG{FPE} handler).
+
+    atan2 sin cos exp log sqrt
+
+These ops are not included in :base_core because they have an effect
+beyond the scope of the compartment.
+
+    rand srand
+
+=item :default
+
+A handy tag name for a I<reasonable> default set of ops.  (The current ops
+allowed are unstable while development continues. It will change.)
+
+    :base_core :base_mem :base_loop :base_io :base_orig
+
+If safety matters to you (and why else would you be using the Opcode module?)
+then you should not rely on the definition of this, or indeed any other, optag!
+
+
+=item :filesys_read
+
+    stat lstat readlink
+
+    ftatime ftblk ftchr ftctime ftdir fteexec fteowned fteread
+    ftewrite ftfile ftis ftlink ftmtime ftpipe ftrexec ftrowned
+    ftrread ftsgid ftsize ftsock ftsuid fttty ftzero ftrwrite ftsvtx
+
+    fttext ftbinary
+
+    fileno
+
+=item :sys_db
+
+    ghbyname ghbyaddr ghostent shostent ehostent      -- hosts
+    gnbyname gnbyaddr gnetent snetent enetent         -- networks
+    gpbyname gpbynumber gprotoent sprotoent eprotoent -- protocols
+    gsbyname gsbyport gservent sservent eservent      -- services
+
+    gpwnam gpwuid gpwent spwent epwent getlogin       -- users
+    ggrnam ggrgid ggrent sgrent egrent                -- groups
+
+=item :browse
+
+A handy tag name for a I<reasonable> default set of ops beyond the
+:default optag.  Like :default (and indeed all the other optags) its
+current definition is unstable while development continues. It will change.
+
+The :browse tag represents the next step beyond :default. It it a
+superset of the :default ops and adds :filesys_read the :sys_db.
+The intent being that scripts can access more (possibly sensitive)
+information about your system but not be able to change it.
+
+    :default :filesys_read :sys_db
+
+=item :filesys_open
+
+    sysopen open close
+    umask binmode
+
+    open_dir closedir -- other dir ops are in :base_io
+
+=item :filesys_write
+
+    link unlink rename symlink truncate
+
+    mkdir rmdir
+
+    utime chmod chown
+
+    fcntl -- not strictly filesys related, but possibly as dangerous?
+
+=item :subprocess
+
+    backtick system
+
+    fork
+
+    wait waitpid
+
+=item :ownprocess
+
+    exec exit kill
+
+    time tms -- could be used for timing attacks (paranoid?)
+
+=item :others
+
+This tag holds groups of assorted specialist opcodes that don't warrant
+having optags defined for them.
+
+SystemV Interprocess Communications:
+
+    msgctl msgget msgrcv msgsnd
+
+    semctl semget semop
+
+    shmctl shmget shmread shmwrite
+
+=item :still_to_be_decided
+
+    chdir
+    flock ioctl
+
+    socket getpeername ssockopt
+    bind connect listen accept shutdown gsockopt getsockname
+
+    sleep alarm -- changes global timer state and signal handling
+    sort -- assorted problems including core dumps
+    tied -- can be used to access object implementing a tie
+    pack unpack -- can be used to create/use memory pointers
+
+    entereval -- can be used to hide code from initial compile
+    require dofile 
+
+    caller -- get info about calling environment and args
+
+    reset
+
+    dbstate -- perl -d version of nextstate(ment) opcode
+
+=item :dangerous
+
+This tag is simply a bucket for opcodes that are unlikely to be used via
+a tag name but need to be tagged for completness and documentation.
+
+    syscall dump chroot
+
+
+=back
+
+=head1 SEE ALSO
+
+ops(3) -- perl pragma interface to Opcode module.
+
+Safe(3) -- Opcode and namespace limited execution compartments
+
+=head1 AUTHORS
+
+Originally designed and implemented by Malcolm Beattie,
+mbeattie@sable.ox.ac.uk as part of Safe version 1.
+
+Split out from Safe module version 1, named opcode tags and other
+changes added by Tim Bunce <Tim.Bunce@ig.co.uk>.
+
+=cut
+
diff --git a/ext/Opcode/Opcode.xs b/ext/Opcode/Opcode.xs
new file mode 100644 (file)
index 0000000..928f680
--- /dev/null
@@ -0,0 +1,471 @@
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+/* maxo shouldn't differ from MAXO but leave room anyway (see BOOT:)   */
+#define OP_MASK_BUF_SIZE (MAXO + 100)
+
+static HV *op_named_bits;      /* cache shared for whole process       */
+static SV *opset_all;          /* mask with all bits set               */
+static IV  opset_len;          /* length of opmasks in bytes           */
+static int opcode_debug = 0;
+
+static SV  *new_opset _((SV *old_opset));
+static int  verify_opset _((SV *opset, int fatal));
+static void set_opset_bits _((char *bitmap, SV *bitspec, int on, char *opname));
+static void put_op_bitspec _((char *optag,  STRLEN len, SV *opset));
+static SV  *get_op_bitspec _((char *opname, STRLEN len, int fatal));
+
+
+/* Initialise our private op_named_bits HV.
+ * It is first loaded with the name and number of each perl operator.
+ * Then the builtin tags :none and :all are added.
+ * Opcode.pm loads the standard optags from __DATA__
+ */
+
+static void
+op_names_init()
+{
+    int i;
+    STRLEN len;
+    char *opname;
+    char *bitmap;
+
+    op_named_bits = newHV();
+    for(i=0; i < maxo; ++i) {
+       hv_store(op_named_bits, op_name[i],strlen(op_name[i]),
+               Sv=newSViv(i), 0);
+       SvREADONLY_on(Sv);
+    }
+
+    put_op_bitspec(":none",0, sv_2mortal(new_opset(Nullsv)));
+
+    opset_all = new_opset(Nullsv);
+    bitmap = SvPV(opset_all, len);
+    i = len-1; /* deal with last byte specially, see below */
+    while(i-- > 0)
+       bitmap[i] = 0xFF;
+    /* Take care to set the right number of bits in the last byte */
+    bitmap[len-1] = ~(~0 << (maxo & 0x07));
+    put_op_bitspec(":all",0, opset_all); /* don't mortalise */
+}
+
+
+/* Store a new tag definition. Always a mask.
+ * The tag must not already be defined.
+ * SV *mask is copied not referenced.
+ */
+
+static void
+put_op_bitspec(optag, len, mask)
+    char *optag;
+    STRLEN len;
+    SV *mask;
+{
+    SV **svp;
+    verify_opset(mask,1);
+    if (!len)
+       len = strlen(optag);
+    svp = hv_fetch(op_named_bits, optag, len, 1);
+    if (SvOK(*svp))
+       croak("Opcode tag \"%s\" already defined", optag);
+    sv_setsv(*svp, mask);
+    SvREADONLY_on(*svp);
+}
+
+
+
+/* Fetch a 'bits' entry for an opname or optag (IV/PV).
+ * Note that we return the actual entry for speed.
+ * Always sv_mortalcopy() if returing it to user code.
+ */
+
+static SV *
+get_op_bitspec(opname, len, fatal)
+    char *opname;
+    STRLEN len;
+    int fatal;
+{
+    SV **svp;
+    if (!len)
+       len = strlen(opname);
+    svp = hv_fetch(op_named_bits, opname, len, 0);
+    if (!svp || !SvOK(*svp)) {
+       if (!fatal)
+           return Nullsv;
+       if (*opname == ':')
+           croak("Unknown operator tag \"%s\"", opname);
+       if (*opname == '!')     /* XXX here later, or elsewhere? */
+           croak("Can't negate operators here (\"%s\")", opname);
+       if (isALPHA(*opname))
+           croak("Unknown operator name \"%s\"", opname);
+       croak("Unknown operator prefix \"%s\"", opname);
+    }
+    return *svp;
+}
+
+
+
+static SV *
+new_opset(old_opset)
+    SV *old_opset;
+{
+    SV *opset;
+    if (old_opset) {
+       verify_opset(old_opset,1);
+       opset = newSVsv(old_opset);
+    }
+    else {
+       opset = newSV(opset_len);
+       Zero(SvPVX(opset), opset_len, char);
+       SvCUR_set(opset, opset_len);
+       (void)SvPOK_only(opset);
+    }
+    /* not mortalised here */
+    return opset;
+}
+
+
+static int
+verify_opset(opset, fatal)
+    SV *opset;
+    int fatal;
+{
+    char *err = Nullch;
+    if      (!SvOK(opset))              err = "undefined";
+    else if (!SvPOK(opset))             err = "wrong type";
+    else if (SvCUR(opset) != opset_len) err = "wrong size";
+    if (err && fatal) {
+       croak("Invalid opset: %s", err);
+    }
+    return !err;
+}
+
+
+static void
+set_opset_bits(bitmap, bitspec, on, opname)
+    char *bitmap;
+    SV *bitspec;
+    int on;
+    char *opname;
+{
+    if (SvIOK(bitspec)) {
+       int myopcode = SvIV(bitspec);
+       int offset = myopcode >> 3;
+       int bit    = myopcode & 0x07;
+       if (myopcode >= maxo || myopcode < 0)
+           croak("panic: opcode \"%s\" value %d is invalid", opname, myopcode);
+       if (opcode_debug >= 2)
+           warn("set_opset_bits bit %2d (off=%d, bit=%d) %s on\n",
+                       myopcode, offset, bit, opname, (on)?"on":"off");
+       if (on)
+           bitmap[offset] |= 1 << bit;
+       else
+           bitmap[offset] &= ~(1 << bit);
+    }
+    else if (SvPOK(bitspec) && SvCUR(bitspec) == opset_len) {
+
+       STRLEN len;
+       char *specbits = SvPV(bitspec, len);
+       if (opcode_debug >= 2)
+           warn("set_opset_bits opset %s %s\n", opname, (on)?"on":"off");
+       if (on) 
+           while(len-- > 0) bitmap[len] |=  specbits[len];
+       else
+           while(len-- > 0) bitmap[len] &= ~specbits[len];
+    }
+    else
+       croak("panic: invalid bitspec for \"%s\" (type %d)",
+               opname, SvTYPE(bitspec));
+}
+
+
+static void
+opmask_add(opset)      /* THE ONLY FUNCTION TO EDIT op_mask ITSELF     */
+    SV *opset;
+{
+    int i,j;
+    char *bitmask;
+    STRLEN len;
+    int myopcode = 0;
+
+    verify_opset(opset,1);                     /* croaks on bad opset  */
+
+    if (!op_mask)              /* caller must ensure op_mask exists    */
+       croak("Can't add to uninitialised op_mask");
+
+    /* OPCODES ALREADY MASKED ARE NEVER UNMASKED. See opmask_addlocal()        */
+
+    bitmask = SvPV(opset, len);
+    for (i=0; i < opset_len; i++) {
+       U16 bits = bitmask[i];
+       if (!bits) {    /* optimise for sparse masks */
+           myopcode += 8;
+           continue;
+       }
+       for (j=0; j < 8 && myopcode < maxo; )
+           op_mask[myopcode++] |= bits & (1 << j++);
+    }
+}
+
+static void
+opmask_addlocal(opset, op_mask_buf) /* Localise op_mask then opmask_add() */
+    SV *opset;
+    char *op_mask_buf;
+{
+    char *orig_op_mask = op_mask;
+    SAVEPPTR(op_mask);
+    if (opcode_debug >= 2)
+       SAVEDESTRUCTOR((void(*)_((void*)))warn,"op_mask restored");
+    op_mask = &op_mask_buf[0];
+    if (orig_op_mask)
+       Copy(orig_op_mask, op_mask, maxo, char);
+    else
+       Zero(op_mask, maxo, char);
+    opmask_add(opset);
+}
+
+
+
+MODULE = Opcode        PACKAGE = Opcode
+
+PROTOTYPES: ENABLE
+
+BOOT:
+    assert(maxo < OP_MASK_BUF_SIZE);
+    opset_len = (maxo / 8) + 1;
+    if (opcode_debug >= 1)
+       warn("opset_len %d\n", opset_len);
+    op_names_init();
+
+
+void
+_safe_call_sv(package, mask, codesv)
+    char *     package
+    SV *       mask
+    SV *       codesv
+    PPCODE:
+    char op_mask_buf[OP_MASK_BUF_SIZE];
+    GV *gv;
+
+    ENTER;
+
+    opmask_addlocal(mask, op_mask_buf);
+
+    save_aptr(&endav);
+    endav = (AV*)sv_2mortal((SV*)newAV()); /* ignore END blocks for now        */
+
+    save_hptr(&defstash);              /* save current default stack   */
+    /* the assignment to global defstash changes our sense of 'main'   */
+    defstash = gv_stashpv(package, GV_ADDWARN); /* should exist already        */
+
+    /* defstash must itself contain a main:: so we'll add that now     */
+    /* take care with the ref counts (was cause of long standing bug)  */
+    /* XXX I'm still not sure if this is right, GV_ADDWARN should warn!        */
+    gv = gv_fetchpv("main::", GV_ADDWARN, SVt_PVHV);
+    sv_free((SV*)GvHV(gv));
+    GvHV(gv) = (HV*)SvREFCNT_inc(defstash);
+
+    PUSHMARK(sp);
+    perl_call_sv(codesv, GIMME|G_EVAL|G_KEEPERR); /* use callers context */
+    SPAGAIN; /* for the PUTBACK added by xsubpp */
+    LEAVE;
+
+
+int
+verify_opset(opset, fatal = 0)
+    SV *opset
+    int fatal
+
+
+void
+invert_opset(opset)
+    SV *opset
+    CODE:
+    {
+    char *bitmap;
+    STRLEN len = opset_len;
+    opset = new_opset(opset);  /* verify and clone opset */
+    bitmap = SvPVX(opset);
+    while(len-- > 0)
+       bitmap[len] = ~bitmap[len];
+    /* take care of extra bits beyond maxo in last byte        */
+    bitmap[opset_len-1] &= ~(0xFF << (maxo & 0x0F));
+    }
+    ST(0) = opset;
+
+
+void
+opset_to_ops(opset, desc = 0)
+    SV *opset
+    int        desc
+    PPCODE:
+    {
+    STRLEN len;
+    int i, j, myopcode;
+    char *bitmap = SvPV(opset, len);
+    char **names = (desc) ? op_desc : op_name;
+    verify_opset(opset,1);
+    for (myopcode=0, i=0; i < opset_len; i++) {
+       U16 bits = bitmap[i];
+       for (j=0; j < 8 && myopcode < maxo; j++, myopcode++) {
+           if ( bits & (1 << j) )
+               XPUSHs(sv_2mortal(newSVpv(names[myopcode], 0)));
+       }
+    }
+    }
+
+
+void
+opset(...)
+    CODE:
+    int i, j;
+    SV *bitspec, *opset;
+    char *bitmap;
+    STRLEN len, on;
+    opset = new_opset(Nullsv);
+    bitmap = SvPVX(opset);
+    for (i = 0; i < items; i++) {
+       char *opname;
+       on = 1;
+       if (verify_opset(ST(i),0)) {
+           opname = "(opset)";
+           bitspec = ST(i);
+       }
+       else {
+           opname = SvPV(ST(i), len);
+           if (*opname == '!') { on=0; ++opname;--len; }
+           bitspec = get_op_bitspec(opname, len, 1);
+       }
+       set_opset_bits(bitmap, bitspec, on, opname);
+    }
+    ST(0) = opset;
+
+
+#define PERMITING  (ix == 0 || ix == 1)
+#define ONLY_THESE (ix == 0 || ix == 2)
+
+void
+permit_only(safe, ...)
+    SV *safe
+    ALIAS:
+       permit    = 1
+       deny_only = 2
+       deny      = 3
+    CODE:
+    int i, on;
+    SV *bitspec, *mask;
+    char *bitmap, *opname;
+    STRLEN len;
+
+    if (!SvROK(safe) || !SvOBJECT(SvRV(safe)) || SvTYPE(SvRV(safe))!=SVt_PVHV)
+       croak("Not a Safe object");
+    mask = *hv_fetch((HV*)SvRV(safe), "Mask",4, 1);
+    if (ONLY_THESE)    /* *_only = new mask, else edit current */
+        sv_setsv(mask, new_opset(PERMITING ? opset_all : Nullsv));
+    else verify_opset(mask,1); /* croaks */
+    bitmap = SvPVX(mask);
+    for (i = 1; i < items; i++) {
+       on = PERMITING ? 0 : 1;         /* deny = mask bit on   */
+       if (verify_opset(ST(i),0)) {    /* it's a valid mask    */
+           opname = "(opset)";
+           bitspec = ST(i);
+       }
+       else {                          /* it's an opname/optag */
+           opname = SvPV(ST(i), len);
+           /* invert if op has ! prefix (only one allowed)     */
+           if (*opname == '!') { on = !on; ++opname; --len; }
+           bitspec = get_op_bitspec(opname, len, 1); /* croaks */
+       }
+       set_opset_bits(bitmap, bitspec, on, opname);
+    }
+    ST(0) = &sv_yes;
+
+
+
+void
+opdesc(...)
+    PPCODE:
+    int i, myopcode;
+    STRLEN len;
+    SV **args;
+    /* copy args to a scratch area since we may push output values onto        */
+    /* the stack faster than we read values off it if masks are used.  */
+    args = (SV**)SvPVX(sv_2mortal(newSVpv((char*)&ST(0), items*sizeof(SV*))));
+    for (i = 0; i < items; i++) {
+       char *opname = SvPV(args[i], len);
+       SV *bitspec = get_op_bitspec(opname, len, 1);
+       if (SvIOK(bitspec)) {
+           myopcode = SvIV(bitspec);
+           if (myopcode < 0 || myopcode >= maxo)
+               croak("panic: opcode %d (%s) out of range",myopcode,opname);
+           XPUSHs(sv_2mortal(newSVpv(op_desc[myopcode], 0)));
+       }
+       else if (SvPOK(bitspec) && SvCUR(bitspec) == opset_len) {
+           int b, j;
+           char *bitmap = SvPV(bitspec,na);
+           myopcode = 0;
+           for (b=0; b < opset_len; b++) {
+               U16 bits = bitmap[b];
+               for (j=0; j < 8 && myopcode < maxo; j++, myopcode++)
+                   if (bits & (1 << j))
+                       XPUSHs(sv_2mortal(newSVpv(op_desc[myopcode], 0)));
+           }
+       }
+       else
+           croak("panic: invalid bitspec for \"%s\" (type %d)",
+               opname, SvTYPE(bitspec));
+    }
+
+
+void
+define_optag(optagsv, mask)
+    SV *optagsv
+    SV *mask
+    CODE:
+    STRLEN len;
+    char *optag = SvPV(optagsv, len);
+    put_op_bitspec(optag, len, mask); /* croaks */
+    ST(0) = &sv_yes;
+
+
+void
+empty_opset()
+    CODE:
+    ST(0) = sv_2mortal(new_opset(Nullsv));
+
+void
+full_opset()
+    CODE:
+    ST(0) = sv_2mortal(new_opset(opset_all));
+
+void
+opmask_add(opset)
+    SV *opset
+    PREINIT:
+    if (!op_mask)
+       Newz(0, op_mask, maxo, char);
+
+void
+opcodes()
+    PPCODE:
+    if (GIMME == G_ARRAY) {
+       croak("opcodes in list context not yet implemented"); /* XXX */
+    }
+    else {
+       XPUSHs(sv_2mortal(newSViv(maxo)));
+    }
+
+void
+opmask()
+    CODE:
+    ST(0) = sv_2mortal(new_opset(Nullsv));
+    if (op_mask) {
+       char *bitmap = SvPVX(ST(0));
+       int myopcode;
+       for(myopcode=0; myopcode < maxo; ++myopcode) {
+           if (op_mask[myopcode])
+               bitmap[myopcode >> 3] |= 1 << (myopcode & 0x07);
+       }
+    }
+
diff --git a/ext/Opcode/ops.pm b/ext/Opcode/ops.pm
new file mode 100644 (file)
index 0000000..5a7b30a
--- /dev/null
@@ -0,0 +1,45 @@
+package ops;
+
+use Opcode qw(opmask_add opset invert_opset);
+
+sub import {
+    shift;
+    # Not that unimport is the prefered form since import's don't
+       # accumulate well owing to the 'only ever add opmask' rule.
+       # E.g., perl -Mops=:set1 -Mops=:setb is unlikely to do as expected.
+    opmask_add(invert_opset opset(@_));
+}
+
+sub unimport {
+    shift;
+    opmask_add(opset(@_));
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+ops - Perl pragma to restrict unsafe operations when compiling
+
+=head1 SYNOPSIS  
+
+  perl -Mops=:default ...    # only allow reasonably safe operations
+
+  perl -M-ops=system ...     # disable the 'system' opcode
+
+=head1 DESCRIPTION
+
+Since the ops pragma currently has an irreversable global effect, it is
+only of significant practical use with the C<-M> option on the command line.
+
+See the L<Opcode> module for information about opcodes, optags, opmasks
+and important information about safety.
+
+=head1 SEE ALSO
+
+Opcode(3), Safe(3), perlrun(3)
+
+=cut
+