Skip to content
Draft
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
76 changes: 76 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Syntax reference https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
# Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners
name: fuzz
on: [pull_request]

permissions:
contents: read

jobs:
fuzz:
runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'danmar' }}

steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

# the man-db trigger causes package installations to stall for several minutes at times. so just drop the package.
# see https://github.com/actions/runner/issues/4030
- name: Remove man-db package
run: |
sudo apt-get update
sudo apt-get remove man-db

- name: Install missing software
run: |
sudo apt-get update
sudo apt-get install -y make

- name: Install clang
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21

- name: Generate corpus
run: |
mkdir corpus_test
make testrunner CXXOPTS="-DSTORE_INPUT_DIR=\"\\\"$(pwd)/corpus_test\\\"\""
./testrunner

- name: Upload corpus (testrunner)
uses: actions/upload-artifact@v6
with:
name: corpus_test
path: ./corpus_test

- name: Build fuzzer
id: build
run: |
# TODO: test O/LTO for best speed
# TODO: use -stdlib=libc++ -lc++
make -j$(nproc) CXX=clang++ CXXOPTS="-O3 -flto -fno-omit-frame-pointer -g -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize-address-use-after-scope -fno-sanitize=integer -fno-sanitize-recover=undefined" LDOPTS="-flto" LIB_FUZZING_ENGINE="-fsanitize=fuzzer" fuzz
env:
CXX: clang-21

- name: Run fuzzer
run: |
mkdir corpus
mkdir artifacts
./fuzz -only_ascii=1 -timeout=5 -fork=$(nproc) -use_value_profile=0 -max_total_time=60 -artifact_prefix=./artifacts/ corpus corpus_test

- name: Upload corpus
uses: actions/upload-artifact@v6
if: (success() || failure()) && steps.build.outcome == 'success'
with:
name: corpus
path: ./corpus

- name: Upload artifacts
uses: actions/upload-artifact@v6
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./artifacts
19 changes: 15 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,32 @@ test.o: CPPFLAGS += $(TEST_CPPFLAGS)
test.o: CXXFLAGS += -Wno-multichar

%.o: %.cpp simplecpp.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $<
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< $(LIB_FUZZING_ENGINE)

fuzz_no.o: fuzz.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -DNO_FUZZ -c -o $@ $^

testrunner: test.o simplecpp.o
$(CXX) $(LDFLAGS) simplecpp.o test.o -o testrunner
$(CXX) $(LDFLAGS) -o $@ $^

test: testrunner simplecpp
./testrunner
python3 run-tests.py
python3 -m pytest integration_test.py -vv

fuzz: fuzz.o simplecpp.o
# TODO: use -stdlib=libc++ -lc++
# make fuzz CXX=clang++ CXXOPTS="-O2 -fno-omit-frame-pointer -g -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize-address-use-after-scope -fno-sanitize=integer -fno-sanitize-recover=undefined" LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
$(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $^ $(LIB_FUZZING_ENGINE)

no-fuzz: fuzz_no.o simplecpp.o
$(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $^

selfcheck: simplecpp
CXX=$(CXX) ./selfcheck.sh

simplecpp: main.o simplecpp.o
$(CXX) $(LDFLAGS) main.o simplecpp.o -o simplecpp
$(CXX) $(LDFLAGS) -o $@ $^

clean:
rm -f testrunner simplecpp *.o
rm -f testrunner fuzz no-fuzz simplecpp *.o
70 changes: 70 additions & 0 deletions fuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* simplecpp - A simple and high-fidelity C/C++ preprocessor library
* Copyright (C) 2016-2024 simplecpp team
*/

#include "simplecpp.h"

#include <cstdint>

#ifdef NO_FUZZ
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <string>
#endif

/*
0 - store in corpus
-1 - omit from corpus
*/
static int doProcess(const uint8_t *data, size_t dataSize)
{
simplecpp::OutputList outputList;
std::vector<std::string> files;
simplecpp::TokenList rawtokens(data, dataSize, files, "test.cpp", &outputList);

simplecpp::TokenList outputTokens(files);
simplecpp::FileDataCache filedata;
const simplecpp::DUI dui;
std::list<simplecpp::MacroUsage> macroUsage;
std::list<simplecpp::IfCond> ifCond;
simplecpp::preprocess(outputTokens, rawtokens, files, filedata, dui, &outputList, &macroUsage, &ifCond);

simplecpp::cleanup(filedata);

return 0;
}

#ifndef NO_FUZZ
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize);

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize)
{
return doProcess(data, dataSize);
}
#else
int main(int argc, char * argv[])
{
if (argc < 2 || argc > 3)
return EXIT_FAILURE;

std::ifstream f(argv[1]);
if (!f.is_open())
return EXIT_FAILURE;

std::ostringstream oss;
oss << f.rdbuf();

if (!f.good())
return EXIT_FAILURE;

const int cnt = (argc == 3) ? std::stoi(argv[2]) : 1;

const std::string code = oss.str();
for (int i = 0; i < cnt; ++i)
doProcess(reinterpret_cast<const uint8_t*>(code.data()), code.size());

return EXIT_SUCCESS;
}
#endif
21 changes: 20 additions & 1 deletion test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,28 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons

#define TEST_CASE(F) (testcase(#F, F, argc, argv))

#ifdef STORE_INPUT_DIR
// make testrunner CXXOPTS="-DSTORE_INPUT_DIR=\"\\\"/home/user/simple_corpus\\\"\""
#include <atomic>
#include <fstream>

static void storeInput(const std::string& str)
{
static std::atomic_uint64_t num(0);
{
std::ofstream out(STORE_INPUT_DIR "/" + std::to_string(num++));
out << str;
}
}
#endif

static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector<std::string> &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr)
{
std::istringstream istr(std::string(code, size));
const std::string str(code, size);
#ifdef STORE_INPUT_DIR
storeInput(str);
#endif
std::istringstream istr(str);
return simplecpp::TokenList(istr,filenames,filename,outputList);
}

Expand Down