python-pickle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +27 -0
  4. data/.gitignore +5 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +14 -0
  8. data/Gemfile +15 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +149 -0
  11. data/Rakefile +13 -0
  12. data/gemspec.yml +25 -0
  13. data/lib/python/pickle/byte_array.rb +40 -0
  14. data/lib/python/pickle/deserializer.rb +595 -0
  15. data/lib/python/pickle/exceptions.rb +12 -0
  16. data/lib/python/pickle/instruction.rb +52 -0
  17. data/lib/python/pickle/instructions/add_items.rb +26 -0
  18. data/lib/python/pickle/instructions/append.rb +24 -0
  19. data/lib/python/pickle/instructions/appends.rb +26 -0
  20. data/lib/python/pickle/instructions/bin_bytes.rb +32 -0
  21. data/lib/python/pickle/instructions/bin_bytes8.rb +32 -0
  22. data/lib/python/pickle/instructions/bin_float.rb +29 -0
  23. data/lib/python/pickle/instructions/bin_get.rb +27 -0
  24. data/lib/python/pickle/instructions/bin_int1.rb +29 -0
  25. data/lib/python/pickle/instructions/bin_put.rb +29 -0
  26. data/lib/python/pickle/instructions/bin_string.rb +32 -0
  27. data/lib/python/pickle/instructions/bin_unicode.rb +32 -0
  28. data/lib/python/pickle/instructions/bin_unicode8.rb +32 -0
  29. data/lib/python/pickle/instructions/build.rb +24 -0
  30. data/lib/python/pickle/instructions/byte_array8.rb +32 -0
  31. data/lib/python/pickle/instructions/dict.rb +17 -0
  32. data/lib/python/pickle/instructions/dup.rb +24 -0
  33. data/lib/python/pickle/instructions/empty_dict.rb +26 -0
  34. data/lib/python/pickle/instructions/empty_list.rb +26 -0
  35. data/lib/python/pickle/instructions/empty_set.rb +26 -0
  36. data/lib/python/pickle/instructions/empty_tuple.rb +26 -0
  37. data/lib/python/pickle/instructions/ext1.rb +29 -0
  38. data/lib/python/pickle/instructions/ext2.rb +29 -0
  39. data/lib/python/pickle/instructions/ext4.rb +29 -0
  40. data/lib/python/pickle/instructions/float.rb +24 -0
  41. data/lib/python/pickle/instructions/frame.rb +29 -0
  42. data/lib/python/pickle/instructions/frozen_set.rb +26 -0
  43. data/lib/python/pickle/instructions/get.rb +27 -0
  44. data/lib/python/pickle/instructions/global.rb +62 -0
  45. data/lib/python/pickle/instructions/has_length_and_value.rb +58 -0
  46. data/lib/python/pickle/instructions/has_value.rb +50 -0
  47. data/lib/python/pickle/instructions/int.rb +24 -0
  48. data/lib/python/pickle/instructions/list.rb +24 -0
  49. data/lib/python/pickle/instructions/long.rb +24 -0
  50. data/lib/python/pickle/instructions/long1.rb +32 -0
  51. data/lib/python/pickle/instructions/long4.rb +32 -0
  52. data/lib/python/pickle/instructions/long_bin_get.rb +27 -0
  53. data/lib/python/pickle/instructions/mark.rb +24 -0
  54. data/lib/python/pickle/instructions/memoize.rb +26 -0
  55. data/lib/python/pickle/instructions/new_false.rb +24 -0
  56. data/lib/python/pickle/instructions/new_obj.rb +26 -0
  57. data/lib/python/pickle/instructions/new_obj_ex.rb +26 -0
  58. data/lib/python/pickle/instructions/new_true.rb +24 -0
  59. data/lib/python/pickle/instructions/next_buffer.rb +26 -0
  60. data/lib/python/pickle/instructions/none.rb +24 -0
  61. data/lib/python/pickle/instructions/pop.rb +24 -0
  62. data/lib/python/pickle/instructions/pop_mark.rb +24 -0
  63. data/lib/python/pickle/instructions/proto.rb +29 -0
  64. data/lib/python/pickle/instructions/put.rb +24 -0
  65. data/lib/python/pickle/instructions/readonly_buffer.rb +26 -0
  66. data/lib/python/pickle/instructions/reduce.rb +24 -0
  67. data/lib/python/pickle/instructions/set_item.rb +24 -0
  68. data/lib/python/pickle/instructions/set_items.rb +26 -0
  69. data/lib/python/pickle/instructions/short_bin_bytes.rb +32 -0
  70. data/lib/python/pickle/instructions/short_bin_string.rb +32 -0
  71. data/lib/python/pickle/instructions/short_bin_unicode.rb +32 -0
  72. data/lib/python/pickle/instructions/stack_global.rb +26 -0
  73. data/lib/python/pickle/instructions/stop.rb +24 -0
  74. data/lib/python/pickle/instructions/string.rb +24 -0
  75. data/lib/python/pickle/instructions/tuple.rb +24 -0
  76. data/lib/python/pickle/instructions/tuple1.rb +24 -0
  77. data/lib/python/pickle/instructions/tuple2.rb +24 -0
  78. data/lib/python/pickle/instructions/tuple3.rb +24 -0
  79. data/lib/python/pickle/protocol.rb +56 -0
  80. data/lib/python/pickle/protocol0.rb +399 -0
  81. data/lib/python/pickle/protocol1.rb +183 -0
  82. data/lib/python/pickle/protocol2.rb +229 -0
  83. data/lib/python/pickle/protocol3.rb +163 -0
  84. data/lib/python/pickle/protocol4.rb +285 -0
  85. data/lib/python/pickle/protocol5.rb +218 -0
  86. data/lib/python/pickle/py_class.rb +75 -0
  87. data/lib/python/pickle/py_object.rb +141 -0
  88. data/lib/python/pickle/tuple.rb +19 -0
  89. data/lib/python/pickle/version.rb +6 -0
  90. data/lib/python/pickle.rb +226 -0
  91. data/python-pickle.gemspec +62 -0
  92. data/spec/byte_array_spec.rb +54 -0
  93. data/spec/deserializer_spec.rb +1201 -0
  94. data/spec/fixtures/ascii_str_v3.pkl +0 -0
  95. data/spec/fixtures/ascii_str_v4.pkl +0 -0
  96. data/spec/fixtures/ascii_str_v5.pkl +0 -0
  97. data/spec/fixtures/bin_str_v0.pkl +3 -0
  98. data/spec/fixtures/bin_str_v1.pkl +0 -0
  99. data/spec/fixtures/bin_str_v2.pkl +0 -0
  100. data/spec/fixtures/bin_str_v3.pkl +0 -0
  101. data/spec/fixtures/bin_str_v4.pkl +0 -0
  102. data/spec/fixtures/bin_str_v5.pkl +0 -0
  103. data/spec/fixtures/bytearray_v0.pkl +10 -0
  104. data/spec/fixtures/bytearray_v1.pkl +0 -0
  105. data/spec/fixtures/bytearray_v2.pkl +0 -0
  106. data/spec/fixtures/bytearray_v3.pkl +0 -0
  107. data/spec/fixtures/bytearray_v4.pkl +0 -0
  108. data/spec/fixtures/bytearray_v5.pkl +0 -0
  109. data/spec/fixtures/class_v0.pkl +4 -0
  110. data/spec/fixtures/class_v1.pkl +0 -0
  111. data/spec/fixtures/class_v2.pkl +0 -0
  112. data/spec/fixtures/class_v3.pkl +0 -0
  113. data/spec/fixtures/class_v4.pkl +0 -0
  114. data/spec/fixtures/class_v5.pkl +0 -0
  115. data/spec/fixtures/dict_v0.pkl +6 -0
  116. data/spec/fixtures/dict_v1.pkl +0 -0
  117. data/spec/fixtures/dict_v2.pkl +0 -0
  118. data/spec/fixtures/dict_v3.pkl +0 -0
  119. data/spec/fixtures/dict_v4.pkl +0 -0
  120. data/spec/fixtures/dict_v5.pkl +0 -0
  121. data/spec/fixtures/escaped_str_v0.pkl +3 -0
  122. data/spec/fixtures/escaped_str_v1.pkl +0 -0
  123. data/spec/fixtures/escaped_str_v2.pkl +0 -0
  124. data/spec/fixtures/false_v0.pkl +2 -0
  125. data/spec/fixtures/false_v1.pkl +2 -0
  126. data/spec/fixtures/false_v2.pkl +1 -0
  127. data/spec/fixtures/false_v3.pkl +1 -0
  128. data/spec/fixtures/false_v4.pkl +1 -0
  129. data/spec/fixtures/false_v5.pkl +1 -0
  130. data/spec/fixtures/float_v0.pkl +2 -0
  131. data/spec/fixtures/float_v1.pkl +1 -0
  132. data/spec/fixtures/float_v2.pkl +1 -0
  133. data/spec/fixtures/float_v3.pkl +1 -0
  134. data/spec/fixtures/float_v4.pkl +0 -0
  135. data/spec/fixtures/float_v5.pkl +0 -0
  136. data/spec/fixtures/function_v0.pkl +4 -0
  137. data/spec/fixtures/function_v1.pkl +0 -0
  138. data/spec/fixtures/function_v2.pkl +0 -0
  139. data/spec/fixtures/function_v3.pkl +0 -0
  140. data/spec/fixtures/function_v4.pkl +0 -0
  141. data/spec/fixtures/function_v5.pkl +0 -0
  142. data/spec/fixtures/hex_str_v0.pkl +3 -0
  143. data/spec/fixtures/hex_str_v1.pkl +0 -0
  144. data/spec/fixtures/hex_str_v2.pkl +0 -0
  145. data/spec/fixtures/int_v0.pkl +2 -0
  146. data/spec/fixtures/int_v1.pkl +1 -0
  147. data/spec/fixtures/int_v2.pkl +1 -0
  148. data/spec/fixtures/int_v3.pkl +1 -0
  149. data/spec/fixtures/int_v4.pkl +1 -0
  150. data/spec/fixtures/int_v5.pkl +1 -0
  151. data/spec/fixtures/list_v0.pkl +7 -0
  152. data/spec/fixtures/list_v1.pkl +0 -0
  153. data/spec/fixtures/list_v2.pkl +0 -0
  154. data/spec/fixtures/list_v3.pkl +0 -0
  155. data/spec/fixtures/list_v4.pkl +0 -0
  156. data/spec/fixtures/list_v5.pkl +0 -0
  157. data/spec/fixtures/long_v0.pkl +2 -0
  158. data/spec/fixtures/long_v1.pkl +2 -0
  159. data/spec/fixtures/long_v2.pkl +0 -0
  160. data/spec/fixtures/long_v3.pkl +0 -0
  161. data/spec/fixtures/long_v4.pkl +0 -0
  162. data/spec/fixtures/long_v5.pkl +0 -0
  163. data/spec/fixtures/nested_dict_v0.pkl +12 -0
  164. data/spec/fixtures/nested_dict_v1.pkl +0 -0
  165. data/spec/fixtures/nested_dict_v2.pkl +0 -0
  166. data/spec/fixtures/nested_dict_v3.pkl +0 -0
  167. data/spec/fixtures/nested_dict_v4.pkl +0 -0
  168. data/spec/fixtures/nested_dict_v5.pkl +0 -0
  169. data/spec/fixtures/nested_list_v0.pkl +9 -0
  170. data/spec/fixtures/nested_list_v1.pkl +0 -0
  171. data/spec/fixtures/nested_list_v2.pkl +0 -0
  172. data/spec/fixtures/nested_list_v3.pkl +0 -0
  173. data/spec/fixtures/nested_list_v4.pkl +0 -0
  174. data/spec/fixtures/nested_list_v5.pkl +0 -0
  175. data/spec/fixtures/none_v0.pkl +1 -0
  176. data/spec/fixtures/none_v1.pkl +1 -0
  177. data/spec/fixtures/none_v2.pkl +1 -0
  178. data/spec/fixtures/none_v3.pkl +1 -0
  179. data/spec/fixtures/none_v4.pkl +1 -0
  180. data/spec/fixtures/none_v5.pkl +1 -0
  181. data/spec/fixtures/object_v0.pkl +19 -0
  182. data/spec/fixtures/object_v1.pkl +0 -0
  183. data/spec/fixtures/object_v2.pkl +0 -0
  184. data/spec/fixtures/object_v3.pkl +0 -0
  185. data/spec/fixtures/object_v4.pkl +0 -0
  186. data/spec/fixtures/object_v5.pkl +0 -0
  187. data/spec/fixtures/str_v0.pkl +3 -0
  188. data/spec/fixtures/str_v1.pkl +0 -0
  189. data/spec/fixtures/str_v2.pkl +0 -0
  190. data/spec/fixtures/str_v3.pkl +0 -0
  191. data/spec/fixtures/str_v4.pkl +0 -0
  192. data/spec/fixtures/str_v5.pkl +0 -0
  193. data/spec/fixtures/true_v0.pkl +2 -0
  194. data/spec/fixtures/true_v1.pkl +2 -0
  195. data/spec/fixtures/true_v2.pkl +1 -0
  196. data/spec/fixtures/true_v3.pkl +1 -0
  197. data/spec/fixtures/true_v4.pkl +1 -0
  198. data/spec/fixtures/true_v5.pkl +1 -0
  199. data/spec/fixtures/unicode_str_v0.pkl +3 -0
  200. data/spec/fixtures/unicode_str_v1.pkl +0 -0
  201. data/spec/fixtures/unicode_str_v2.pkl +0 -0
  202. data/spec/fixtures/unicode_str_v3.pkl +0 -0
  203. data/spec/fixtures/unicode_str_v4.pkl +0 -0
  204. data/spec/fixtures/unicode_str_v5.pkl +0 -0
  205. data/spec/generate_pickles2.py +41 -0
  206. data/spec/generate_pickles3.py +40 -0
  207. data/spec/integration/load/protocol0_spec.rb +258 -0
  208. data/spec/integration/load/protocol1_spec.rb +258 -0
  209. data/spec/integration/load/protocol2_spec.rb +258 -0
  210. data/spec/integration/load/protocol3_spec.rb +258 -0
  211. data/spec/integration/load/protocol4_spec.rb +258 -0
  212. data/spec/integration/load/protocol5_spec.rb +258 -0
  213. data/spec/integration/parse/protocol0_spec.rb +467 -0
  214. data/spec/integration/parse/protocol1_spec.rb +459 -0
  215. data/spec/integration/parse/protocol2_spec.rb +471 -0
  216. data/spec/integration/parse/protocol3_spec.rb +407 -0
  217. data/spec/integration/parse/protocol4_spec.rb +439 -0
  218. data/spec/integration/parse/protocol5_spec.rb +419 -0
  219. data/spec/pickle_spec.rb +163 -0
  220. data/spec/protocol0_read_instruction_examples.rb +211 -0
  221. data/spec/protocol0_spec.rb +445 -0
  222. data/spec/protocol1_read_instruction_examples.rb +156 -0
  223. data/spec/protocol1_spec.rb +59 -0
  224. data/spec/protocol2_read_instruction_examples.rb +135 -0
  225. data/spec/protocol2_spec.rb +128 -0
  226. data/spec/protocol3_read_instruction_examples.rb +29 -0
  227. data/spec/protocol3_spec.rb +32 -0
  228. data/spec/protocol4_read_instruction_examples.rb +142 -0
  229. data/spec/protocol4_spec.rb +58 -0
  230. data/spec/protocol5_spec.rb +68 -0
  231. data/spec/py_class_spec.rb +62 -0
  232. data/spec/py_object_spec.rb +149 -0
  233. data/spec/spec_helper.rb +3 -0
  234. data/spec/tuple_spec.rb +18 -0
  235. metadata +325 -0
