python-pickle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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