Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/atheris/version_dependent.py: 46%
226 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1# Copyright 2021 Google LLC
2# Copyright 2021 Fraunhofer FKIE
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""This module manages the version specific aspects of bytecode instrumentation.
17Accross Python versions there are variations in:
18 - Instructions
19 - Instruction arguments
20 - Shape of a code object
21 - Construction of the lnotab
23Currently supported python versions are:
24 - 3.6
25 - 3.7
26 - 3.8
27 - 3.9
28 - 3.10
29 - 3.11
30"""
32import sys
33import types
34import dis
35import opcode
36from typing import List
38PYTHON_VERSION = sys.version_info[:2]
40if PYTHON_VERSION < (3, 6) or PYTHON_VERSION > (3, 11):
41 raise RuntimeError(
42 "You are fuzzing on an unsupported python version: "
43 + f"{PYTHON_VERSION[0]}.{PYTHON_VERSION[1]}. Only 3.6 - 3.11 are "
44 + "supported by atheris 2.0. Use atheris 1.0 for older python versions."
45 )
47### Instruction categories ###
49CONDITIONAL_JUMPS = [
50 # common
51 "FOR_ITER",
52 "JUMP_IF_FALSE_OR_POP",
53 "JUMP_IF_TRUE_OR_POP",
54 "POP_JUMP_IF_FALSE",
55 "POP_JUMP_IF_TRUE",
56 # 3.9
57 "JUMP_IF_NOT_EXC_MATCH",
58 # 3.11
59 "POP_JUMP_FORWARD_IF_TRUE",
60 "POP_JUMP_BACKWARD_IF_TRUE",
61 "POP_JUMP_FORWARD_IF_FALSE",
62 "POP_JUMP_BACKWARD_IF_FALSE",
63 "POP_JUMP_FORWARD_IF_NOT_NONE",
64 "POP_JUMP_BACKWARD_IF_NOT_NONE",
65 "POP_JUMP_FORWARD_IF_NONE",
66 "POP_JUMP_BACKWARD_IF_NONE",
67]
69UNCONDITIONAL_JUMPS = [
70 # common
71 "JUMP_FORWARD",
72 "JUMP_ABSOLUTE",
73 # 3.6 / 3.7
74 "CONTINUE_LOOP",
75 # 3.8
76 "CALL_FINALLY",
77 # 3.11
78 "JUMP_BACKWARD",
79 "JUMP_BACKWARD_NO_INTERRUPT",
80]
82ENDS_FUNCTION = [
83 # common
84 "RAISE_VARARGS",
85 "RETURN_VALUE",
87 # 3.9
88 "RERAISE",
89]
91HAVE_REL_REFERENCE = [
92 # common
93 "SETUP_WITH",
94 "JUMP_FORWARD",
95 "FOR_ITER",
96 "SETUP_FINALLY",
97 "CALL_FINALLY",
98 # 3.6 / 3.7
99 "SETUP_LOOP",
100 "SETUP_EXCEPT",
101 # 3.11
102 "JUMP_BACKWARD",
103 "JUMP_BACKWARD_NO_INTERRUPT",
104 "POP_JUMP_FORWARD_IF_TRUE",
105 "POP_JUMP_BACKWARD_IF_TRUE",
106 "POP_JUMP_FORWARD_IF_FALSE",
107 "POP_JUMP_BACKWARD_IF_FALSE",
108 "POP_JUMP_FORWARD_IF_NOT_NONE",
109 "POP_JUMP_BACKWARD_IF_NOT_NONE",
110 "POP_JUMP_FORWARD_IF_NONE",
111 "POP_JUMP_BACKWARD_IF_NONE",
112]
114HAVE_ABS_REFERENCE = [
115 # common
116 "POP_JUMP_IF_TRUE",
117 "POP_JUMP_IF_FALSE",
118 "JUMP_ABSOLUTE",
120 # 3.6 / 3.7
121 "CONTINUE_LOOP",
123 # 3.9
124 "JUMP_IF_NOT_EXC_MATCH",
125]
127REL_REFERENCE_IS_INVERTED = [
128 # 3.11
129 "JUMP_BACKWARD",
130 "JUMP_BACKWARD_NO_INTERRUPT",
131 "POP_JUMP_BACKWARD_IF_TRUE",
132 "POP_JUMP_BACKWARD_IF_FALSE",
133 "POP_JUMP_BACKWARD_IF_NOT_NONE",
134 "POP_JUMP_BACKWARD_IF_NONE",
135]
137if PYTHON_VERSION <= (3, 10):
138 HAVE_ABS_REFERENCE.extend([
139 "JUMP_IF_TRUE_OR_POP",
140 "JUMP_IF_FALSE_OR_POP",
141 ])
142else:
143 HAVE_REL_REFERENCE.extend([
144 "JUMP_IF_TRUE_OR_POP",
145 "JUMP_IF_FALSE_OR_POP",
146 ])
149# Returns -1 for instructions that have backward relative references
150# (e.g. JUMP_BACKWARD, an instruction that uses a positive number to
151# indicate a negative jump)
152def rel_reference_scale(opname):
153 assert opname in HAVE_REL_REFERENCE
154 if opname in REL_REFERENCE_IS_INVERTED:
155 return -1
156 return 1
159### Compare ops ###
161REVERSE_CMP_OP = [4, 5, 2, 3, 0, 1]
163### CodeTypes ###
165if (3, 6) <= PYTHON_VERSION <= (3, 7):
167 def get_code_object(
168 code_obj, stacksize, bytecode, consts, names, lnotab, exceptiontable
169 ):
170 return types.CodeType(code_obj.co_argcount, code_obj.co_kwonlyargcount,
171 code_obj.co_nlocals, stacksize, code_obj.co_flags,
172 bytecode, consts, names, code_obj.co_varnames,
173 code_obj.co_filename, code_obj.co_name,
174 code_obj.co_firstlineno, lnotab, code_obj.co_freevars,
175 code_obj.co_cellvars)
177elif (3, 8) <= PYTHON_VERSION <= (3, 10):
179 def get_code_object(
180 code_obj, stacksize, bytecode, consts, names, lnotab, exceptiontable
181 ):
182 return types.CodeType(code_obj.co_argcount, code_obj.co_posonlyargcount,
183 code_obj.co_kwonlyargcount, code_obj.co_nlocals,
184 stacksize, code_obj.co_flags, bytecode, consts, names,
185 code_obj.co_varnames, code_obj.co_filename,
186 code_obj.co_name, code_obj.co_firstlineno, lnotab,
187 code_obj.co_freevars, code_obj.co_cellvars)
189else:
191 def get_code_object(
192 code_obj, stacksize, bytecode, consts, names, lnotab, exceptiontable
193 ):
194 return types.CodeType(
195 code_obj.co_argcount,
196 code_obj.co_posonlyargcount,
197 code_obj.co_kwonlyargcount,
198 code_obj.co_nlocals,
199 stacksize,
200 code_obj.co_flags,
201 bytecode,
202 consts,
203 names,
204 code_obj.co_varnames,
205 code_obj.co_filename,
206 code_obj.co_name,
207 code_obj.co_qualname,
208 code_obj.co_firstlineno,
209 lnotab,
210 exceptiontable,
211 code_obj.co_freevars,
212 code_obj.co_cellvars,
213 )
216### Python 3.10 uses instruction (2 byte) offsets rather than byte offsets ###
218if PYTHON_VERSION >= (3, 10):
220 def jump_arg_bytes(arg: int) -> int:
221 return arg * 2
223 def add_bytes_to_jump_arg(arg: int, size: int) -> int:
224 return arg + size // 2
225else:
227 def jump_arg_bytes(arg: int) -> int:
228 return arg
230 def add_bytes_to_jump_arg(arg: int, size: int) -> int:
231 return arg + size
234### Lnotab/linetable handling ###
236# In Python 3.10 lnotab was deprecated, context:
237# 3.10 specific notes: https://github.com/python/cpython/blob/28b75c80dcc1e17ed3ac1c69362bf8dc164b760a/Objects/lnotab_notes.txt
238# GitHub PR: https://github.com/python/cpython/commit/877df851c3ecdb55306840e247596e7b7805a60a
239# Inspiration for the 3.10 code: https://github.com/python/cpython/blob/28b75c80dcc1e17ed3ac1c69362bf8dc164b760a/Python/compile.c#L5563
240# It changes again in 3.11.
243if (3, 6) <= PYTHON_VERSION <= (3, 9):
244 def get_lnotab(code, listing):
245 """Returns line number table."""
246 lnotab = []
247 current_lineno = listing[0].lineno
248 i = 0
250 assert listing[0].lineno >= code.co_firstlineno
252 if listing[0].lineno > code.co_firstlineno:
253 delta_lineno = listing[0].lineno - code.co_firstlineno
255 while delta_lineno > 127:
256 lnotab.extend([0, 127])
257 delta_lineno -= 127
259 lnotab.extend([0, delta_lineno])
261 while True:
262 delta_bc = 0
264 while i < len(listing) and listing[i].lineno == current_lineno:
265 delta_bc += listing[i].get_size()
266 i += 1
268 if i >= len(listing):
269 break
271 assert delta_bc > 0
273 delta_lineno = listing[i].lineno - current_lineno
275 while delta_bc > 255:
276 lnotab.extend([255, 0])
277 delta_bc -= 255
279 if delta_lineno < 0:
280 while delta_lineno < -128:
281 lnotab.extend([delta_bc, 0x80])
282 delta_bc = 0
283 delta_lineno += 128
285 delta_lineno %= 256
286 else:
287 while delta_lineno > 127:
288 lnotab.extend([delta_bc, 127])
289 delta_bc = 0
290 delta_lineno -= 127
292 lnotab.extend([delta_bc, delta_lineno])
293 current_lineno = listing[i].lineno
295 return bytes(lnotab)
298if (3, 10) <= PYTHON_VERSION <= (3, 10):
299 def get_lnotab(code, listing):
300 """Returns line number table."""
301 lnotab = []
302 prev_lineno = listing[0].lineno
304 for instr in listing:
305 bdelta = instr.get_size()
306 if bdelta == 0:
307 continue
308 ldelta = 0
309 if instr.lineno < 0:
310 ldelta = -128
311 else:
312 ldelta = instr.lineno - prev_lineno
313 while ldelta > 127:
314 lnotab.extend([0, 127])
315 ldelta -= 127
316 while ldelta < -127:
317 lnotab.extend([0, -127 % 256])
318 ldelta += 127
319 assert -128 <= ldelta < 128
320 ldelta %= 256
321 while bdelta > 254:
322 lnotab.extend([254, ldelta])
323 ldelta = -128 % 256 if instr.lineno < 0 else 0
324 bdelta -= 254
325 lnotab.extend([bdelta, ldelta])
326 prev_lineno = instr.lineno
328 return bytes(lnotab)
331if (3, 11) <= PYTHON_VERSION <= (3, 11):
332 from .native import _generate_codetable
333 def get_lnotab(code, listing):
334 ret = _generate_codetable(code, listing)
335 return ret
337### exceptiontable handling ###
339class ExceptionTableEntry:
341 def __init__(self, start_offset, end_offset, target, depth, lasti):
342 self.start_offset = start_offset
343 self.end_offset = end_offset
344 self.target = target
345 self.depth = depth
346 self.lasti = lasti
348 def __repr__(self):
349 return (
350 f"(start_offset={self.start_offset} end_offset={self.end_offset} target={self.target} depth={self.depth} lasti={self.lasti})"
351 )
353 def __str__(self):
354 return self.__repr__()
356 def __eq__(self, other):
357 return (
358 self.start_offset == other.start_offset
359 and self.end_offset == other.end_offset
360 and self.target == other.target
361 and self.depth == other.depth
362 and self.lasti == other.lasti
363 )
366class ExceptionTable:
368 def __init__(self, entries: List[ExceptionTableEntry]):
369 self.entries = entries
371 def __repr__(self):
372 return "\n".join([repr(x) for x in self.entries])
374 def __str__(self):
375 return "\n".join([repr(x) for x in self.entries])
377 def __eq__(self, other):
378 if len(self.entries) != len(other.entries):
379 return False
380 for i in range(len(self.entries)):
381 if self.entries[i] != other.entries[i]:
382 return False
383 return True
385# Default implementations
386# 3.11+ override these.
387def generate_exceptiontable(original_code, exception_table_entries):
388 return b""
390def parse_exceptiontable(code):
391 return ExceptionTable([])
394if (3, 11) <= PYTHON_VERSION <= (3, 11):
395 from .native import _generate_exceptiontable
397 def generate_exceptiontable(original_code, exception_table_entries):
398 return _generate_exceptiontable(original_code, exception_table_entries)
400 def parse_exceptiontable(co_exceptiontable):
401 if isinstance(co_exceptiontable, types.CodeType):
402 return parse_exceptiontable(co_exceptiontable.co_exceptiontable)
404 # These functions taken from:
405 # https://github.com/python/cpython/blob/main/Objects/exception_handling_notes.txt
406 def parse_varint(iterator):
407 b = next(iterator)
408 val = b & 63
409 while b & 64:
410 val <<= 6
411 b = next(iterator)
412 val |= b & 63
413 return val
415 def parse_exception_table(co_exceptiontable):
416 iterator = iter(co_exceptiontable)
417 try:
418 while True:
419 start = parse_varint(iterator) * 2
420 length = parse_varint(iterator) * 2
421 end = start + length - 2 # Present as inclusive, not exclusive
422 target = parse_varint(iterator) * 2
423 dl = parse_varint(iterator)
424 depth = dl >> 1
425 lasti = bool(dl & 1)
426 yield start, end, target, depth, lasti
427 except StopIteration:
428 return
430 entries = [
431 ExceptionTableEntry(*x)
432 for x in parse_exception_table(co_exceptiontable)
433 ]
434 return ExceptionTable(entries)
437### Opcode compatibility ###
438# These functions generate a series of (opcode, arg) tuples to represent the
439# requested operation.
441if (3, 6) <= PYTHON_VERSION <= (3, 10):
443 # There are no CACHE instructions in these versions, so return 0.
444 def cache_count(op):
445 return 0
447 # There are no CACHE instructions in these versions, so return empty list.
448 def caches(op):
449 return []
451 # Rotate the top width_n instructions, shift_n times.
452 def rot_n(width_n: int, shift_n: int = 1):
453 if shift_n != 1:
454 return RuntimeError("rot_n not supported with shift_n!=1. (Support could be emulated if needed.)")
456 if width_n < 1:
457 raise RuntimeError("Rotating by <1 does not make sense.")
458 if width_n == 1:
459 return []
460 if width_n == 2:
461 return [(dis.opmap["ROT_TWO"], 0)]
462 if width_n == 3:
463 return [(dis.opmap["ROT_THREE"], 0)]
465 if PYTHON_VERSION < (3, 8):
466 raise RuntimeError(
467 "Only Python versions 3.8+ support rotations greater than three."
468 )
470 if width_n == 4:
471 return [(dis.opmap["ROT_FOUR"], 0)]
473 if PYTHON_VERSION < (3, 10):
474 raise RuntimeError(
475 "Only Python versions 3.10+ support rotations greater than four."
476 )
478 return [(dis.opmap["ROT_N"], width_n)]
480 # 3.11+ needs a null terminator for the argument list, but 3.10- does not.
481 def args_terminator():
482 return []
484 # In 3.10-, all you need to call a function is CALL_FUNCTION.
485 def call(argc: int):
486 return [(dis.opmap["CALL_FUNCTION"], argc)]
488 # In 3.10-, each call pops 1 thing other than the arguments off the stack:
489 # the callable itself.
490 CALLABLE_STACK_ENTRIES = 1
493if PYTHON_VERSION >= (3, 11):
495 # The number of CACHE instructions that must go after the given instr.
496 def cache_count(op):
497 if isinstance(op, str):
498 op = dis.opmap[op]
500 return opcode._inline_cache_entries[op]
502 # Generate a list of CACHE instructions for the given instr.
503 def caches(op):
504 cc = cache_count(op)
505 return [(dis.opmap["CACHE"], 0)] * cc
507 # Rotate the top width_n instructions, shift_n times.
508 def rot_n(width_n: int, shift_n: int = 1):
509 ret = []
510 for j in range(shift_n):
511 for i in range(width_n, 1, -1):
512 ret.append([dis.opmap["SWAP"], i])
513 return ret
515 # Calling a free function in 3.11 requires a null terminator for the
516 # args list on the stack.
517 def args_terminator():
518 return [(dis.opmap["PUSH_NULL"], 0)]
520 # 3.11 requires a PRECALL instruction prior to every CALL instruction.
521 def call(argc: int):
522 ret = []
523 ret.append((dis.opmap["PRECALL"], argc))
524 ret.append((dis.opmap["CALL"], argc))
525 return ret
527 # A call pops 2 items off the stack in addition to the args: the callable
528 # itself, and a null terminator.
529 CALLABLE_STACK_ENTRIES = 2
531### disassembler compatibility ###
532# In 3.11, we need to pass show_caches=True.
534if (3, 6) <= PYTHON_VERSION <= (3, 10):
536 def get_instructions(x, *, first_line=None):
537 return dis.get_instructions(x, first_line=first_line)
540if (3, 11) <= PYTHON_VERSION:
542 def get_instructions(x, *, first_line=None, adaptive=False):
543 return dis.get_instructions(
544 x, first_line=first_line, adaptive=adaptive, show_caches=True
545 )