gotch/pickle/pickle.go

1947 lines
46 KiB
Go

package pickle
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
"math/big"
"os"
"strconv"
"strings"
"reflect"
"runtime"
)
// This file implements Python Pickle Machinery.
//
// Pickle creates portable serialized representations of Python objects.
// See module `copyreg` for a mechanism for registering custom picklers
// See module `pickletools` source for extensive comments
// Ref. https://github.com/python/cpython/blob/main/Lib/pickle.py
// See more:...
// https://docs.python.org/3/library/pickle.html
// https://docs.python.org/3/library/pickletools.html
// https://pytorch.org/tutorials/beginner/saving_loading_models.html
// https://github.com/pytorch/pytorch/blob/master/torch/serialization.py
// Pikle Version:
// ==============
// FormatVersion = "4.0"
// CompatibleFormats [
// "1.0": Original protocol 0
// "1.1": Protocol 0 with INST added
// "1.2": Original protocol 1
// "1.3": Protocol 1 with BINFLOAT added
// "2.0": Protocol 2
// "3.0": Protocol 3
// "4.0": Protocol 4
// "5.0": Protocol 5
// ]
const HighestProtocol byte = 5 // The highest protocol number pickle currently knows how to read
var DefaultProtocol byte = 4 // The protocol pickle currently used to write by default.
// Error formatter:
// ================
func pickleError(msg string) error {
err := fmt.Errorf("PicklingError: %s", msg)
return err
}
func picklingError(msg string) error {
err := fmt.Errorf("Unpickable Object: %s", msg)
return err
}
func unpicklingError(msg string) error {
err := fmt.Errorf("UnpicklingError: %s", msg)
return err
}
// Stop implements error interface. It is raised by `Unpickler.LoadStop()`
// in response to the STOP opcode, passing the object that is the result of unpickling.
type Stop struct {
value interface{} // TODO. specific type
}
func newStop(value interface{}) Stop { return Stop{value} }
func (s Stop) Error() string { return "STOP" }
var _ error = Stop{}
// Pickle opcodes:
// ==============
// See pickletools.py for extensive docs.
var (
MARK rune = '(' // push special markobject on stack
STOP rune = '.' // every pickle ends with STOP
POP rune = '0' // discard topmost stack item
POP_MARK rune = '1' // discard stack top through topmost markobject
DUP rune = '2' // duplicate top stack item
FLOAT rune = 'F' // push float object; decimal string argument
INT rune = 'I' // push integer or bool; decimal string argument
BININT rune = 'J' // push four-byte signed int
BININT1 rune = 'K' // push 1-byte unsigned int
LONG rune = 'L' // push long; decimal string argument
BININT2 rune = 'M' // push 2-byte unsigned int
NONE rune = 'N' // push None
PERSID rune = 'P' // push persistent object; id is taken from string arg
BINPERSID rune = 'Q' // " " " ; " " " " stack
REDUCE rune = 'R' // apply callable to argtuple, both on stack
STRING rune = 'S' // push string; NL-terminated string argument
BINSTRING rune = 'T' // push string; counted binary string argument
SHORT_BINSTRING rune = 'U' // " " ; " " " " < 256 bytes
UNICODE rune = 'V' // push Unicode string; raw-unicode-escaped'd argument
BINUNICODE rune = 'X' // " " " ; counted UTF-8 string argument
APPEND rune = 'a' // append stack top to list below it
BUILD rune = 'b' // call __setstate__ or __dict__.update()
GLOBAL rune = 'c' // push self.find_class(modname, name); 2 string args
DICT rune = 'd' // build a dict from stack items
EMPTY_DICT rune = '}' // push empty dict
APPENDS rune = 'e' // extend list on stack by topmost stack slice
GET rune = 'g' // push item from memo on stack; index is string arg
BINGET rune = 'h' // " " " " " " ; " " 1-byte arg
INST rune = 'i' // build & push class instance
LONG_BINGET rune = 'j' // push item from memo on stack; index is 4-byte arg
LIST rune = 'l' // build list from topmost stack items
EMPTY_LIST rune = ']' // push empty list
OBJ rune = 'o' // build & push class instance
PUT rune = 'p' // store stack top in memo; index is string arg
BINPUT rune = 'q' // " " " " " ; " " 1-byte arg
LONG_BINPUT rune = 'r' // " " " " " ; " " 4-byte arg
SETITEM rune = 's' // add key+value pair to dict
TUPLE rune = 't' // build tuple from topmost stack items
EMPTY_TUPLE rune = ')' // push empty tuple
SETITEMS rune = 'u' // modify dict by adding topmost key+value pairs
BINFLOAT rune = 'G' // push float; arg is 8-byte float encoding
// TRUE rune = 'I01\n' // not an opcode; see INT docs in pickletools.py
// FALSE rune = 'I00\n' // not an opcode; see INT docs in pickletools.py
// Protocol 2
PROTO rune = '\x80' // identify pickle protocol
NEWOBJ rune = '\x81' // build object by applying cls.__new__ to argtuple
EXT1 rune = '\x82' // push object from extension registry; 1-byte index
EXT2 rune = '\x83' // ditto, but 2-byte index
EXT4 rune = '\x84' // ditto, but 4-byte index
TUPLE1 rune = '\x85' // build 1-tuple from stack top
TUPLE2 rune = '\x86' // build 2-tuple from two topmost stack items
TUPLE3 rune = '\x87' // build 3-tuple from three topmost stack items
NEWTRUE rune = '\x88' // push True
NEWFALSE rune = '\x89' // push False
LONG1 rune = '\x8a' // push long from < 256 bytes
LONG4 rune = '\x8b' // push really big long
tuplesize2code []rune = []rune{EMPTY_TUPLE, TUPLE1, TUPLE2, TUPLE3}
// Protocol 3 (Python 3.x)
BINBYTES rune = 'B' // push bytes; counted binary string argument
SHORT_BINBYTES rune = 'C' // " " ; " " " " < 256 bytes
// Protocol 4
SHORT_BINUNICODE rune = '\x8c' // push short string; UTF-8 length < 256 bytes
BINUNICODE8 rune = '\x8d' // push very long string
BINBYTES8 rune = '\x8e' // push very long bytes string
EMPTY_SET rune = '\x8f' // push empty set on the stack
ADDITEMS rune = '\x90' // modify set by adding topmost stack items
FROZENSET rune = '\x91' // build frozenset from topmost stack items
NEWOBJ_EX rune = '\x92' // like NEWOBJ but work with keyword only arguments
STACK_GLOBAL rune = '\x93' // same as GLOBAL but using names on the stacks
MEMOIZE rune = '\x94' // store top of the stack in memo
FRAME rune = '\x95' // indicate the beginning of a new frame
// Protocol 5
BYTEARRAY8 rune = '\x96' // push bytearray
NEXT_BUFFER rune = '\x97' // push next out-of-band buffer
READONLY_BUFFER rune = '\x98' // make top of stack readonly
)
// Unpickling Machinery:
// =====================
type Unpickler struct {
proto byte // protocol version of the pickle
reader io.Reader // binary file reader
currentFrame *bytes.Reader // buffer frame reader
stack []interface{} // keeps marked objects
metaStack [][]interface{} // keeps stacks of marked objects
// data structure that remembers which objects the pickler/unpickler has already seen
// so that shared or recursive objects are pickled/unpickled by reference and not by value
// This property is useful when re-using picklers/unpicklers.
memo map[int]interface{}
FindClass func(module, name string) (interface{}, error) // function to determine data type
PersistentLoad func(interface{}) (interface{}, error) // function how to load pickled objects by its id.
GetExtension func(code int) (interface{}, error)
NextBufferFunc func() (interface{}, error)
MakeReadOnlyFunc func(interface{}) (interface{}, error)
}
// NewUnpickler creates a new Unpickler.
func NewUnpickler(r io.Reader) Unpickler {
return Unpickler{
reader: r,
memo: make(map[int]interface{}, 0),
}
}
// read reads n bytes from reader.
func (up *Unpickler) read(n int) ([]byte, error) {
data := make([]byte, n)
if up.currentFrame != nil {
nbytes, err := io.ReadFull(up.currentFrame, data)
switch {
case err != nil && err != io.EOF && err != io.ErrUnexpectedEOF:
return nil, err
case nbytes == 0 && n != 0: // remaining data
up.currentFrame = nil
nbytes, err := io.ReadFull(up.reader, data)
return data[0:nbytes], err
case nbytes < n:
err := fmt.Errorf("Unpickler.read() failed: pickle exhausted before end of frame")
return nil, err
default:
return data[0:nbytes], nil
}
}
nbytes, err := io.ReadFull(up.reader, data)
return data[0:nbytes], err
}
// readOne reads 1 byte.
func (up *Unpickler) readOne() (byte, error) {
data, err := up.read(1)
if err != nil {
return 0, err
}
return data[0], nil
}
// readLine reads one line of data.
func (up *Unpickler) readLine() ([]byte, error) {
if up.currentFrame != nil {
line, err := readLine(up.currentFrame)
if err != nil {
if err == io.EOF && len(line) == 0 {
up.currentFrame = nil
return readLine(up.reader)
}
return nil, err
}
if len(line) == 0 {
err := fmt.Errorf("Unpickler.readLine() failed: no data.")
return nil, err
}
if line[len(line)-1] != '\n' {
err := fmt.Errorf("Unpickler.readLine() failed: pickle exhausted before end of frame.")
return nil, err
}
return line, nil
}
return readLine(up.reader)
}
// readLine reads one line of data. Line ends by '\n' byte.
func readLine(r io.Reader) ([]byte, error) {
bufferSize := 64 // just set buffer line = 64. One might change it.
line := make([]byte, 0, bufferSize)
buf := make([]byte, 1)
for {
nbytes, err := r.Read(buf)
if nbytes != 1 {
return line, err
}
line = append(line, buf[0])
if buf[0] == '\n' || err != nil {
return line, err
}
}
}
// loadFrame loads new data to currentFrame. It throws error if currentFrame is not empty.
func (up *Unpickler) loadFrame(frameSize int) error {
buf := make([]byte, frameSize)
// Throw error if current frame is not empty
if up.currentFrame != nil {
nbytes, err := up.currentFrame.Read(buf)
if nbytes > 0 || err == nil {
err := unpicklingError("beginning of a new frame before end of a current frame")
return err
}
}
// now, load data to currentFrame
_, err := io.ReadFull(up.reader, buf)
if err != nil {
return err
}
up.currentFrame = bytes.NewReader(buf)
return nil
}
// append appends an object to stack.
func (up *Unpickler) append(obj interface{}) {
up.stack = append(up.stack, obj)
}
// stackPop pops an object out of stack.
func (up *Unpickler) stackPop() (interface{}, error) {
obj, err := up.stackLast()
if err != nil {
return nil, err
}
up.stack = up.stack[:len(up.stack)-1]
return obj, nil
}
// stackLast get last object in stack.
func (up *Unpickler) stackLast() (interface{}, error) {
if len(up.stack) == 0 {
err := fmt.Errorf("Unpickler.stackLast() failed: stack is empty.")
return nil, err
}
last := up.stack[len(up.stack)-1]
return last, nil
}
// metaStackPop pop a stack out from metaStack.
func (up *Unpickler) metaStackPop() ([]interface{}, error) {
stack, err := up.metaStackLast()
if err != nil {
return nil, err
}
up.metaStack = up.metaStack[:len(up.metaStack)-1]
return stack, nil
}
// metaStackLast get last stack in metaStack.
func (up *Unpickler) metaStackLast() ([]interface{}, error) {
if len(up.metaStack) == 0 {
err := fmt.Errorf("Unpickler.metaStackLast() failed: metaStack is empty.")
return nil, err
}
last := up.metaStack[len(up.metaStack)-1]
return last, nil
}
// popMark pops all objects those have been pushed to the stack (after last mask).
func (up *Unpickler) popMark() ([]interface{}, error) {
objects := up.stack
newStack, err := up.metaStackPop()
if err != nil {
return nil, err
}
up.stack = newStack
return objects, nil
}
func (up *Unpickler) findClass(module, name string) (interface{}, error) {
switch module {
case "collections":
switch name {
case "OrderedDict":
return &OrderedDictClass{}, nil
}
case "__builtin__":
switch name {
case "object":
return &ObjectClass{}, nil
}
case "copy_reg":
switch name {
case "_reconstructor":
return &Reconstructor{}, nil
}
}
if up.FindClass != nil {
return up.FindClass(module, name)
}
return NewGenericClass(module, name), nil
}
func (up *Unpickler) persistentLoad(pid interface{}) error {
err := unpicklingError("Unpickler.persistentLoad() failed: unsupported persistent id encountered.")
return err
}
// Construct dispatch table:
// =========================
// See https://en.wikipedia.org/wiki/Dispatch_table
// dispatch table is a table of pointers to functions/methods.
// unpickle dispatch table
var upDispatch [math.MaxUint8]func(*Unpickler) error
// loadProto reads pickle protocol version.
func loadProto(up *Unpickler) error {
proto, err := up.readOne()
if err != nil {
return err
}
if proto < 0 || proto >= HighestProtocol {
err := fmt.Errorf("loadProto() failed: unsupported pickle protocol (%d)", proto)
return err
}
up.proto = proto
return nil
}
// loadFrame loads new frame.
func loadFrame(up *Unpickler) error {
buf, err := up.read(8)
if err != nil {
return err
}
frameSize := binary.LittleEndian.Uint64(buf)
if frameSize > math.MaxUint64 {
err := fmt.Errorf("loadFrame() failed: frame size > sys.maxsize %v", frameSize)
return err
}
return up.loadFrame(int(frameSize))
}
// loadPersIds load persistent object to stack.
func loadPersId(up *Unpickler) error {
if up.PersistentLoad == nil {
err := fmt.Errorf("loadPersId() failed: unsupported persistent Id encountered.")
return err
}
line, err := up.readLine()
if err != nil {
return err
}
pid := string(line[:len(line)-1])
obj, err := up.PersistentLoad(pid)
if err != nil {
err = fmt.Errorf("loadPersId() failed: %w", err)
return err
}
up.append(obj)
return nil
}
func loadBinPersId(up *Unpickler) error {
if up.PersistentLoad == nil {
err := fmt.Errorf("loadPersId() failed: unsupported persistent Id encountered.")
return err
}
pid, err := up.stackPop()
if err != nil {
return err
}
obj, err := up.PersistentLoad(pid)
if err != nil {
err = fmt.Errorf("loadBinPersId() failed: %w", err)
return err
}
up.append(obj)
return nil
}
// loads nil object
func loadNone(up *Unpickler) error {
up.append(nil)
return nil
}
// loads a bool object with value = false.
func loadFalse(up *Unpickler) error {
up.append(false)
return nil
}
// loads a bool object with value = true
func loadTrue(up *Unpickler) error {
up.append(true)
return nil
}
// loads object of type int (can be integer, bool or decimal string value)
func loadInt(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadInt() failed: %w", err)
return err
}
data := string(line[:len(line)-1])
switch {
case len(data) == 2 && data[0] == '0' && data[1] == '0':
up.append(false)
return nil
case len(data) == 2 && data[0] == '0' && data[1] == '1':
up.append(true)
return nil
default:
val, err := strconv.Atoi(data)
if err != nil {
err = fmt.Errorf("loadInt() failed: %w", err)
}
up.append(val)
return nil
}
}
// load 4 bytes of uint.
func loadBinInt(up *Unpickler) error {
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("loadBinInt() failed: %w", err)
return err
}
uval := binary.LittleEndian.Uint32(buf)
val := int(uval)
if buf[3]&0x80 != 0 {
val = -(int(^uval) + 1)
}
up.append(val)
return nil
}
// loads one byte of uint.
func loadBinInt1(up *Unpickler) error {
b, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadBinInt1() failed: %w", err)
return err
}
up.append(int(b))
return nil
}
// loads 2 bytes of uint.
func loadBinInt2(up *Unpickler) error {
buf, err := up.read(2)
if err != nil {
err = fmt.Errorf("loadBinInt2() failed: %w", err)
return err
}
val := int(binary.LittleEndian.Uint16(buf))
up.append(val)
return nil
}
// load long; decimal string argument.
func loadLong(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadLong() failed: %w", err)
return nil
}
// last byte is string dtype.
if len(line) == 1 {
err = fmt.Errorf("loadLong() failed: invalid long data")
}
data := line[:len(line)-1]
if data[len(data)-1] == 'L' {
data = data[0 : len(data)-1]
}
val, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
// check for overflow, if so, swap to larger range.
if numErr, ok := err.(*strconv.NumError); ok && numErr.Err == strconv.ErrRange {
bigInt, ok := new(big.Int).SetString(string(data), 10)
if !ok {
err = fmt.Errorf("loadLong() failed: invalid long data")
return err
}
up.append(bigInt)
return nil
}
err = fmt.Errorf("loadLong() failed: %w", err)
return err
}
up.append(int(val))
return nil
}
// loads long interger of less than 256 bytes.
func loadLong1(up *Unpickler) error {
len, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadLong1() failed: %w", err)
return err
}
buf, err := up.read(int(len))
if err != nil {
err = fmt.Errorf("loadLong1() failed: %w", err)
return err
}
val := decodeLong(buf)
up.append(val)
return nil
}
// loads object of really big long integer.
func loadLong4(up *Unpickler) error {
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("loadLong4() failed: %w", err)
return err
}
len := decodeInt32(buf)
if len < 0 {
err = fmt.Errorf("loadLong4() failed: LONG pickle has negative byte count")
}
data, err := up.read(len)
if err != nil {
err = fmt.Errorf("loadLong4() failed: %w", err)
return err
}
val := decodeLong(data)
up.append(val)
return nil
}
// loads float object or decimal string argument.
func loadFloat(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadFloat() failed: %w", err)
return err
}
val, err := strconv.ParseFloat(string(line[:len(line)-1]), 64)
if err != nil {
err = fmt.Errorf("loadFloat() failed: %w", err)
return err
}
up.append(val)
return nil
}
// loads float object of 8-byte encoding.
func loadBinFloat(up *Unpickler) error {
buf, err := up.read(8)
if err != nil {
err = fmt.Errorf("loadBinFloat() failed: %w", err)
return err
}
val := math.Float64frombits(binary.BigEndian.Uint64(buf))
up.append(val)
return nil
}
// loads object of string value.
func loadString(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadString() failed: %w", err)
return err
}
data := line[:len(line)-1]
// strip outermost quotes
if len(data) >= 2 && data[0] == data[len(data)-1] && (data[0] == '\'' || data[0] == '"') {
data = data[1 : len(data)-1]
} else {
err = unpicklingError("the STRING opcode argument must be quoted.")
err = fmt.Errorf("loadString() failed: %w", err)
return err
}
up.append(data)
return nil
}
// loads object of counted binary string.
func loadBinString(up *Unpickler) error {
// Deprecated BINSTRING uses signed 32-bit length
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("loadBinString() failed: %w", err)
return err
}
len := decodeInt32(buf)
if len < 0 {
err = unpicklingError("loadBinString() failed: BINSTRING pickle has negative byte count.")
return err
}
data, err := up.read(len)
if err != nil {
err = fmt.Errorf("loadBinString() failed: %w", err)
return err
}
val := string(data)
up.append(val)
return nil
}
// loads object of bytes
func loadBinBytes(up *Unpickler) error {
buf, err := up.read(4)
if err != nil {
err := fmt.Errorf("loadBinBytes() failed: %w", err)
return err
}
len := int(binary.LittleEndian.Uint32(buf))
buf, err = up.read(len)
if err != nil {
err := fmt.Errorf("loadBinBytes() failed: %w", err)
return err
}
up.append(buf)
return nil
}
// loads object of Unicode string value (raw-unicode-escaped).
func loadUnicode(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err := fmt.Errorf("loadUnicode() failed: %w", err)
return err
}
val := string(line[:len(line)-1])
up.append(val)
return nil
}
// loads objects of Unicode string (counted UTF-8 string)
func loadBinUnicode(up *Unpickler) error {
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("loadBinUnicode() failed: %w", err)
return err
}
len := int(binary.LittleEndian.Uint32(buf))
buf, err = up.read(len)
if err != nil {
err = fmt.Errorf("loadBinUnicode() failed: %w", err)
return err
}
val := string(buf)
up.append(val)
return nil
}
// loads a object of very long string value.
func loadBinUnicode8(up *Unpickler) error {
buf, err := up.read(8)
if err != nil {
err = fmt.Errorf("loadBinUnicode8() failed: %w", err)
return err
}
len := int(binary.LittleEndian.Uint64(buf))
if len > math.MaxInt64 {
err = unpicklingError("loadBinUnicode8() failed: BINUNICODE8 exceeds system's maximum size")
return err
}
buf, err = up.read(len)
if err != nil {
err = fmt.Errorf("loadBinUnicode8() failed: %w", err)
return err
}
val := string(buf)
up.append(val)
return nil
}
// loads object of very long bytes string value.
func loadBinBytes8(up *Unpickler) error {
buf, err := up.read(8)
if err != nil {
err = fmt.Errorf("loadBinBytes8() failed: %w", err)
return err
}
len := binary.LittleEndian.Uint64(buf)
if len > math.MaxInt64 {
err = unpicklingError("loadBinBytes8() failed: BINBYTES8 exceeds system's maximum size")
return err
}
buf, err = up.read(int(len))
if err != nil {
err = fmt.Errorf("loadBinBytes8() failed: %w", err)
return err
}
up.append(buf)
return nil
}
func loadByteArray8(up *Unpickler) error {
buf, err := up.read(8)
if err != nil {
err = fmt.Errorf("loadBinBytes8() failed: %w", err)
return err
}
len := binary.LittleEndian.Uint64(buf)
if len > math.MaxInt64 {
err = unpicklingError("loadBinBytes8() failed: BINBYTES8 exceeds system's maximum size.")
return err
}
buf, err = up.read(int(len))
if err != nil {
err = fmt.Errorf("loadBinBytes8() failed: %w", err)
return err
}
val := NewByteArrayFromSlice(buf)
up.append(val)
return nil
}
// loads next out-of-band buffer.
func loadNextBuffer(up *Unpickler) error {
if up.NextBufferFunc == nil {
err := fmt.Errorf("loadNextBuffer() failed: Pickle stream refers to out-of-band data but NextBufferFunc was not given")
return err
}
buf, err := up.NextBufferFunc()
if err != nil {
err = fmt.Errorf("loadNextBuffer() failed: %w", err)
return err
}
up.append(buf)
return nil
}
// makes top of stack readonly.
func loadReadOnlyBuffer(up *Unpickler) error {
if up.MakeReadOnlyFunc == nil {
return nil
}
buf, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadReadOnlyBuffer() failed: %w", err)
return err
}
buf, err = up.MakeReadOnlyFunc(buf)
if err != nil {
err = fmt.Errorf("loadReadOnlyBuffer() failed: %w", err)
return err
}
up.append(buf)
return nil
}
// loads counted binary string object (< 256 bytes).
func loadShortBinString(up *Unpickler) error {
len, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadShortBinString() failed: %w", err)
return err
}
data, err := up.read(int(len))
if err != nil {
err = fmt.Errorf("loadShortBinString() failed: %w", err)
return err
}
up.append(string(data))
return nil
}
// loads bytes object with counted binary string < 256 bytes.
func loadShortBinBytes(up *Unpickler) error {
len, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadShortBinBytes() failed: %w", err)
return err
}
buf, err := up.read(int(len))
if err != nil {
err = fmt.Errorf("loadShortBinBytes() failed: %w", err)
return err
}
up.append(buf)
return nil
}
// load short string object; UTF-8 length < 256 bytes.
func loadShortBinUnicode(up *Unpickler) error {
len, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadShortBinUnicode() failed: %w", err)
return err
}
buf, err := up.read(int(len))
if err != nil {
err = fmt.Errorf("loadShortBinUnicode() failed: %w", err)
return err
}
up.append(string(buf))
return nil
}
// loads tuple from last-mark stack objects.
func loadTuple(up *Unpickler) error {
objects, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadTuple() failed: %w", err)
return err
}
val := NewTupleFromSlice(objects)
up.append(val)
return nil
}
// load empty tuple.
func loadEmptyTuple(up *Unpickler) error {
t := NewTupleFromSlice([]interface{}{})
up.append(t)
return nil
}
// load one tuple from stack top.
func loadTuple1(up *Unpickler) error {
obj, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadTuple() failed: %w", err)
return err
}
val := NewTupleFromSlice([]interface{}{obj})
up.append(val)
return nil
}
// load 2-tuple object from 2 topmost stack objects.
func loadTuple2(up *Unpickler) error {
obj2, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadTuple2() failed: %w", err)
return err
}
obj1, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadTuple2() failed: %w", err)
return err
}
val := NewTupleFromSlice([]interface{}{obj1, obj2})
up.append(val)
return nil
}
// loads 3-tuple object from 3 most stack objects
func loadTuple3(up *Unpickler) error {
objects := make([]interface{}, 3)
var (
err error
)
for i := 2; i >= 0; i-- {
objects[i], err = up.stackPop()
if err != nil {
err = fmt.Errorf("loadTuple3() failed: %w", err)
}
}
val := NewTupleFromSlice(objects)
up.append(val)
return nil
}
// loads empty list object.
func loadEmptyList(up *Unpickler) error {
up.append(NewList())
return nil
}
// loads empty dict.
func loadEmptyDict(up *Unpickler) error {
up.append(NewDict())
return nil
}
// loads empty set on the stack.
func loadEmptySet(up *Unpickler) error {
up.append(NewSet())
return nil
}
// loads frozenset from topmost stack objects.
func loadFrozenSet(up *Unpickler) error {
objects, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadFrozenSet() failed: %w", err)
return err
}
up.append(NewFrozenSetFromSlice(objects))
return nil
}
// loads list from topmost stack objects
func loadList(up *Unpickler) error {
objects, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadList() failed: %w", err)
return err
}
up.append(NewListFromSlice(objects))
return nil
}
// loads a dict from stack objects
func loadDict(up *Unpickler) error {
objects, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadDict() failed: %w", err)
return err
}
d := NewDict()
objectsLen := len(objects)
for i := 0; i < objectsLen; i += 2 {
d.Set(objects[i], objects[i+1])
}
up.append(d)
return nil
}
// loads class instance.
func loadInst(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadInst() failed: %w", err)
return err
}
module := string(line[0 : len(line)-1])
line, err = up.readLine()
if err != nil {
err = fmt.Errorf("loadInst() failed: %w", err)
return err
}
name := string(line[0 : len(line)-1])
class, err := up.findClass(module, name)
if err != nil {
err = fmt.Errorf("loadInst() failed: %w", err)
return err
}
args, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadInst() failed: %w", err)
return err
}
return up.instantiate(class, args)
}
// loads class instance
func loadObj(up *Unpickler) error {
// Stack is ... markobject classobject arg1 arg2 ...
args, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadObj() failed: %w", err)
return err
}
if len(args) == 0 {
return fmt.Errorf("OBJ class missing")
}
class := args[0]
args = args[1:len(args)]
return up.instantiate(class, args)
}
// instantiates a object based on input dtype and arguments.
func (up *Unpickler) instantiate(class interface{}, args []interface{}) error {
var err error
var value interface{}
switch ct := class.(type) {
case Callable:
value, err = ct.Call(args...)
case PyNewable:
value, err = ct.PyNew(args...)
default:
return fmt.Errorf("cannot instantiate %#v", class)
}
if err != nil {
err = fmt.Errorf("instantiate() failed: %w", err)
return err
}
up.append(value)
return nil
}
// loads object by applying cls.__new__ to argtuple
func loadNewObj(up *Unpickler) error {
args, err := up.stackPop()
if err != nil {
return err
}
argsTuple, argsOk := args.(*Tuple)
if !argsOk {
err := fmt.Errorf("NEWOBJ args must be *Tuple")
err = fmt.Errorf("loadNewObj() failed: %w", err)
return err
}
rawClass, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadNewObj() failed: %w", err)
return err
}
class, classOk := rawClass.(PyNewable)
if !classOk {
err := fmt.Errorf("NEWOBJ requires a PyNewable object: %#v", rawClass)
err = fmt.Errorf("loadNewObj() failed: %w", err)
return err
}
result, err := class.PyNew(*argsTuple...)
if err != nil {
return err
}
up.append(result)
return nil
}
// like NEWOBJ but work with keyword only arguments
func loadNewObjEx(up *Unpickler) error {
kwargs, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadNewObjEx() failed: %w", err)
return err
}
args, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadNewObjEx() failed: %w", err)
return err
}
argsTuple, argsOk := args.(*Tuple)
if !argsOk {
err := fmt.Errorf("NEWOBJ_EX args must be *Tuple")
err = fmt.Errorf("loadNewObjEx() failed: %w", err)
return err
}
rawClass, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadNewObjEx() failed: %w", err)
return err
}
class, classOk := rawClass.(PyNewable)
if !classOk {
err := fmt.Errorf("NEWOBJ_EX requires a PyNewable object")
err = fmt.Errorf("loadNewObjEx() failed: %w", err)
return err
}
allArgs := []interface{}(*argsTuple)
allArgs = append(allArgs, kwargs)
result, err := class.PyNew(allArgs...)
if err != nil {
err = fmt.Errorf("loadNewObjEx() failed: %w", err)
return err
}
up.append(result)
return nil
}
// loads 'self.find_class(module, name)'; 2 string args.
// It decodes "module" and "name" of the object from binary file
// and find object class, then push to stack.
//
// NOTE. Pytorch rebuilds tensor (legacy) triggers from here
// with: module "torch._utils" - name "_rebuild_tensor" or "_rebuild_tensor_v2"
// rebuild tensor based on '_rebuild_tensor_v2' hook may break in the future.
// ref. https://github.com/pytorch/pytorch/blob/c2255c36ec121fdb998ce3db8deb7508c814b567/torch/_utils.py#L138
func loadGlobal(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadGlobal() failed: %w", err)
return err
}
module := string(line[0 : len(line)-1])
line, err = up.readLine()
if err != nil {
err = fmt.Errorf("loadGlobal() failed: %w", err)
return err
}
name := string(line[0 : len(line)-1])
class, err := up.findClass(module, name)
if err != nil {
err = fmt.Errorf("loadGlobal() failed: %w", err)
return err
}
up.append(class)
return nil
}
// same as GLOBAL but using names on the stacks
func loadStackGlobal(up *Unpickler) error {
rawName, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
name, nameOk := rawName.(string)
if !nameOk {
err := fmt.Errorf("STACK_GLOBAL requires str name: %#v", rawName)
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
rawModule, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
module, moduleOk := rawModule.(string)
if !moduleOk {
err := fmt.Errorf("STACK_GLOBAL requires str module: %#v", rawModule)
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
class, err := up.findClass(module, name)
if err != nil {
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
up.append(class)
return nil
}
// loads object from extension registry; 1-byte index
func opExt1(up *Unpickler) error {
if up.GetExtension == nil {
err := fmt.Errorf("unsupported extension code encountered")
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
i, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
obj, err := up.GetExtension(int(i))
if err != nil {
err = fmt.Errorf("loadStackGlobal() failed: %w", err)
return err
}
up.append(obj)
return nil
}
// ditto, but 2-byte index
func opExt2(up *Unpickler) error {
if up.GetExtension == nil {
err := fmt.Errorf("unsupported extension code encountered")
err = fmt.Errorf("opExt2() failed: %w", err)
return err
}
buf, err := up.read(2)
if err != nil {
err = fmt.Errorf("opExt2() failed: %w", err)
return err
}
code := int(binary.LittleEndian.Uint16(buf))
obj, err := up.GetExtension(code)
if err != nil {
err = fmt.Errorf("opExt2() failed: %w", err)
return err
}
up.append(obj)
return nil
}
// ditto, but 4-byte index
func opExt4(up *Unpickler) error {
if up.GetExtension == nil {
err := fmt.Errorf("unsupported extension code encountered")
err = fmt.Errorf("opExt4() failed: %w", err)
return err
}
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("opExt4() failed: %w", err)
return err
}
code := int(binary.LittleEndian.Uint32(buf))
obj, err := up.GetExtension(code)
if err != nil {
err = fmt.Errorf("opExt4() failed: %w", err)
return err
}
up.append(obj)
return nil
}
// apply callable to argtuple, both on stack
func loadReduce(up *Unpickler) error {
args, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadReduce() failed: %w", err)
return err
}
argsTuple, argsOk := args.(*Tuple)
if !argsOk {
err := fmt.Errorf("REDUCE args must be *Tuple")
err = fmt.Errorf("loadReduce() failed: %w", err)
return err
}
function, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadReduce() failed: %w", err)
return err
}
callable, callableOk := function.(Callable)
if !callableOk {
err := fmt.Errorf("REDUCE requires a Callable object: %#v", function)
err = fmt.Errorf("loadReduce() failed: %w", err)
return err
}
result, err := callable.Call(*argsTuple...)
if err != nil {
err = fmt.Errorf("loadReduce() failed: %w", err)
return err
}
up.append(result)
return nil
}
// discards topmost stack item
func loadPop(up *Unpickler) error {
if len(up.stack) == 0 {
_, err := up.popMark()
err = fmt.Errorf("loadPop() failed: %w", err)
return err
}
up.stack = up.stack[:len(up.stack)-1]
return nil
}
// discards stack top through topmost markobject
func loadPopMark(up *Unpickler) error {
_, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadPopMark() failed: %w", err)
return err
}
return nil
}
// duplicate top stack item
func loadDup(up *Unpickler) error {
item, err := up.stackLast()
if err != nil {
err = fmt.Errorf("loadDup() failed: %w", err)
return err
}
up.append(item)
return nil
}
// loads object from memo on stack; index is string arg
func loadGet(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadGet() failed: %w", err)
return err
}
i, err := strconv.Atoi(string(line[:len(line)-1]))
if err != nil {
err = fmt.Errorf("loadGet() failed: %w", err)
return err
}
up.append(up.memo[i])
return nil
}
// loads object from memo on stack; index is 1-byte arg
func loadBinGet(up *Unpickler) error {
i, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadBinGet() failed: %w", err)
return err
}
up.append(up.memo[int(i)])
return nil
}
// load object from memo on stack; index is 4-byte arg
func loadLongBinGet(up *Unpickler) error {
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("loadBinGet() failed: %w", err)
return err
}
i := int(binary.LittleEndian.Uint32(buf))
up.append(up.memo[i])
return nil
}
// store stack top in memo; index is string arg
func loadPut(up *Unpickler) error {
line, err := up.readLine()
if err != nil {
err = fmt.Errorf("loadPut() failed: %w", err)
return err
}
i, err := strconv.Atoi(string(line[:len(line)-1]))
if err != nil {
err = fmt.Errorf("loadPut() failed: %w", err)
return err
}
if i < 0 {
err := fmt.Errorf("negative PUT argument")
err = fmt.Errorf("loadPut() failed: %w", err)
return err
}
up.memo[i], err = up.stackLast()
if err != nil {
err = fmt.Errorf("loadPut() failed: %w", err)
return err
}
return nil
}
// store stack top in memo; index is 1-byte arg
func loadBinPut(up *Unpickler) error {
i, err := up.readOne()
if err != nil {
err = fmt.Errorf("loadBinPut() failed: %w", err)
return err
}
up.memo[int(i)], err = up.stackLast()
if err != nil {
err = fmt.Errorf("loadBinPut() failed: %w", err)
}
return nil
}
// stores stack top in memo; index is 4-byte arg
func loadLongBinPut(up *Unpickler) error {
buf, err := up.read(4)
if err != nil {
err = fmt.Errorf("loadLongBinPut() failed: %w", err)
return err
}
i := int(binary.LittleEndian.Uint32(buf))
up.memo[i], err = up.stackLast()
if err != nil {
err = fmt.Errorf("loadLongBinPut() failed: %w", err)
return err
}
return nil
}
// stores top of the stack in memo
func loadMemoize(up *Unpickler) error {
value, err := up.stackLast()
if err != nil {
err = fmt.Errorf("loadMemoize() failed: %w", err)
return err
}
up.memo[len(up.memo)] = value
return nil
}
// appends stack top to list below it
func loadAppend(up *Unpickler) error {
value, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadAppend() failed: %w", err)
return err
}
obj, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadAppend() failed: %w", err)
return err
}
list, listOk := obj.(ListAppender)
if !listOk {
err := fmt.Errorf("APPEND requires ListAppender")
err = fmt.Errorf("loadAppend() failed: %w", err)
return err
}
list.Append(value)
up.append(list)
return nil
}
// extends list on stack by topmost stack slice
func loadAppends(up *Unpickler) error {
items, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadAppends() failed: %w", err)
return err
}
obj, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadAppends() failed: %w", err)
return err
}
list, listOk := obj.(ListAppender)
if !listOk {
err := fmt.Errorf("APPEND requires List")
err = fmt.Errorf("loadAppends() failed: %w", err)
return err
}
for _, item := range items {
list.Append(item)
}
up.append(list)
return nil
}
// adds key+value pair to dict
func loadSetItem(up *Unpickler) error {
value, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadSetItem() failed: %w", err)
return err
}
key, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadSetItem() failed: %w", err)
return err
}
obj, err := up.stackLast()
if err != nil {
err = fmt.Errorf("loadSetItem() failed: %w", err)
return err
}
dict, dictOk := obj.(DictSetter)
if !dictOk {
err := fmt.Errorf("SETITEM requires DictSetter")
err = fmt.Errorf("loadSetItem() failed: %w", err)
return err
}
dict.Set(key, value)
return nil
}
// modifies dict by adding topmost key+value pairs
func loadSetItems(up *Unpickler) error {
items, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadSetItems() failed: %w", err)
return err
}
obj, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadSetItems() failed: %w", err)
return err
}
dict, dictOk := obj.(DictSetter)
if !dictOk {
err := fmt.Errorf("SETITEMS requires DictSetter")
err = fmt.Errorf("loadSetItems() failed: %w", err)
return err
}
itemsLen := len(items)
for i := 0; i < itemsLen; i += 2 {
dict.Set(items[i], items[i+1])
}
up.append(dict)
return nil
}
// modifies set by adding topmost stack items
func loadAddItems(up *Unpickler) error {
items, err := up.popMark()
if err != nil {
err = fmt.Errorf("loadAddItems() failed: %w", err)
return err
}
obj, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadAddItems() failed: %w", err)
return err
}
set, setOk := obj.(SetAdder)
if !setOk {
err := fmt.Errorf("ADDITEMS requires SetAdder")
err = fmt.Errorf("loadAddItems() failed: %w", err)
return err
}
for _, item := range items {
set.Add(item)
}
up.append(set)
return nil
}
// calls __setstate__ or __dict__.update()
func loadBuild(up *Unpickler) error {
state, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
inst, err := up.stackLast()
if err != nil {
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
if obj, ok := inst.(PyStateSettable); ok {
return obj.PySetState(state)
}
var slotState interface{}
if tuple, ok := state.(*Tuple); ok && tuple.Len() == 2 {
state = tuple.Get(0)
slotState = tuple.Get(1)
}
if stateDict, ok := state.(*Dict); ok {
instPds, instPdsOk := inst.(PyDictSettable)
if !instPdsOk {
err := fmt.Errorf("BUILD requires a PyDictSettable instance: %#v", inst)
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
for _, entry := range *stateDict {
err := instPds.PyDictSet(entry.Key, entry.Value)
if err != nil {
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
}
}
if slotStateDict, ok := slotState.(*Dict); ok {
instSa, instOk := inst.(PyAttrSettable)
if !instOk {
err := fmt.Errorf("BUILD requires a PyAttrSettable instance: %#v", inst)
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
for _, entry := range *slotStateDict {
sk, keyOk := entry.Key.(string)
if !keyOk {
err := fmt.Errorf("BUILD requires string slot state keys")
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
err := instSa.PySetAttr(sk, entry.Value)
if err != nil {
err = fmt.Errorf("loadBuild() failed: %w", err)
return err
}
}
}
return nil
}
// loads special markobject on stack
func loadMark(up *Unpickler) error {
up.metaStack = append(up.metaStack, up.stack)
up.stack = make([]interface{}, 0)
return nil
}
// every pickle ends with STOP
func loadStop(up *Unpickler) error {
value, err := up.stackPop()
if err != nil {
err = fmt.Errorf("loadStop() failed: %w", err)
return err
}
return Stop{value: value}
}
// initUnpickleDispatch creates a dispatch table for unpickling machinery.
func initUnpicklerDispatch() {
upDispatch[PROTO] = loadProto
upDispatch[FRAME] = loadFrame
upDispatch[PERSID] = loadPersId
upDispatch[BINPERSID] = loadBinPersId
upDispatch[NONE] = loadNone
upDispatch[NEWFALSE] = loadFalse
upDispatch[NEWTRUE] = loadTrue
upDispatch[INT] = loadInt
upDispatch[BININT] = loadBinInt
upDispatch[BININT1] = loadBinInt1
upDispatch[BININT2] = loadBinInt2
upDispatch[LONG] = loadLong
upDispatch[LONG1] = loadLong1
upDispatch[LONG4] = loadLong4
upDispatch[FLOAT] = loadFloat
upDispatch[BINFLOAT] = loadBinFloat
upDispatch[STRING] = loadString
upDispatch[BINSTRING] = loadBinString
upDispatch[BINBYTES] = loadBinBytes
upDispatch[UNICODE] = loadUnicode
upDispatch[BINUNICODE] = loadBinUnicode
upDispatch[BINUNICODE8] = loadBinUnicode8
upDispatch[BINBYTES8] = loadBinBytes8
upDispatch[BYTEARRAY8] = loadByteArray8
upDispatch[NEXT_BUFFER] = loadNextBuffer
upDispatch[READONLY_BUFFER] = loadReadOnlyBuffer
upDispatch[SHORT_BINSTRING] = loadShortBinString
upDispatch[SHORT_BINBYTES] = loadShortBinBytes
upDispatch[SHORT_BINUNICODE] = loadShortBinUnicode
upDispatch[TUPLE] = loadTuple
upDispatch[EMPTY_TUPLE] = loadEmptyTuple
upDispatch[TUPLE1] = loadTuple1
upDispatch[TUPLE2] = loadTuple2
upDispatch[TUPLE3] = loadTuple3
upDispatch[EMPTY_LIST] = loadEmptyList
upDispatch[EMPTY_DICT] = loadEmptyDict
upDispatch[EMPTY_SET] = loadEmptySet
upDispatch[FROZENSET] = loadFrozenSet
upDispatch[LIST] = loadList
upDispatch[DICT] = loadDict
upDispatch[INST] = loadInst
upDispatch[OBJ] = loadObj
upDispatch[NEWOBJ] = loadNewObj
upDispatch[NEWOBJ_EX] = loadNewObjEx
upDispatch[GLOBAL] = loadGlobal
upDispatch[STACK_GLOBAL] = loadStackGlobal
upDispatch[EXT1] = opExt1
upDispatch[EXT2] = opExt2
upDispatch[EXT4] = opExt4
upDispatch[REDUCE] = loadReduce
upDispatch[POP] = loadPop
upDispatch[POP_MARK] = loadPopMark
upDispatch[DUP] = loadDup
upDispatch[GET] = loadGet
upDispatch[BINGET] = loadBinGet
upDispatch[LONG_BINGET] = loadLongBinGet
upDispatch[PUT] = loadPut
upDispatch[BINPUT] = loadBinPut
upDispatch[LONG_BINPUT] = loadLongBinPut
upDispatch[MEMOIZE] = loadMemoize
upDispatch[APPEND] = loadAppend
upDispatch[APPENDS] = loadAppends
upDispatch[SETITEM] = loadSetItem
upDispatch[SETITEMS] = loadSetItems
upDispatch[ADDITEMS] = loadAddItems
upDispatch[BUILD] = loadBuild
upDispatch[MARK] = loadMark
upDispatch[STOP] = loadStop
}
func decodeInt32(buf []byte) int {
uval := binary.LittleEndian.Uint32(buf)
val := int(uval)
if buf[3]&0x80 != 0 {
val = -(int(^uval) + 1)
}
return val
}
func decodeLong(data []byte) interface{} {
if len(data) == 0 {
return nil
}
// determine whether most-significant bit (MSB) is set
isMsbSet := data[len(data)-1]&0x80 != 0
if len(data) > 8 {
bInt := new(big.Int)
for i := len(data) - 1; i >= 0; i-- {
bInt = bInt.Lsh(bInt, 8) // left shift 8 bits
if isMsbSet {
bInt = bInt.Or(bInt, big.NewInt(int64(^data[i])))
} else {
bInt = bInt.Or(bInt, big.NewInt(int64(data[i])))
}
} // for
if isMsbSet {
bInt = bInt.Add(bInt, big.NewInt(1))
bInt = bInt.Neg(bInt)
}
return bInt
} // if
var val, bitMask uint64
for i := len(data) - 1; i >= 0; i-- {
val = (val << 8) | uint64(data[i])
bitMask = (bitMask << 8) | 0xFF
}
if isMsbSet {
return -(int(^val & bitMask))
}
return int(val)
}
func init() {
initUnpicklerDispatch()
}
// Load decodes objects by loading through unpickling machinery.
func (up *Unpickler) Load() (interface{}, error) {
up.metaStack = make([][]interface{}, 0)
up.stack = make([]interface{}, 0)
up.proto = 0
for {
opcode, err := up.readOne()
if err != nil {
return nil, err
}
opFunc := upDispatch[opcode]
if opFunc == nil {
err := fmt.Errorf("Unpickler.Load() failed:unknown opcode: 0x%x '%c'", opcode, opcode)
return nil, err
}
err = opFunc(up)
if err != nil {
if p, ok := err.(Stop); ok {
return p.value, nil
}
err := fmt.Errorf("Unpickler.Load() failed: %w", err)
return nil, err
}
}
}
// Load unpickles a pickled file.
func Load(filename string) (interface{}, error) {
f, err := os.Open(filename)
if err != nil {
err := fmt.Errorf("Load() failed: %w", err)
return nil, err
}
defer f.Close()
up := NewUnpickler(f)
return up.Load()
}
// Loads unpicles a string.
func Loads(s string) (interface{}, error) {
sr := strings.NewReader(s)
up := NewUnpickler(sr)
return up.Load()
}
func GetFunctionName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}