From d74482bec8ae702035bf4cc102b2894d00339c7b Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Wed, 20 May 2026 17:45:52 +0100 Subject: [PATCH 1/2] fix: raise CompileError when solc returns no contracts to avoid batch crash --- diffyscan/utils/compiler.py | 20 ++++++++- tests/test_compiler.py | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 tests/test_compiler.py diff --git a/diffyscan/utils/compiler.py b/diffyscan/utils/compiler.py index 67bff8a..a882831 100644 --- a/diffyscan/utils/compiler.py +++ b/diffyscan/utils/compiler.py @@ -107,7 +107,25 @@ def compile_contracts(compiler_path: str, input_settings: str) -> dict: raise CompileError(f"Compiler process timed out: {e}") except Exception as e: raise CompileError(f"An unexpected error occurred: {e}") - return json.loads(process.stdout) + + try: + output = json.loads(process.stdout) + except json.JSONDecodeError as e: + raise CompileError( + f"solc produced non-JSON output: {e}; stdout head: {process.stdout[:500]!r}" + ) + + if "contracts" not in output: + errors = output.get("errors") or [] + fatal = [e for e in errors if e.get("severity") == "error"] or errors + msgs = "\n".join( + e.get("formattedMessage") or e.get("message") or str(e) for e in fatal + ) + raise CompileError( + f"solc returned no contracts ({len(fatal)} error(s)):\n{msgs}" + ) + + return output def get_target_compiled_contract( diff --git a/tests/test_compiler.py b/tests/test_compiler.py new file mode 100644 index 0000000..f13a7c1 --- /dev/null +++ b/tests/test_compiler.py @@ -0,0 +1,89 @@ +"""Tests for diffyscan.utils.compiler.compile_contracts error handling.""" + +import json +import subprocess +from types import SimpleNamespace + +import pytest + +from diffyscan.utils.compiler import compile_contracts +from diffyscan.utils.custom_exceptions import CompileError + + +def _fake_run(stdout: bytes): + def runner(*args, **kwargs): + return SimpleNamespace(stdout=stdout, stderr=b"", returncode=0) + + return runner + + +def test_compile_contracts_raises_when_output_has_no_contracts(monkeypatch): + """solc returning only errors must surface a CompileError, not KeyError downstream.""" + errors_only = json.dumps( + { + "errors": [ + { + "severity": "error", + "formattedMessage": ( + 'ParserError: Source "@openzeppelin/contracts/utils/' + 'cryptography/MessageHashUtils.sol" not found.' + ), + "message": "Source not found", + } + ] + } + ).encode() + + monkeypatch.setattr(subprocess, "run", _fake_run(errors_only)) + + with pytest.raises(CompileError) as excinfo: + compile_contracts("/fake/solc", "{}") + + msg = str(excinfo.value) + assert "solc returned no contracts" in msg + assert "MessageHashUtils.sol" in msg + + +def test_compile_contracts_filters_warnings_when_no_contracts(monkeypatch): + """Warnings should be ignored when picking which messages to surface.""" + payload = json.dumps( + { + "errors": [ + {"severity": "warning", "message": "spdx warning"}, + { + "severity": "error", + "formattedMessage": "DeclarationError: Identifier not found.", + }, + ] + } + ).encode() + + monkeypatch.setattr(subprocess, "run", _fake_run(payload)) + + with pytest.raises(CompileError) as excinfo: + compile_contracts("/fake/solc", "{}") + + msg = str(excinfo.value) + assert "DeclarationError" in msg + assert "spdx warning" not in msg + assert "1 error(s)" in msg + + +def test_compile_contracts_raises_on_invalid_json(monkeypatch): + monkeypatch.setattr(subprocess, "run", _fake_run(b"not json at all")) + + with pytest.raises(CompileError) as excinfo: + compile_contracts("/fake/solc", "{}") + + assert "non-JSON output" in str(excinfo.value) + + +def test_compile_contracts_returns_output_on_success(monkeypatch): + payload = json.dumps( + {"contracts": {"Demo.sol": {"Demo": {"abi": [], "evm": {}}}}} + ).encode() + monkeypatch.setattr(subprocess, "run", _fake_run(payload)) + + result = compile_contracts("/fake/solc", "{}") + assert "contracts" in result + assert "Demo" in result["contracts"]["Demo.sol"] From 3110c4eb33bc6fefd813f5f7a88c1ffe2809dfb9 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Wed, 20 May 2026 18:03:48 +0100 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- diffyscan/utils/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diffyscan/utils/compiler.py b/diffyscan/utils/compiler.py index a882831..9b6ab92 100644 --- a/diffyscan/utils/compiler.py +++ b/diffyscan/utils/compiler.py @@ -110,7 +110,7 @@ def compile_contracts(compiler_path: str, input_settings: str) -> dict: try: output = json.loads(process.stdout) - except json.JSONDecodeError as e: + except (json.JSONDecodeError, UnicodeDecodeError, TypeError) as e: raise CompileError( f"solc produced non-JSON output: {e}; stdout head: {process.stdout[:500]!r}" )