Skip to content

Commit c92c25c

Browse files
committed
Add ConcreteBytecode.{instr_at_code_offset, index_at_code_offset}
1 parent e9cc959 commit c92c25c

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

bytecode/concrete.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,45 @@ def to_bytecode(self):
377377
bytecode.extend(instructions)
378378
return bytecode
379379

380+
def index_at_code_offset(self, offset):
381+
"""
382+
Returns the index `i`, for use in slicing, such that:
383+
`self[i:]` is the decoded version of `self.to_code()[offset:]`
384+
`self[:i]` is the decoded version of `self.to_code()[:offset]`
385+
386+
For getting a single instruction an offset, use instr_at_code_offset
387+
388+
Raises IndexError if `offset` > len(self.to_code()), or `offset`
389+
lies midway through an instruction.
390+
"""
391+
if offset < 0:
392+
raise IndexError('Offset {} is out of range'.format(offset))
393+
394+
at = 0
395+
for i, instr in enumerate(self):
396+
if offset == at:
397+
return i
398+
elif offset < at:
399+
raise IndexError('Offset {} lies within instruction #{}, {}'.format(offset, i, instr))
400+
if isinstance(instr, ConcreteInstr):
401+
at += instr.size
402+
403+
# returning the length of the array is ok
404+
if offset == at:
405+
return i + 1
406+
407+
raise IndexError('Offset {} is out of range for code of length {}'.format(offset, at))
408+
409+
def instr_at_code_offset(self, offset):
410+
"""
411+
Return the instruction starting at `offset` within `self.to_code()`
412+
"""
413+
i = self.index_at_code_offset(offset)
414+
while i < len(self):
415+
if isinstance(self[i], ConcreteInstr):
416+
return self[i]
417+
i += 1
418+
raise IndexError('Instruction at {} is out of range'.format(i))
380419

381420
class _ConvertBytecodeToConcrete:
382421

bytecode/tests/test_concrete.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,42 @@ def test_load_classderef(self):
335335
self.assertEqual(
336336
code.co_code, b'\x94\x01\x89\x01' if WORDCODE else b'\x94\x01\x00\x89\x01\x00')
337337

338+
def test_offset_index(self):
339+
concrete = ConcreteBytecode()
340+
concrete[:] = [
341+
ConcreteInstr('LOAD_FAST', 0),
342+
ConcreteInstr('LOAD_FAST', 1),
343+
SetLineno(2),
344+
ConcreteInstr('BINARY_ADD'),
345+
ConcreteInstr('RETURN_VALUE')
346+
]
347+
# simple cases
348+
self.assertEqual(concrete.index_at_code_offset(0), 0)
349+
self.assertEqual(concrete.instr_at_code_offset(0), concrete[0])
350+
self.assertEqual(concrete.index_at_code_offset(3), 1)
351+
self.assertEqual(concrete.instr_at_code_offset(3), concrete[1])
352+
self.assertEqual(concrete.index_at_code_offset(7), 4)
353+
self.assertEqual(concrete.instr_at_code_offset(7), concrete[4])
354+
355+
# these indices are deliberately different
356+
# the index returns the lower bound, the SetLineno
357+
# the instruction returns the actual instruction
358+
self.assertEqual(concrete.index_at_code_offset(6), 2)
359+
self.assertEqual(concrete.instr_at_code_offset(6), concrete[3])
360+
361+
# asking for the index at the end is OK, but not the instruction
362+
self.assertEqual(concrete.index_at_code_offset(8), 5)
363+
self.assertRaisesRegex(IndexError, 'out of range', concrete.instr_at_code_offset, 8)
364+
365+
# other disallowed things
366+
self.assertRaisesRegex(IndexError, 'within', concrete.instr_at_code_offset, 1)
367+
self.assertRaisesRegex(IndexError, 'within', concrete.instr_at_code_offset, 1)
368+
self.assertRaisesRegex(IndexError, 'within', concrete.index_at_code_offset, 5)
369+
self.assertRaisesRegex(IndexError, 'within', concrete.instr_at_code_offset, 5)
370+
self.assertRaisesRegex(IndexError, 'out of range', concrete.index_at_code_offset, -1)
371+
self.assertRaisesRegex(IndexError, 'out of range', concrete.instr_at_code_offset, -1)
372+
self.assertRaisesRegex(IndexError, 'out of range', concrete.index_at_code_offset, 9)
373+
self.assertRaisesRegex(IndexError, 'out of range', concrete.instr_at_code_offset, 9)
338374

339375
class ConcreteFromCodeTests(TestCase):
340376

0 commit comments

Comments
 (0)