@@ -0,0 +1,595 @@
1
+ require 'python/pickle/py_class'
2
+ require 'python/pickle/py_object'
3
+ require 'python/pickle/tuple'
4
+ require 'python/pickle/byte_array'
5
+ require 'python/pickle/exceptions'
6
+
7
+ require 'python/pickle/instructions/proto'
8
+ require 'python/pickle/instructions/frame'
9
+ require 'python/pickle/instructions/get'
10
+ require 'python/pickle/instructions/bin_get'
11
+ require 'python/pickle/instructions/long_bin_get'
12
+ require 'python/pickle/instructions/mark'
13
+ require 'python/pickle/instructions/pop_mark'
14
+ require 'python/pickle/instructions/dup'
15
+ require 'python/pickle/instructions/put'
16
+ require 'python/pickle/instructions/bin_put'
17
+ require 'python/pickle/instructions/pop'
18
+ require 'python/pickle/instructions/memoize'
19
+ require 'python/pickle/instructions/ext1'
20
+ require 'python/pickle/instructions/ext2'
21
+ require 'python/pickle/instructions/ext4'
22
+ require 'python/pickle/instructions/none'
23
+ require 'python/pickle/instructions/new_true'
24
+ require 'python/pickle/instructions/new_false'
25
+ require 'python/pickle/instructions/float'
26
+ require 'python/pickle/instructions/bin_float'
27
+ require 'python/pickle/instructions/int'
28
+ require 'python/pickle/instructions/bin_int1'
29
+ require 'python/pickle/instructions/long'
30
+ require 'python/pickle/instructions/long1'
31
+ require 'python/pickle/instructions/long4'
32
+ require 'python/pickle/instructions/bin_bytes'
33
+ require 'python/pickle/instructions/short_bin_bytes'
34
+ require 'python/pickle/instructions/bin_bytes8'
35
+ require 'python/pickle/instructions/string'
36
+ require 'python/pickle/instructions/bin_string'
37
+ require 'python/pickle/instructions/short_bin_string'
38
+ require 'python/pickle/instructions/bin_unicode'
39
+ require 'python/pickle/instructions/short_bin_unicode'
40
+ require 'python/pickle/instructions/bin_unicode8'
41
+ require 'python/pickle/instructions/byte_array8'
42
+ require 'python/pickle/instructions/empty_list'
43
+ require 'python/pickle/instructions/empty_tuple'
44
+ require 'python/pickle/instructions/tuple'
45
+ require 'python/pickle/instructions/empty_dict'
46
+ require 'python/pickle/instructions/append'
47
+ require 'python/pickle/instructions/appends'
48
+ require 'python/pickle/instructions/list'
49
+ require 'python/pickle/instructions/tuple1'
50
+ require 'python/pickle/instructions/tuple2'
51
+ require 'python/pickle/instructions/tuple3'
52
+ require 'python/pickle/instructions/dict'
53
+ require 'python/pickle/instructions/global'
54
+ require 'python/pickle/instructions/stack_global'
55
+ require 'python/pickle/instructions/new_obj'
56
+ require 'python/pickle/instructions/new_obj_ex'
57
+ require 'python/pickle/instructions/reduce'
58
+ require 'python/pickle/instructions/build'
59
+ require 'python/pickle/instructions/set_item'
60
+ require 'python/pickle/instructions/set_items'
61
+ require 'python/pickle/instructions/stop'
62
+
63
+ module Python
64
+ module Pickle
65
+ #
66
+ # Handles deserializing a stream of Python Pickle instructions.
67
+ #
68
+ # @api private
69
+ #
70
+ class Deserializer
71
+
72
+ # The meta-stack for saving/restoring {#stack}.
73
+ #
74
+ # @return [Array]
75
+ attr_reader :meta_stack
76
+
77
+ # The object stack.
78
+ #
79
+ # @return [Array]
80
+ attr_reader :stack
81
+
82
+ # The memo dictionary.
83
+ #
84
+ # @return [Array]
85
+ attr_reader :memo
86
+
87
+ # Mapping of Python constants to Ruby classes and methods.
88
+ #
89
+ # @return [Hash{String => Hash{String => Class,Method}}]
90
+ attr_reader :constants
91
+
92
+ # Mapping of Python Pickle extension codes to Ruby objects.
93
+ #
94
+ # @return [Hash{Integer => Object}]
95
+ attr_reader :extensions
96
+
97
+ # The Python `object` class.
98
+ OBJECT_CLASS = PyClass.new('__builtins__','object')
99
+
100
+ #
101
+ # Initializes the deserializer.
102
+ #
103
+ # @param [Hash{Integer => Object}] extensions
104
+ # A Hash of registered extension IDs and their Objects.
105
+ #
106
+ # @param [Hash{String => Hash{String => Class,Method}}] constants
107
+ # An optional mapping of custom Python constant names to Ruby classes
108
+ # or methods.
109
+ #
110
+ def initialize(constants: nil, extensions: nil)
111
+ @meta_stack = []
112
+ @stack = []
113
+ @memo = []
114
+
115
+ @constants = {
116
+ # Python 2.x
117
+ 'copy_reg' => {
118
+ '_reconstructor' => method(:copyreg_reconstructor)
119
+ },
120
+
121
+ '__builtin__' => {
122
+ 'object' => OBJECT_CLASS,
123
+ 'bytearray' => ByteArray
124
+ },
125
+
126
+ # Python 3.x
127
+ 'builtins' => {
128
+ 'object' => OBJECT_CLASS,
129
+ 'bytearray' => ByteArray
130
+ }
131
+ }
132
+ @constants.merge!(constants) if constants
133
+
134
+ @extensions = {}
135
+ @extensions.merge!(extensions) if extensions
136
+ end
137
+
138
+ #
139
+ # Pushes the {#stack} onto the {#meta_stack}.
140
+ #
141
+ def push_meta_stack
142
+ @meta_stack.push(@stack)
143
+ @stack = []
144
+ end
145
+
146
+ #
147
+ # Pops a previous stack off of {#meta_stack} and restores {#stack}.
148
+ #
149
+ # @return [Array]
150
+ # The current {#stack} values will be returned.
151
+ #
152
+ def pop_meta_stack
153
+ items = @stack
154
+ @stack = (@meta_stack.pop || [])
155
+ return items
156
+ end
157
+
158
+ #
159
+ # Executes a Python Pickle instruction.
160
+ #
161
+ def execute(instruction)
162
+ case instruction
163
+ when Instructions::Proto,
164
+ Instructions::Frame
165
+ # no-op
166
+ when Instructions::Get,
167
+ Instructions::BinGet,
168
+ Instructions::LongBinGet
169
+ execute_get(instruction)
170
+ when Instructions::MARK then execute_mark
171
+ when Instructions::POP_MARK then execute_pop_mark
172
+ when Instructions::DUP then execute_dup
173
+ when Instructions::Put,
174
+ Instructions::BinPut
175
+ execute_put(instruction)
176
+ when Instructions::POP then execute_pop
177
+ when Instructions::MEMOIZE then execute_memoize
178
+ when Instructions::Ext1,
179
+ Instructions::Ext2,
180
+ Instructions::Ext4
181
+ execute_ext(instruction)
182
+ when Instructions::NONE then execute_none
183
+ when Instructions::NEWTRUE then execute_newtrue
184
+ when Instructions::NEWFALSE then execute_newfalse
185
+ when Instructions::Float,
186
+ Instructions::BinFloat,
187
+ Instructions::Int,
188
+ Instructions::BinInt1,
189
+ Instructions::Long,
190
+ Instructions::Long1,
191
+ Instructions::Long4,
192
+ Instructions::BinBytes,
193
+ Instructions::ShortBinBytes,
194
+ Instructions::BinBytes8,
195
+ Instructions::String,
196
+ Instructions::BinString,
197
+ Instructions::ShortBinString,
198
+ Instructions::BinUnicode,
199
+ Instructions::ShortBinUnicode,
200
+ Instructions::BinUnicode8
201
+ @stack.push(instruction.value)
202
+ when Instructions::ByteArray8 then execute_byte_array8(instruction)
203
+ when Instructions::EMPTY_LIST then execute_empty_list
204
+ when Instructions::EMPTY_TUPLE then execute_empty_tuple
205
+ when Instructions::TUPLE then execute_tuple
206
+ when Instructions::EMPTY_DICT then execute_empty_dict
207
+ when Instructions::APPEND then execute_append
208
+ when Instructions::APPENDS then execute_appends
209
+ when Instructions::LIST then execute_list
210
+ when Instructions::TUPLE1 then execute_tuple1
211
+ when Instructions::TUPLE2 then execute_tuple2
212
+ when Instructions::TUPLE3 then execute_tuple3
213
+ when Instructions::DICT then execute_dict
214
+ when Instructions::Global then execute_global(instruction)
215
+ when Instructions::STACK_GLOBAL then execute_stack_global
216
+ when Instructions::NEWOBJ then execute_newobj
217
+ when Instructions::NEWOBJ_EX then execute_newobj_ex
218
+ when Instructions::REDUCE then execute_reduce
219
+ when Instructions::BUILD then execute_build
220
+ when Instructions::SETITEM then execute_setitem
221
+ when Instructions::SETITEMS then execute_setitems
222
+ when Instructions::STOP
223
+ return :halt, @stack.pop
224
+ else
225
+ raise(NotImplementedError,"instruction is currently not fully supported: #{instruction.inspect}")
226
+ end
227
+ end
228
+
229
+ #
230
+ # Executes a `GET`, `BINGET`, or `LONG_BINGET` instruction.
231
+ #
232
+ # @param [Instructions::Get, Instructions::BinGet, Instructions::LongBinGet] instructions
233
+ # The `GET`, `BINGET`, or `LONG_BINGET` instruction.
234
+ #
235
+ def execute_get(instruction)
236
+ index = instruction.value
237
+
238
+ @stack.push(@memo[index])
239
+ end
240
+
241
+ #
242
+ # Executes a `MARK` instruction.
243
+ #
244
+ def execute_mark
245
+ push_meta_stack
246
+ end
247
+
248
+ #
249
+ # Executes a `POP_MARK` instruction.
250
+ #
251
+ def execute_pop_mark
252
+ pop_meta_stack
253
+ end
254
+
255
+ #
256
+ # Executes a `DUP` instruction.
257
+ #
258
+ def execute_dup
259
+ @stack.push(@stack.last)
260
+ end
261
+
262
+ #
263
+ # Executes a `PUT`, `BINPUT`, or `LONG_BINPUT` instruction.
264
+ #
265
+ # @param [Instructions::Get, Instructions::BinGet, Instructions::LongBinGet] instructions
266
+ # The `PUT`, `BINPUT`, or `LONG_BINPUT` instruction.
267
+ #
268
+ def execute_put(instruction)
269
+ index = instruction.value
270
+ value = @stack.last
271
+
272
+ @memo[index] = value
273
+ end
274
+
275
+ #
276
+ # Executes the `POP` instruction.
277
+ #
278
+ def execute_pop
279
+ @stack.pop
280
+ end
281
+
282
+ #
283
+ # Executes the `MEMOIZE` instruction.
284
+ #
285
+ def execute_memoize
286
+ @memo.push(@stack.last)
287
+ end
288
+
289
+ #
290
+ # Executes a `EXT1`, `EXT2`, or `EXT4` instruction.
291
+ #
292
+ # @param [Instructions::Ext1, Instructions::Ext2, Instructions::Ext4] instruction
293
+ # The `EXT1`, `EXT2`, or `EXT4` instruction.
294
+ #
295
+ # @raise [DeserializationError]
296
+ # The execution ID was not found in {#extensions}.
297
+ #
298
+ def execute_ext(instruction)
299
+ ext_id = instruction.value
300
+ object = @extensions.fetch(ext_id) do
301
+ raise(DeserializationError,"unknown extension ID: #{ext_id.inspect}")
302
+ end
303
+
304
+ @stack.push(object)
305
+ end
306
+
307
+ #
308
+ # Executes a `NONE` instruction.
309
+ #
310
+ def execute_none
311
+ @stack.push(nil)
312
+ end
313
+
314
+ #
315
+ # Executes a `NEWTRUE` instruction.
316
+ #
317
+ def execute_newtrue
318
+ @stack.push(true)
319
+ end
320
+
321
+ #
322
+ # Executes a `NEWFALSE` instruction.
323
+ #
324
+ def execute_newfalse
325
+ @stack.push(false)
326
+ end
327
+
328
+ #
329
+ # Executes a `BYTEARRAY8` instruction.
330
+ #
331
+ def execute_byte_array8(instruction)
332
+ @stack.push(ByteArray.new(instruction.value))
333
+ end
334
+
335
+ #
336
+ # Executes the `EMPTY_LIST` instruction.
337
+ #
338
+ def execute_empty_list
339
+ @stack.push([])
340
+ end
341
+
342
+ #
343
+ # Executes the `EMPTY_TUPLE` instruction.
344
+ #
345
+ def execute_empty_tuple
346
+ @stack.push(Tuple.new)
347
+ end
348
+
349
+ #
350
+ # Executes a `TUPLE` instruction.
351
+ #
352
+ def execute_tuple
353
+ items = Tuple.new(pop_meta_stack)
354
+ @stack.push(items)
355
+ end
356
+
357
+ #
358
+ # Executes an `EMPTY_DICT` instruction.
359
+ #
360
+ def execute_empty_dict
361
+ @stack.push({})
362
+ end
363
+
364
+ #
365
+ # Executes an `APPEND` instruction.
366
+ #
367
+ def execute_append
368
+ item = @stack.pop
369
+ list = @stack.last
370
+
371
+ unless list.kind_of?(Array)
372
+ raise(DeserializationError,"cannot append element #{item.inspect} onto a non-Array: #{list.inspect}")
373
+ end
374
+
375
+ list.push(item)
376
+ end
377
+
378
+ #
379
+ # Executes an `APPENDS` instruction.
380
+ #
381
+ def execute_appends
382
+ items = pop_meta_stack
383
+ list = @stack.last
384
+
385
+ unless list.kind_of?(Array)
386
+ raise(DeserializationError,"cannot append elements #{items.inspect} onto a non-Array: #{list.inspect}")
387
+ end
388
+
389
+ list.concat(items)
390
+ end
391
+
392
+ #
393
+ # Executes a `LIST` instruction.
394
+ #
395
+ def execute_list
396
+ elements = pop_meta_stack
397
+ @stack.push(elements)
398
+ end
399
+
400
+ #
401
+ # Executes a `TUPLE1` instruction.
402
+ #
403
+ def execute_tuple1
404
+ new_tuple = Tuple.new(@stack.pop(1))
405
+
406
+ @stack.push(new_tuple)
407
+ end
408
+
409
+ #
410
+ # Executes a `TUPLE2` instruction.
411
+ #
412
+ def execute_tuple2
413
+ new_tuple = Tuple.new(@stack.pop(2))
414
+
415
+ @stack.push(new_tuple)
416
+ end
417
+
418
+ #
419
+ # Executes a `TUPLE3` instruction.
420
+ #
421
+ def execute_tuple3
422
+ new_tuple = Tuple.new(@stack.pop(3))
423
+
424
+ @stack.push(new_tuple)
425
+ end
426
+
427
+ #
428
+ # Executes a `DICT` instruction.
429
+ #
430
+ def execute_dict
431
+ pairs = pop_meta_stack
432
+ new_dict = {}
433
+
434
+ until pairs.empty?
435
+ key, value = pairs.pop(2)
436
+ new_dict[key] = value
437
+ end
438
+
439
+ @stack.push(new_dict)
440
+ end
441
+
442
+ #
443
+ # Implements Python's `copyreg._reconstructor` function for Python Pickle
444
+ # protocol 0 compatibility.
445
+ #
446
+ # @param [PyClass, Class] class
447
+ # The Python or Ruby class to be initialized.
448
+ #
449
+ # @param [PyClass] super_class
450
+ # The Python super-class of the class.
451
+ #
452
+ # @param [Array, nil] init_arg
453
+ # The argument(s) that will be passed to the class'es `new` method.
454
+ #
455
+ def copyreg_reconstructor(klass,super_class,init_arg)
456
+ klass.new(*init_arg)
457
+ end
458
+
459
+ #
460
+ # Resolves a constant that exists in a Python namespace.
461
+ #
462
+ # @param [String] namespace
463
+ # The namespace name.
464
+ #
465
+ # @param [String] name
466
+ # The name of the constant within the namespace.
467
+ #
468
+ # @return [Class, PyClass, Method, nil]
469
+ # The resolved class or method.
470
+ #
471
+ def resolve_constant(namespace,name)
472
+ constant = if (mod = @constants[namespace])
473
+ mod[name]
474
+ end
475
+
476
+ return constant || PyClass.new(namespace,name)
477
+ end
478
+
479
+ #
480
+ # Executes a `GLOBAL` instruction.
481
+ #
482
+ # @param [Instructions::Global] instruction
483
+ # The `GLOBAL` instruction.
484
+ #
485
+ def execute_global(instruction)
486
+ namespace = instruction.namespace
487
+ name = instruction.name
488
+ constant = resolve_constant(namespace,name)
489
+
490
+ @stack.push(constant)
491
+ end
492
+
493
+ #
494
+ # Executes a `STACK_GLOBAL` instruction.
495
+ #
496
+ def execute_stack_global
497
+ namespace, name = @stack.pop(2)
498
+ constant = resolve_constant(namespace,name)
499
+
500
+ @stack.push(constant)
501
+ end
502
+
503
+ #
504
+ # Executes a `NEWOBJ` instruction.
505
+ #
506
+ def execute_newobj
507
+ py_class, args = @stack.pop(2)
508
+ py_object = py_class.new(*args)
509
+
510
+ @stack.push(py_object)
511
+ end
512
+
513
+ #
514
+ # Executes a `NEWOBJ_EX` instruction.
515
+ #
516
+ def execute_newobj_ex
517
+ py_class, args, kwargs = @stack.pop(3)
518
+ py_object = if kwargs
519
+ kwargs = kwargs.transform_keys(&:to_sym)
520
+
521
+ py_class.new(*args,**kwargs)
522
+ else
523
+ py_class.new(*args)
524
+ end
525
+
526
+ @stack.push(py_object)
527
+ end
528
+
529
+ #
530
+ # Executes a `REDUCE` instruction.
531
+ #
532
+ def execute_reduce
533
+ callable, arg = @stack.pop(2)
534
+ object = case callable
535
+ when PyClass, Class
536
+ callable.new(*arg)
537
+ when Method
538
+ callable.call(*arg)
539
+ else
540
+ raise(DeserializationError,"cannot execute REDUCE on a non-class: #{callable.inspect}")
541
+ end
542
+
543
+ @stack.push(object)
544
+ end
545
+
546
+ #
547
+ # Executes a `BUILD` instruction.
548
+ #
549
+ def execute_build
550
+ arg = @stack.pop
551
+ object = @stack.last
552
+
553
+ if object.respond_to?(:__setstate__)
554
+ object.__setstate__(arg)
555
+ elsif object.kind_of?(Hash)
556
+ object.merge!(arg)
557
+ else
558
+ raise(DeserializationError,"cannot execute BUILD on an object that does not define a __setstate__ method or is not a Hash: #{object.inspect}")
559
+ end
560
+ end
561
+
562
+ #
563
+ # Executes a `SETITEM` instruction.
564
+ #
565
+ def execute_setitem
566
+ key, value = @stack.pop(2)
567
+ dict = @stack.last
568
+
569
+ unless dict.kind_of?(Hash)
570
+ raise(DeserializationError,"cannot set key (#{key.inspect}) and value (#{value.inspect}) into non-Hash: #{dict.inspect}")
571
+ end
572
+
573
+ dict[key] = value
574
+ end
575
+
576
+ #
577
+ # Executes a `SETITEMS` instruction.
578
+ #
579
+ def execute_setitems
580
+ pairs = pop_meta_stack
581
+ dict = @stack.last
582
+
583
+ unless dict.kind_of?(Hash)
584
+ raise(DeserializationError,"cannot set key value pairs (#{pairs.inspect}) into non-Hash: #{dict.inspect}")
585
+ end
586
+
587
+ until pairs.empty?
588
+ key, value = pairs.pop(2)
589
+ dict[key] = value
590
+ end
591
+ end
592
+
593
+ end
594
+ end
595
+ end
@@ -0,0 +1,12 @@
1
+ module Python
2
+ module Pickle
3
+ class Error < RuntimeError
4
+ end
5
+
6
+ class InvalidFormat < Error
7
+ end
8
+
9
+ class DeserializationError < Error
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ module Python
2
+ module Pickle
3
+ class Instruction
4
+
5
+ # The opcode name.
6
+ #
7
+ # @return [Symbol]
8
+ attr_reader :opcode
9
+
10
+ #
11
+ # Initializes the instruction.
12
+ #
13
+ # @param [Symbol] opcode
14
+ #
15
+ def initialize(opcode)
16
+ @opcode = opcode
17
+ end
18
+
19
+ #
20
+ # Compares the instruction to another instruction.
21
+ #
22
+ # @param [Instruction] other
23
+ # The other instruction to compare against.
24
+ #
25
+ # @return [Boolean]
26
+ # Indicates whether the other instruction matches this one.
27
+ #
28
+ def ==(other)
29
+ (self.class == other.class) && (@opcode == other.opcode)
30
+ end
31
+
32
+ #
33
+ # Converts the instruction into a String.
34
+ #
35
+ # @return [String]
36
+ #
37
+ def to_s
38
+ @opcode.to_s
39
+ end
40
+
41
+ #
42
+ # Inspects the instruction.
43
+ #
44
+ # @return [String]
45
+ #
46
+ def inspect
47
+ "#<#{self.class}: #{self}>"
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ require 'python/pickle/instruction'
2
+
3
+ module Python
4
+ module Pickle
5
+ module Instructions
6
+ #
7
+ # Represents a pickle `ADDITEMS` instruction.
8
+ #
9
+ # @note Introduced in protocol 4.
10
+ #
11
+ class AddItems < Instruction
12
+
13
+ #
14
+ # Initializes the `ADDITEMS` instruction.
15
+ #
16
+ def initialize
17
+ super(:ADDITEMS)
18
+ end
19
+
20
+ end
21
+
22
+ # The `ADDITEMS` instruction.
23
+ ADDITEMS = AddItems.new
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ require 'python/pickle/instruction'
2
+
3
+ module Python
4
+ module Pickle
5
+ module Instructions
6
+ #
7
+ # Represents a pickle `APPEND` instruction.
8
+ #
9
+ class Append < Instruction
10
+
11
+ #
12
+ # Initializes the `APPEND` instruction.
13
+ #
14
+ def initialize
15
+ super(:APPEND)
16
+ end
17
+
18
+ end
19
+
20
+ # The `APPEND` instruction.
21
+ APPEND = Append.new
22
+ end
23
+ end
24
+ end