Use constants where possible
[p5sagit/Devel-PeekPoke.git] / lib / Devel / PeekPoke / PP.pm
CommitLineData
6be43b56 1package # hide hide not just from PAUSE but from everyone - shoo shoo shooooo!
2 Devel::PeekPoke::PP;
3
4use strict;
5use warnings;
6
7use 5.008001; # because 5.6 doesn't have B::PV::object_2svref
8
9use Carp;
10use Config;
11use Devel::PeekPoke::Constants qw/PTR_SIZE PTR_PACK_TYPE/;
12use B (); # for B::PV
13
42cf0549 14use constant {
15 _MAX_ADDR => 'FF' x PTR_SIZE,
16 _PERLVERSION => "$]", # we do not support every perl, as we rely on the implementation of SV/SvPV
17};
18
19BEGIN {
20 # we know we start from 5.8.1
21 if ( (_PERLVERSION =~ /^5\.(\d{3})/)[0] % 2 ) {
22 die "@{[ __PACKAGE__ ]} does not function on development perl versions (by design)\n";
23 }
24 elsif (_PERLVERSION < 5.010) {
25 constant->import({
26 _SV_SIZE => PTR_SIZE + 4 + 4, # SvANY + 32bit refcnt + 32bit flags
27 _XPV_SIZE => PTR_SIZE + $Config{sizesize} + $Config{sizesize}, # PVX ptr + cur + len
28 _SVU_OFFSET => 0,
29 });
30 }
31 elsif (_PERLVERSION < 5.016) {
32 constant->import({
33 _SV_SIZE => PTR_SIZE + 4 + 4 + $Config{ivsize}, # SvANY + 32bit refcnt + 32bit flags + SV_U
34 _XPV_SIZE => undef, # it isn't really undefined, we just do not care
35 _SVU_OFFSET => PTR_SIZE + 4 + 4,
36 });
37 }
38 else {
39 # do not take any chanes with not-yet-released perls - things may change
40 die "@{[ __PACKAGE__ ]} does not *yet* support this perl $], please file a bugreport (it is very very easy to fix)\n";
41 }
6be43b56 42}
43
6be43b56 44sub _pack_address {
45 my ($digits) = (defined $_[0] and $_[0] =~ /^(\d+)$/)
46 or croak "Invalid address '$_[0]' - expecting an integer";
47
48 my $p = pack(PTR_PACK_TYPE, $_[0]);
49
50 # FIXME - is there a saner way to check for overflows?
51 no warnings 'portable'; # hex() with a 64bit value
42cf0549 52 croak "Your system does not support addresses larger than 0x@{[ _MAX_ADDR ]}, you supplied $digits"
53 if ( $_[0] > hex(_MAX_ADDR) or uc(unpack('H*', $p)) eq _MAX_ADDR );
6be43b56 54
55 return $p;
56}
57
58sub peek {
59 #my($location, $len_bytes) = @_;
60 croak "Peek where and how much?" unless (defined $_[0]) and $_[1];
61 unpack "P$_[1]", _pack_address($_[0]);
62}
63
64# this implementation is based on (a portably written version of)
65# http://www.perlmonks.org/?node_id=379428
66# there should be a much simpler way according to Reini Urban, but I
67# was not able to make it work: https://gist.github.com/1151345
68sub poke {
69 my($location, $bytes) = @_;
70 croak "Poke where and what?" unless (defined $location) and (defined $bytes);
71
72 # sanity check is (imho) warranted as described here:
73 # http://blogs.perl.org/users/aristotle/2011/08/utf8-flag.html#comment-36499
74 if (utf8::is_utf8($bytes) and $bytes =~ /([^\x00-\x7F])/) {
75 croak( ord($1) > 255
76 ? "Expecting a byte string, but received characters"
77 : "Expecting a byte string, but received what looks like *possible* characters, please utf8_downgrade the input"
78 );
79 }
80
81 # this should be constant once we pass the regex check above... right?
82 my $len = length($bytes);
83
42cf0549 84 # construct a B::PV object, backed by a SV/SvPV to a dummy string length($bytes)
85 # long, and substitute $location as the actual string storage
6be43b56 86 # we specifically use the same length so we do not have to deal with resizing
42cf0549 87 my $dummy = 'X' x $len;
88 my $dummy_addr = \$dummy + 0;
6be43b56 89
42cf0549 90 my $ghost_sv_contents = peek($dummy_addr, _SV_SIZE);
91
92 if (_XPV_SIZE) { # 5.8 xpv stuff
93 my $xpv_addr = unpack(PTR_PACK_TYPE, peek($dummy_addr, PTR_SIZE) );
94 my $xpv_contents = peek( $xpv_addr, _XPV_SIZE ); # we do not care about cur/len (they will be the same)
6be43b56 95
96 substr( $xpv_contents, 0, PTR_SIZE ) = _pack_address($location); # replace pvx in xpv with sanity-checked $location
42cf0549 97 substr( $ghost_sv_contents, 0, PTR_SIZE) = pack ('P', $xpv_contents ); # replace xpv in sv
98 }
99 else { # new style 5.10+ SVs
100 substr( $ghost_sv_contents, _SVU_OFFSET, PTR_SIZE ) = _pack_address($location);
6be43b56 101 }
102
42cf0549 103 my $ghost_string_ref = bless( \ unpack(
104 PTR_PACK_TYPE,
36e403bb 105 # it is crucial to create a copy of $sv_contents, and work with a temporary
106 # memory location. Otherwise perl memory allocation will kick in and wreak
107 # considerable havoc culminating with an inevitable segfault
42cf0549 108 do { no warnings 'pack'; pack( 'P', $ghost_sv_contents.'' ) },
109 ), 'B::PV' )->object_2svref;
6be43b56 110
111 # now when we write to the newly created "string" we are actually writing
112 # to $location
113 # note we HAVE to use lvalue substr - a plain assignment will add a \0
42cf0549 114 substr($$ghost_string_ref, 0, $len) = $bytes;
36e403bb 115
6be43b56 116 return $len;
117}
118
1191;