flare-floss/tests/test_code_ranges.py
lakshit verma ae271e6b1a
refactor(tests): drop qs prefix from remaining test modules
Rename test_qs_* to names that match layout/tags/ranges scope after
QuantumStrand removal (test_layout_*, test_oss_db, test_ranges, etc.).
2026-08-03 01:01:37 +05:30

183 lines
5.6 KiB
Python

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from unittest.mock import Mock, MagicMock
import pefile
import pytest
from floss.ranges import Range, Slice, merge_overlapping_ranges
from floss.layout.pe import _get_code_ranges
# Tests for merge_overlapping_ranges
def test_merge_empty_list():
"""Test merging an empty list of ranges."""
assert merge_overlapping_ranges([]) == []
def test_merge_no_overlap():
"""Test merging ranges that do not overlap."""
ranges = [(10, 20), (30, 40), (50, 60)]
assert merge_overlapping_ranges(ranges) == [(10, 20), (30, 40), (50, 60)]
def test_merge_with_overlap():
"""Test merging ranges that partially overlap."""
ranges = [(10, 20), (15, 25), (30, 40)]
assert merge_overlapping_ranges(ranges) == [(10, 25), (30, 40)]
def test_merge_adjacent():
"""Test merging ranges that are right next to each other."""
ranges = [(10, 20), (21, 30), (31, 40)]
assert merge_overlapping_ranges(ranges) == [(10, 40)]
def test_merge_fully_contained():
"""Test merging ranges where some are fully contained within others."""
ranges = [(10, 40), (15, 25), (20, 30)]
assert merge_overlapping_ranges(ranges) == [(10, 40)]
def test_merge_complex_mix():
"""Test a complex mixture of overlapping, adjacent, and contained ranges."""
ranges = [(50, 60), (10, 20), (18, 30), (35, 40), (39, 55)]
# After sorting: [(10, 20), (18, 30), (35, 40), (39, 55), (50, 60)]
# (10, 20) and (18, 30) -> (10, 30)
# (35, 40) and (39, 55) -> (35, 55)
# (35, 55) and (50, 60) -> (35, 60)
assert merge_overlapping_ranges(ranges) == [(10, 30), (35, 60)]
# Tests for _get_code_ranges
@pytest.fixture
def mock_pe():
"""Fixture for a mocked pefile.PE object."""
pe = MagicMock(spec=pefile.PE)
def get_offset_from_rva(rva):
# Simple mapping for testing: offset is just rva + 0x1000
return rva + 0x1000
pe.get_offset_from_rva.side_effect = get_offset_from_rva
return pe
def _make_instr(size: int) -> Mock:
instr = Mock()
instr.raw_bytes = b"\x90" * size
return instr
def _make_be2_mocks(bb_instructions: list):
"""
Build mock be2 and idx objects.
bb_instructions: list of lists of (va, size) tuples, one sub-list per basic block.
"""
be2 = MagicMock()
idx = MagicMock()
basic_blocks = {}
instr_map = {}
for bb_i, instrs in enumerate(bb_instructions):
bb = Mock()
basic_blocks[bb_i] = bb
instr_map[id(bb)] = [(i, _make_instr(sz), va) for i, (va, sz) in enumerate(instrs)]
fg = Mock()
fg.basic_block_index = list(range(len(basic_blocks)))
be2.flow_graph = [fg]
be2.basic_block = basic_blocks
def _bb_instructions(bb):
return iter(instr_map.get(id(bb), []))
idx.basic_block_instructions.side_effect = _bb_instructions
return be2, idx
def test_get_code_ranges_basic(mock_pe):
"""Test basic extraction of code ranges."""
# base_address = 0x400000, rva = va - base
# bb1: va 0x401000, size 0x10 -> rva 0x1000, offset 0x2000 -> range (0x2000, 0x200F)
# bb2: va 0x401020, size 0x15 -> rva 0x1020, offset 0x2020 -> range (0x2020, 0x2034)
# bb3: va 0x402000, size 0x20 -> rva 0x2000, offset 0x3000 -> range (0x3000, 0x301F)
be2, idx = _make_be2_mocks(
[
[(0x401000, 0x10)],
[(0x401020, 0x15)],
[(0x402000, 0x20)],
]
)
slice_ = Slice(buf=b"", range=Range(offset=0, length=0x5000))
ranges = _get_code_ranges(be2, idx, 0x400000, mock_pe, slice_)
assert ranges == [
(0x2000, 0x200F), # bb1: offset 0x2000, size 0x10
(0x2020, 0x2034), # bb2: offset 0x2020, size 0x15
(0x3000, 0x301F), # bb3: offset 0x3000, size 0x20
]
def test_get_code_ranges_skips_invalid_offset(mock_pe):
"""Test that it skips instructions that fall outside the slice."""
be2, idx = _make_be2_mocks(
[
[(0x401000, 0x10)], # offset 0x2000, fits in slice
[(0x401020, 0x15)], # offset 0x2020, outside slice
[(0x402000, 0x20)], # offset 0x3000, outside slice
]
)
# Slice only covers through offset 0x2010
slice_ = Slice(buf=b"", range=Range(offset=0, length=0x2010))
ranges = _get_code_ranges(be2, idx, 0x400000, mock_pe, slice_)
# Only bb1 should be included
assert ranges == [(0x2000, 0x200F)]
def test_get_code_ranges_handles_pe_error(mock_pe):
"""Test that it handles PEFormatError when getting an offset."""
def get_offset_from_rva_with_error(rva):
if rva == 0x1020: # Corresponds to bb2
raise pefile.PEFormatError("Test Error")
return rva + 0x1000
mock_pe.get_offset_from_rva.side_effect = get_offset_from_rva_with_error
be2, idx = _make_be2_mocks(
[
[(0x401000, 0x10)],
[(0x401020, 0x15)],
[(0x402000, 0x20)],
]
)
slice_ = Slice(buf=b"", range=Range(offset=0, length=0x5000))
ranges = _get_code_ranges(be2, idx, 0x400000, mock_pe, slice_)
# bb2 should be skipped due to PEFormatError
assert ranges == [
(0x2000, 0x200F),
(0x3000, 0x301F),
]