Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/deepstate/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def __init__(self):
L.debug("Analysis backend name: %s", self.name)

self.compiler_exe = self.EXECUTABLES.pop("COMPILER", None)
# added c compiler pop
self.compiler_c_exe = self.EXECUTABLES.pop("COMPILER_C", None)

# parsed argument attributes
self.binary: Optional[str] = None
Expand Down
22 changes: 19 additions & 3 deletions bin/deepstate/core/fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ def _set_executables(self):
self.compiler_exe = self._search_for_executable(self.compiler_exe)
L.debug("Will use %s as fuzzer compiler.", self.compiler_exe)

# added resolver for compile_c_exe
if self.compiler_c_exe:
self.compiler_c_exe = self._search_for_executable(self.compiler_c_exe)
L.debug("Will use %s as C compiler.", self.compiler_c_exe)

# set additional executables
for exe_name, exe_file in self.EXECUTABLES.items():
self.EXECUTABLES[exe_name] = self._search_for_executable(exe_file)
Expand Down Expand Up @@ -373,13 +378,24 @@ def compile(self, lib_path: str, flags: List[str], _out_bin: str, env = os.envir
L.debug("Static library path: %s", lib_path)

# initialize compiler envvars
env["CC"] = self.compiler_exe.replace('++', '')
# added compiler_c_exe for c file specifically
env["CC"] = self.compiler_c_exe if self.compiler_c_exe else self.compiler_exe.replace('++', '')
env["CXX"] = self.compiler_exe
L.debug("CC=%s and CXX=%s", env['CC'], env['CXX'])

# initialize command with prepended compiler
compiler_args: List[str] = ["-std=c++11", self.compile_test] + flags + ["-o", _out_bin] # type: ignore
compile_cmd = [self.compiler_exe] + compiler_args
# changing so that -std=c++11 is only used with c++ files
is_c_file = self.compile_test.endswith('.c')
if is_c_file:
chosen_compiler = env["CC"]
std_flag = "-std=c11"
else:
chosen_compiler = env["CXX"]
std_flag = "-std=c++11"
compiler_args = [std_flag, self.compile_test] + flags + ["-o", _out_bin]
compile_cmd = [chosen_compiler] + compiler_args
# compiler_args: List[str] = ["-std=c++11", self.compile_test] + flags + ["-o", _out_bin] # type: ignore
# compile_cmd = [self.compiler_exe] + compiler_args
L.debug("Compilation command: %s", compile_cmd)

# call compiler, and deal with exceptions accordingly
Expand Down
3 changes: 2 additions & 1 deletion bin/deepstate/executors/fuzz/afl.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class AFL(FuzzerFrontend):
""" Defines AFL fuzzer frontend """

NAME = "AFL"
EXECUTABLES = {"FUZZER": "afl-fuzz", "COMPILER": "afl-clang++"}
# added COMPILER_C for C files
EXECUTABLES = {"FUZZER": "afl-fuzz", "COMPILER": "afl-clang++", "COMPILER_C": "afl-clang"}

ENVVAR = "AFL_HOME"
REQUIRE_SEEDS = True
Expand Down
236 changes: 236 additions & 0 deletions tests/test_compiler_c_exe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""
Tests for the COMPILER_C / compiler_c_exe changes described in issue #337.

Place this file at: deepstate/tests/test_compiler_c_exe.py

Run from the repo root:
cd deepstate
PYTHONPATH=bin python3 -m unittest tests.test_compiler_c_exe -v
"""

import os
import sys
import unittest
from unittest.mock import patch, MagicMock

# ---------------------------------------------------------------------------
# Make sure the real deepstate package is importable.
# All tests in this repo are run with PYTHONPATH=bin, which puts
# bin/deepstate on the path — mirroring how the other tests work.
# ---------------------------------------------------------------------------
from deepstate.core.base import AnalysisBackend, AnalysisBackendError
from deepstate.core.fuzz import FuzzerFrontend, FuzzFrontendError
from deepstate.executors.fuzz.afl import AFL


# ---------------------------------------------------------------------------
# Helper: build a concrete FuzzerFrontend subclass on the fly
# ---------------------------------------------------------------------------

def make_frontend(executables: dict, compile_test: str = "harness.cpp"):
"""
Returns an instantiated FuzzerFrontend subclass configured with the
given EXECUTABLES dict and compile_test path.
Each call gets its own fresh copy of the dict so tests don't bleed
into each other.
"""

class TestFrontend(FuzzerFrontend):
NAME = "TestFuzzer"
EXECUTABLES = dict(executables)
ENVVAR = "PATH"

frontend = TestFrontend()
frontend.compile_test = compile_test
return frontend


# ---------------------------------------------------------------------------
# 1. base.py — compiler_c_exe is initialised from EXECUTABLES
# ---------------------------------------------------------------------------

class TestCompilerCExeInit(unittest.TestCase):

def test_compiler_c_exe_set_when_provided(self):
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
"COMPILER_C": "afl-clang",
})
self.assertEqual(fe.compiler_c_exe, "afl-clang")

def test_compiler_c_exe_none_when_not_provided(self):
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
})
self.assertIsNone(fe.compiler_c_exe)

def test_compiler_c_exe_popped_from_executables(self):
"""COMPILER_C must be removed from EXECUTABLES after init."""
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
"COMPILER_C": "afl-clang",
})
self.assertNotIn("COMPILER_C", fe.EXECUTABLES)


# ---------------------------------------------------------------------------
# 2. fuzz.py _set_executables() — compiler_c_exe is resolved
# ---------------------------------------------------------------------------

class TestSetExecutables(unittest.TestCase):

def test_compiler_c_exe_resolved(self):
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
"COMPILER_C": "afl-clang",
})
# patch _search_for_executable to avoid needing real binaries installed
with patch.object(fe, "_search_for_executable", side_effect=lambda x: x):
fe._set_executables()
self.assertEqual(fe.compiler_c_exe, "afl-clang")

def test_compiler_c_exe_skipped_when_none(self):
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
})
with patch.object(fe, "_search_for_executable", side_effect=lambda x: x):
fe._set_executables()
self.assertIsNone(fe.compiler_c_exe)


# ---------------------------------------------------------------------------
# 3. fuzz.py compile() — CC / CXX env vars
# ---------------------------------------------------------------------------

class TestCompileEnvVars(unittest.TestCase):

def _run_compile(self, frontend):
"""Run compile() with subprocess and filesystem mocked out."""
captured_env = {}

def fake_popen(cmd, env=None):
captured_env.update(env or {})
m = MagicMock()
m.communicate.return_value = (b"", b"")
return m

with patch("subprocess.Popen", side_effect=fake_popen), \
patch("os.path.isfile", return_value=True), \
patch("os.path.exists", return_value=False):
frontend.compile(lib_path="/fake/lib.a", flags=[], _out_bin="out")

return captured_env

def test_cc_uses_compiler_c_exe_when_set(self):
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
"COMPILER_C": "afl-clang",
})
env = self._run_compile(fe)
self.assertEqual(env["CC"], "afl-clang")
self.assertEqual(env["CXX"], "afl-clang++")

def test_cc_falls_back_to_stripping_plusplus(self):
"""Without COMPILER_C, CC should be derived by stripping '++'."""
fe = make_frontend({
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
})
env = self._run_compile(fe)
self.assertEqual(env["CC"], "afl-clang")
self.assertEqual(env["CXX"], "afl-clang++")


# ---------------------------------------------------------------------------
# 4. fuzz.py compile() — correct compiler binary and -std flag
# ---------------------------------------------------------------------------

class TestCompileCommandSelection(unittest.TestCase):

def _captured_cmd(self, compile_test, executables):
captured = {}

def fake_popen(cmd, env=None):
captured["cmd"] = cmd
m = MagicMock()
m.communicate.return_value = (b"", b"")
return m

fe = make_frontend(executables, compile_test=compile_test)

with patch("subprocess.Popen", side_effect=fake_popen), \
patch("os.path.isfile", return_value=True), \
patch("os.path.exists", return_value=False):
fe.compile(lib_path="/fake/lib.a", flags=[], _out_bin="out")

return captured["cmd"]

def test_c_file_uses_c_compiler_and_c11(self):
cmd = self._captured_cmd(
compile_test="harness.c",
executables={
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
"COMPILER_C": "afl-clang",
},
)
self.assertEqual(cmd[0], "afl-clang",
"C file should use the C compiler, not the C++ one")
self.assertIn("-std=c11", cmd)
self.assertNotIn("-std=c++11", cmd)

def test_cpp_file_uses_cxx_compiler_and_cpp11(self):
cmd = self._captured_cmd(
compile_test="harness.cpp",
executables={
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
"COMPILER_C": "afl-clang",
},
)
self.assertEqual(cmd[0], "afl-clang++",
"C++ file should use the C++ compiler")
self.assertIn("-std=c++11", cmd)
self.assertNotIn("-std=c11", cmd)

def test_c_file_fallback_does_not_use_cxx_binary(self):
"""Even without COMPILER_C, a .c file must not use afl-clang++."""
cmd = self._captured_cmd(
compile_test="harness.c",
executables={
"FUZZER": "afl-fuzz",
"COMPILER": "afl-clang++",
},
)
self.assertNotEqual(cmd[0], "afl-clang++")
self.assertEqual(cmd[0], "afl-clang")


# ---------------------------------------------------------------------------
# 5. afl.py — EXECUTABLES has COMPILER_C with the correct value
# ---------------------------------------------------------------------------

class TestAFLExecutables(unittest.TestCase):

def test_afl_has_compiler_c_key(self):
self.assertIn("COMPILER_C", AFL.EXECUTABLES)

def test_afl_compiler_c_is_afl_clang(self):
self.assertEqual(AFL.EXECUTABLES["COMPILER_C"], "afl-clang")

def test_afl_compiler_c_differs_from_compiler(self):
self.assertNotEqual(
AFL.EXECUTABLES["COMPILER_C"],
AFL.EXECUTABLES["COMPILER"],
)


if __name__ == "__main__":
unittest.main(verbosity=2)