Skip to content

Commit 55c9023

Browse files
F-WRunTimelisandrasilvapalinatolmachrv-auditor
authored
Source maps improvement & Update expected output job fix (#820)
* wip to import sourcemapping approach from simbolik * wip * Refactoring * Fixed formatting * Updated lock file * Fixed typo * Fixed print_node function * forge build with extra-output * Added extra-output info to forge build * Updated expected files * Updated expected output for CSE tests * Error occurs when trying to copy updated test-data from in the container to the host. The file / folder is not found. Correcting this by specifying the full file path up to 'show' folder and copying the file to the relative host path ./src/tests/integration/test-data. modified: .github/workflows/update-expected-output.yml * Update expected output files --------- Co-authored-by: lisandrasilva <[email protected]> Co-authored-by: Palina Tolmach <[email protected]> Co-authored-by: devops <[email protected]>
1 parent 86451cb commit 55c9023

File tree

52 files changed

+911
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+911
-20
lines changed

.github/workflows/update-expected-output.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
docker exec --user github-user kontrol-ci-integration-${GITHUB_SHA} bash -c "make cov-integration TEST_ARGS='${TEST_ARGS} -k \"test_kontrol_cse or test_foundry_minimize_proof\"' || true"
3434
- name: 'Copy updated files to host'
3535
run: |
36-
docker cp kontrol-ci-integration-${GITHUB_SHA}:/home/user/src/tests/integration/test-data/show ./src/tests/integration/test-data/show
36+
docker cp kontrol-ci-integration-${GITHUB_SHA}:/home/github-user/workspace/src/tests/integration/test-data/show ./src/tests/integration/test-data/
3737
- name: 'Configure GitHub user'
3838
run: |
3939
git config user.name devops

poetry.lock

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ python = "^3.10"
1515
kevm-pyk = { git = "https://github.com/runtimeverification/evm-semantics.git", tag = "v1.0.711", subdirectory = "kevm-pyk" }
1616
eth-utils = "^4.1.1"
1717
pycryptodome = "^3.20.0"
18+
pyevmasm = "^0.2.3"
1819

1920
[tool.poetry.group.dev.dependencies]
2021
autoflake = "*"

src/kontrol/__main__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .hevm import Hevm
4141
from .kompile import foundry_kompile
4242
from .prove import foundry_prove
43+
from .solc import CompilationUnit
4344
from .solc_to_k import solc_compile, solc_to_k
4445
from .utils import _LOG_FORMAT, _rv_blue, _rv_yellow, check_k_version, config_file_path, console, loglevel
4546

@@ -311,11 +312,13 @@ def exec_view_kcfg(options: ViewKcfgOptions) -> None:
311312
contract_name, _ = test_id.split('.')
312313
proof = foundry.get_apr_proof(test_id)
313314

315+
compilation_unit = CompilationUnit.load_build_info(options.foundry_root)
316+
314317
def _short_info(cterm: CTerm) -> Iterable[str]:
315318
return foundry.short_info_for_contract(contract_name, cterm)
316319

317320
def _custom_view(elem: KCFGElem) -> Iterable[str]:
318-
return foundry.custom_view(contract_name, elem)
321+
return foundry.custom_view(contract_name, elem, compilation_unit)
319322

320323
node_printer = foundry_node_printer(foundry, contract_name, proof)
321324
viewer = APRProofViewer(proof, foundry.kevm, node_printer=node_printer, custom_view=_custom_view)

src/kontrol/foundry.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from pyk.utils import ensure_dir_path, hash_str, run_process_2, single, unique
3838

3939
from . import VERSION
40+
from .solc import CompilationUnit
4041
from .solc_to_k import Contract, _contract_name_from_bytecode
4142
from .state_record import RecreateState, StateDiffEntry, StateDumpEntry
4243
from .utils import (
@@ -78,7 +79,6 @@
7879
UnrefuteNodeOptions,
7980
)
8081

81-
8282
_LOGGER: Final = logging.getLogger(__name__)
8383

8484

@@ -339,18 +339,21 @@ def srcmap_data(self, contract_name: str, pc: int) -> tuple[Path, int, int] | No
339339
_, start, end = byte_offset_to_lines(src_contract_text.split('\n'), s, l)
340340
return (src_contract_path, start, end)
341341

342+
def solidity_src_print(self, path: Path, start: int, end: int) -> Iterable[str]:
343+
lines = path.read_text().split('\n')
344+
prefix_lines = [f' {l}' for l in lines[:start]]
345+
actual_lines = [f' | {l}' for l in lines[start:end]]
346+
suffix_lines = [f' {l}' for l in lines[end:]]
347+
return prefix_lines + actual_lines + suffix_lines
348+
342349
def solidity_src(self, contract_name: str, pc: int) -> Iterable[str]:
343350
srcmap_data = self.srcmap_data(contract_name, pc)
344351
if srcmap_data is None:
345352
return [f'No sourcemap data for contract at pc {contract_name}: {pc}']
346353
contract_path, start, end = srcmap_data
347354
if not (contract_path.exists() and contract_path.is_file()):
348355
return [f'No file at path for contract {contract_name}: {contract_path}']
349-
lines = contract_path.read_text().split('\n')
350-
prefix_lines = [f' {l}' for l in lines[:start]]
351-
actual_lines = [f' | {l}' for l in lines[start:end]]
352-
suffix_lines = [f' {l}' for l in lines[end:]]
353-
return prefix_lines + actual_lines + suffix_lines
356+
return self.solidity_src_print(contract_path, start, end)
354357

355358
def short_info_for_contract(self, contract_name: str, cterm: CTerm) -> list[str]:
356359
ret_strs = self.kevm.short_info(cterm)
@@ -362,23 +365,44 @@ def short_info_for_contract(self, contract_name: str, cterm: CTerm) -> list[str]
362365
ret_strs.append(f'src: {str(path)}:{start}:{end}')
363366
return ret_strs
364367

365-
def custom_view(self, contract_name: str, element: KCFGElem) -> Iterable[str]:
368+
def custom_view(self, contract_name: str, element: KCFGElem, compilation_unit: CompilationUnit) -> Iterable[str]:
366369
if type(element) is KCFG.Node:
367370
pc_cell = element.cterm.try_cell('PC_CELL')
371+
program_cell = element.cterm.try_cell('PROGRAM_CELL')
368372
if type(pc_cell) is KToken and pc_cell.sort == INT:
369-
return self.solidity_src(contract_name, int(pc_cell.token))
373+
if type(program_cell) is KToken:
374+
try:
375+
bytecode = ast.literal_eval(program_cell.token)
376+
instruction = compilation_unit.get_instruction(bytecode, int(pc_cell.token))
377+
node = instruction.node()
378+
start_line, _, end_line, _ = node.source_range()
379+
return self.solidity_src_print(Path(node.source.name), start_line - 1, end_line)
380+
except Exception:
381+
return [f'No sourcemap data for contract at pc {contract_name}: {int(pc_cell.token)}']
382+
return ['NO DATA']
370383
elif type(element) is KCFG.Edge:
371384
return list(element.rules)
372385
elif type(element) is KCFG.NDBranch:
373386
return list(element.rules)
374387
return ['NO DATA']
375388

376389
def build(self, no_metadata: bool) -> None:
377-
forge_build_args = ['forge', 'build', '--build-info', '--root', str(self._root)] + (
378-
['--no-metadata'] if no_metadata else []
379-
)
390+
forge_build_args = [
391+
'forge',
392+
'build',
393+
'--build-info',
394+
'--extra-output',
395+
'storageLayout',
396+
'evm.bytecode.generatedSources',
397+
'evm.deployedBytecode.generatedSources',
398+
'--root',
399+
str(self._root),
400+
] + (['--no-metadata'] if no_metadata else [])
380401
try:
381-
run_process_2(forge_build_args, logger=_LOGGER)
402+
run_process_2(
403+
forge_build_args,
404+
logger=_LOGGER,
405+
)
382406
except FileNotFoundError as err:
383407
raise RuntimeError(
384408
"Error: 'forge' command not found. Please ensure that 'forge' is installed and added to your PATH."
@@ -1333,24 +1357,32 @@ class FoundryNodePrinter(KEVMNodePrinter):
13331357
foundry: Foundry
13341358
contract_name: str
13351359
omit_unstable_output: bool
1360+
compilation_unit: CompilationUnit
13361361

13371362
def __init__(self, foundry: Foundry, contract_name: str, omit_unstable_output: bool = False):
13381363
KEVMNodePrinter.__init__(self, foundry.kevm)
13391364
self.foundry = foundry
13401365
self.contract_name = contract_name
13411366
self.omit_unstable_output = omit_unstable_output
1367+
self.compilation_unit = CompilationUnit.load_build_info(foundry._root)
13421368

13431369
def print_node(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
13441370
ret_strs = super().print_node(kcfg, node)
13451371
_pc = node.cterm.try_cell('PC_CELL')
1372+
program_cell = node.cterm.try_cell('PROGRAM_CELL')
1373+
13461374
if type(_pc) is KToken and _pc.sort == INT:
1347-
srcmap_data = self.foundry.srcmap_data(self.contract_name, int(_pc.token))
1348-
if not self.omit_unstable_output and srcmap_data is not None:
1349-
path, start, end = srcmap_data
1350-
ret_strs.append(f'src: {str(path)}:{start}:{end}')
1375+
if type(program_cell) is KToken:
1376+
try:
1377+
bytecode = ast.literal_eval(program_cell.token)
1378+
instruction = self.compilation_unit.get_instruction(bytecode, int(_pc.token))
1379+
ast_node = instruction.node()
1380+
start_line, _, end_line, _ = ast_node.source_range()
1381+
ret_strs.append(f'src: {str(Path(ast_node.source.name))}:{start_line}:{end_line}')
1382+
except Exception:
1383+
pass
13511384

13521385
calldata_cell = node.cterm.try_cell('CALLDATA_CELL')
1353-
program_cell = node.cterm.try_cell('PROGRAM_CELL')
13541386

13551387
if type(program_cell) is KToken:
13561388
selector_bytes = None

0 commit comments

Comments
 (0)