Skip to content
Merged
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ endif()
set(LIB_SOURCES
src/compilerlib/compiler.cpp
src/compilerlib/emit/llvm_output.cpp
src/compilerlib/frontend/optnone_action.cpp
src/compilerlib/toolchain.cpp
src/compilerlib/instrumentation/alloc.cpp
src/compilerlib/instrumentation/bounds.cpp
Expand Down
171 changes: 94 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,50 @@
# coretrace-compiler
# CoreTrace Compiler

#### BUILD
CoreTrace Compiler is a Clang/LLVM-based wrapper that can emit LLVM IR, build binaries, and optionally
instrument code with runtime checks (alloc/bounds/trace/vtable). It can run in a file-based mode or
an in-memory mode for tooling pipelines.

## Build

Quick build:

```zsh
mkdir -p build && cd build
./build.sh
```

### Code style (clang-format)

- Version cible : `clang-format` 17 (utilisée dans la CI).
- Formater localement : `./scripts/format.sh`
- Vérifier sans modifier : `./scripts/format-check.sh`
- CMake : `cmake --build build --target format` ou `--target format-check`
- CI : le job GitHub Actions `clang-format` échoue si un fichier n’est pas formaté.

#### BUILD (macOS)
macOS:

```zsh
mkdir -p build && cd build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DLLVM_DIR=$(brew --prefix llvm)/lib/cmake/llvm \
-DClang_DIR=$(brew --prefix llvm)/lib/cmake/clang \
-DUSE_SHARED_LIB=OFF
-DLLVM_DIR=$(brew --prefix llvm)/lib/cmake/llvm \
-DClang_DIR=$(brew --prefix llvm)/lib/cmake/clang \
-DUSE_SHARED_LIB=OFF
```

#### BUILD (Linux)
Linux:

```zsh
mkdir -p build && cd build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DLLVM_DIR=/usr/lib/llvm-${LLVM_VERSION}/lib/cmake/llvm \
-DClang_DIR=/usr/lib/llvm-${LLVM_VERSION}/lib/cmake/clang \
-DCLANG_LINK_CLANG_DYLIB=ON \
-DLLVM_LINK_LLVM_DYLIB=ON \
-DUSE_SHARED_LIB=OFF \
-DLLVM_DIR=/usr/lib/llvm-${LLVM_VERSION}/lib/cmake/llvm \
-DClang_DIR=/usr/lib/llvm-${LLVM_VERSION}/lib/cmake/clang \
-DCLANG_LINK_CLANG_DYLIB=ON \
-DLLVM_LINK_LLVM_DYLIB=ON \
-DUSE_SHARED_LIB=OFF \
&& cmake --build build -j"$(nproc)"
```

#### CORETRACE-COMPILER USAGE
## Code Style (clang-format)

- Target version: `clang-format` 17 (CI uses this).
- Format locally: `./scripts/format.sh`
- Check without modifying: `./scripts/format-check.sh`
- CMake targets: `cmake --build build --target format` or `--target format-check`
- CI: the `clang-format` GitHub Actions job fails if a file is not formatted.

## Usage

```zsh
./cc -S -emit-llvm test.cc
Expand All @@ -56,32 +62,44 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
./cc --in-mem -emit-llvm test.c
```

Options:
- --instrument: enable instrumentation (required for --ct-* flags).
- --in-mem, --in-memory: print LLVM IR to stdout (use with -emit-llvm).
- --ct-shadow: enable shadow memory in the produced binary.
- --ct-shadow-aggressive, --ct-shadow=aggressive: enable the aggressive shadow mode.
- --ct-bounds-no-abort: do not abort on bounds errors.
- --ct-modules=trace,alloc,bounds,vtable: select which instrumentation passes to run (supports "all").
- --ct-no-trace, --ct-trace: disable/enable function entry/exit instrumentation.
- --ct-no-alloc, --ct-alloc: disable/enable malloc/free instrumentation.
- --ct-no-bounds, --ct-bounds: disable/enable bounds checks.
- --ct-no-autofree, --ct-autofree: disable/enable auto-free on unreachable allocations.
- --ct-no-alloc-trace, --ct-alloc-trace: disable/enable malloc/free tracing logs.
- --ct-vcall-trace, --ct-no-vcall-trace: enable/disable virtual call tracing (Itanium ABI).
- --ct-vtable-diag, --ct-no-vtable-diag: enable/disable vtable diagnostics (extra warnings).

All other arguments are forwarded to clang (e.g., -O2, -g, -I, -D, -L, -l, -std=...).
Alloc instrumentation rewrites malloc/free/calloc/realloc and basic C++ operator new/delete
(scalar/array). Sized/aligned new/delete overloads are not handled yet.
Vtable tooling (module vtable) requires C++ and an Itanium ABI (macOS/Linux).

#### Auto-free GC scan (conservative)
## CLI Options

Core options:
- `--instrument`: enable CoreTrace instrumentation (required for `--ct-*` flags).
- `--in-mem`, `--in-memory`: print LLVM IR to stdout (use with `-emit-llvm`).

Instrumentation toggles:
- `--ct-modules=<list>`: comma-separated list `trace,alloc,bounds,vtable,all`.
- `--ct-shadow`: enable shadow memory in the produced binary.
- `--ct-shadow-aggressive`, `--ct-shadow=aggressive`: aggressive shadow mode.
- `--ct-bounds-no-abort`: do not abort on bounds errors.
- `--ct-no-trace` / `--ct-trace`: disable/enable function entry/exit instrumentation.
- `--ct-no-alloc` / `--ct-alloc`: disable/enable malloc/free instrumentation.
- `--ct-no-bounds` / `--ct-bounds`: disable/enable bounds checks.
- `--ct-no-autofree` / `--ct-autofree`: disable/enable auto-free on unreachable allocations.
- `--ct-no-alloc-trace` / `--ct-alloc-trace`: disable/enable malloc/free tracing logs.
- `--ct-no-vcall-trace` / `--ct-vcall-trace`: disable/enable virtual call tracing (Itanium ABI).
- `--ct-no-vtable-diag` / `--ct-vtable-diag`: enable/disable vtable diagnostics.

Frontend toggles:
- `--ct-optnone`: add `optnone` and `noinline` to user-defined functions.
- `--ct-no-optnone`: disable optnone injection.

Notes:
- All other arguments are forwarded to clang (e.g. `-O2`, `-g`, `-I`, `-D`, `-L`, `-l`, `-std=...`).
- Alloc instrumentation rewrites `malloc/free/calloc/realloc` and basic C++ `operator new/delete`
(scalar/array). Sized/aligned new/delete overloads are not handled yet.
- Vtable tooling requires C++ and an Itanium ABI (macOS/Linux).
- Clang automatically adds `optnone` at `-O0`. Use `--ct-optnone` to force the attribute even when
passing `-Xclang -disable-O0-optnone`.

## Auto-free GC Scan (Conservative)

The runtime can run a conservative root scan (stack/regs/globals) to decide whether an
allocation is still reachable. This is optional and controlled by env vars.
allocation is still reachable. This is optional and controlled by environment variables.

Typical usage:

```zsh
CT_AUTOFREE_SCAN=1 CT_AUTOFREE_SCAN_START=1 \
CT_AUTOFREE_SCAN_PTR=0 \
Expand All @@ -93,11 +111,13 @@ CT_DEBUG_AUTOFREE_SCAN=1 \
```

Without GC scan (auto-free only from compile-time analysis):

```zsh
./app
```

With GC scan (conservative root scan):

```zsh
CT_AUTOFREE_SCAN=1 CT_AUTOFREE_SCAN_START=1 \
CT_AUTOFREE_SCAN_PTR=1 \
Expand All @@ -109,62 +129,59 @@ CT_DEBUG_AUTOFREE_SCAN=1 \
```

Environment variables (ms can be floating-point; US/NS override MS):
- CT_AUTOFREE_SCAN=1: enable conservative scanning.
- CT_AUTOFREE_SCAN_START=1: run a scan at startup and launch a periodic scan thread.
- CT_AUTOFREE_SCAN_PERIOD_MS=N: period between scans when START=1 (default: 1000ms).
- CT_AUTOFREE_SCAN_PERIOD_US=N: period between scans in microseconds.
- CT_AUTOFREE_SCAN_PERIOD_NS=N: period between scans in nanoseconds.
- CT_AUTOFREE_SCAN_BUDGET_MS=N: time budget per scan; if exceeded, no frees are performed.
- CT_AUTOFREE_SCAN_BUDGET_US=N: time budget per scan in microseconds.
- CT_AUTOFREE_SCAN_BUDGET_NS=N: time budget per scan in nanoseconds.
- CT_AUTOFREE_SCAN_STACK=0/1: scan thread stacks (default: 1).
- CT_AUTOFREE_SCAN_REGS=0/1: scan registers (default: 1).
- CT_AUTOFREE_SCAN_GLOBALS=0/1: scan globals (__DATA segments) (default: 1).
- CT_AUTOFREE_SCAN_INTERIOR=0/1: treat interior pointers as roots (default: 1).
- CT_AUTOFREE_SCAN_PTR=0/1: enable per-pointer scan before auto-free (default: 1).
- CT_DEBUG_AUTOFREE_SCAN=1: log scan activity only when a scan frees or times out.
- CT_DEBUG_AUTOFREE_SCAN=2: log every scan + per-pointer scans.

Use-cases:
- Keep CT_AUTOFREE_SCAN_PTR=1 if you want a conservative safety check before any auto-free.
- Set CT_AUTOFREE_SCAN_PTR=0 if you want immediate auto-free for "unreachable" sites and
only rely on periodic scans.
- Disable GLOBALS (CT_AUTOFREE_SCAN_GLOBALS=0) to reduce scan cost when you see timeouts.
- `CT_AUTOFREE_SCAN=1`: enable conservative scanning.
- `CT_AUTOFREE_SCAN_START=1`: run a scan at startup and launch a periodic scan thread.
- `CT_AUTOFREE_SCAN_PERIOD_MS=N`: period between scans when START=1 (default: 1000ms).
- `CT_AUTOFREE_SCAN_PERIOD_US=N`: period between scans in microseconds.
- `CT_AUTOFREE_SCAN_PERIOD_NS=N`: period between scans in nanoseconds.
- `CT_AUTOFREE_SCAN_BUDGET_MS=N`: time budget per scan; if exceeded, no frees are performed.
- `CT_AUTOFREE_SCAN_BUDGET_US=N`: time budget per scan in microseconds.
- `CT_AUTOFREE_SCAN_BUDGET_NS=N`: time budget per scan in nanoseconds.
- `CT_AUTOFREE_SCAN_STACK=0/1`: scan thread stacks (default: 1).
- `CT_AUTOFREE_SCAN_REGS=0/1`: scan registers (default: 1).
- `CT_AUTOFREE_SCAN_GLOBALS=0/1`: scan globals (`__DATA` segments) (default: 1).
- `CT_AUTOFREE_SCAN_INTERIOR=0/1`: treat interior pointers as roots (default: 1).
- `CT_AUTOFREE_SCAN_PTR=0/1`: enable per-pointer scan before auto-free (default: 1).
- `CT_DEBUG_AUTOFREE_SCAN=1`: log scan activity only when a scan frees or times out.
- `CT_DEBUG_AUTOFREE_SCAN=2`: log every scan plus per-pointer scans.

Use cases:
- Keep `CT_AUTOFREE_SCAN_PTR=1` for a conservative safety check before any auto-free.
- Set `CT_AUTOFREE_SCAN_PTR=0` for immediate auto-free on unreachable sites, and rely on periodic scans.
- Disable `CT_AUTOFREE_SCAN_GLOBALS=0` to reduce scan cost when you see timeouts.

Notes:
- This is conservative: stale values on stack/regs/globals can keep a pointer "reachable".
- If a scan times out (budget exceeded), nothing is freed to avoid false positives.

Vtable diagnostics (--ct-vtable-diag):
Vtable diagnostics (`--ct-vtable-diag`):
- Logs an init line with alloc-tracking state: enabled/disabled and the reason if disabled.
- Warns when vptr/vtable data is invalid: null this pointer, missing vptr, or missing typeinfo.
- Warns when vptr/vtable data is invalid: null `this` pointer, missing vptr, or missing typeinfo.
- Warns when vtable address cannot be resolved to a module (dladdr/phdr/dyld fallback).
- Warns on module mismatch when both vtable module and target module are resolved.
- Notes partial resolution cases (only vtable or target resolved) and escalates if target is non-exec.
- Warns on static vs dynamic type mismatch when a static type is available.
- Warns when the object appears freed (alloc table says state=freed).

#### HOW TO USE THIS CORETRACE-COMPILER IN YOUR PROJECT

You can integrate coretrace-compiler directly into your own CMake project using FetchContent or by building it as a standalone library.

Below is a minimal example using the sample project located in the extern-project directory.
## Using coretrace-compiler in Your Project

##### (Please use a recent LLVM version for best compatibility : llvm@19/20/...)
You can integrate coretrace-compiler into your own CMake project using FetchContent or by building
it as a standalone library. The sample project in `extern-project` shows a minimal setup.

```zsh
cd extern-project
mkdir -p build && cd build

cmake .. -DLLVM_DIR=$(brew --prefix llvm@20)/lib/cmake/llvm/ \
-DClang_DIR=$(brew --prefix llvm@20)/lib/cmake/clang
-DClang_DIR=$(brew --prefix llvm@20)/lib/cmake/clang

make
```

This example demonstrates how to:
Link against compilerlib_static or compilerlib_shared
Use the public API from include/compilerlib/
Build your project with the correct LLVM/Clang version (20 on macOS)
- Link against `compilerlib_static` or `compilerlib_shared`.
- Use the public API from `include/compilerlib/`.
- Build with the correct LLVM/Clang version (20 on macOS).

You can use the same pattern in any external project by passing the correct LLVM_DIR and Clang_DIR paths to CMake.
You can use the same pattern in any external project by passing the correct `LLVM_DIR`
and `Clang_DIR` paths to CMake.
40 changes: 40 additions & 0 deletions include/compilerlib/frontend/optnone_action.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef COMPILERLIB_FRONTEND_OPTNONE_ACTION_HPP
#define COMPILERLIB_FRONTEND_OPTNONE_ACTION_HPP

#include <clang/AST/ASTConsumer.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/MultiplexConsumer.h>
#include <llvm/ADT/StringRef.h>

#include <memory>
#include <vector>

namespace compilerlib::frontend
{

std::unique_ptr<clang::ASTConsumer> makeOptNoneConsumer(clang::ASTContext& ctx);

template <typename BaseAction> class OptNoneAction : public BaseAction
{
public:
using BaseAction::BaseAction;

protected:
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance& CI,
llvm::StringRef InFile) override
{
auto codegen = BaseAction::CreateASTConsumer(CI, InFile);
if (!codegen)
{
return codegen;
}
std::vector<std::unique_ptr<clang::ASTConsumer>> consumers;
consumers.push_back(makeOptNoneConsumer(CI.getASTContext()));
consumers.push_back(std::move(codegen));
return std::make_unique<clang::MultiplexConsumer>(std::move(consumers));
}
};

} // namespace compilerlib::frontend

#endif // COMPILERLIB_FRONTEND_OPTNONE_ACTION_HPP
1 change: 1 addition & 0 deletions include/compilerlib/instrumentation/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace compilerlib
bool autofree_enabled = false;
bool alloc_trace_enabled = true;
bool bounds_without_alloc = false;
bool optnone_enabled = false;
};

void extractRuntimeConfig(const std::vector<std::string>& input,
Expand Down
4 changes: 4 additions & 0 deletions src/cli/help.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ namespace cli
<< " --ct-no-vcall-trace / --ct-vcall-trace\n"
<< " --ct-no-vtable-diag / --ct-vtable-diag\n"
<< "\n"
<< "Frontend toggles:\n"
<< " --ct-optnone Add optnone/noinline to user-defined functions.\n"
<< " --ct-no-optnone Disable optnone injection.\n"
<< "\n"
<< "Defaults:\n"
<< " instrumentation: off\n"
<< " modules: trace,alloc,bounds (vtable disabled)\n"
Expand Down
9 changes: 8 additions & 1 deletion src/cli/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ int main(int argc, char* argv[])
}

auto res = compilerlib::compile(parsed.compiler_args, parsed.mode, parsed.instrument);
if (!res.success)
if (!res.diagnostics.empty())
{
std::cerr << res.diagnostics;
if (res.diagnostics.back() != '\n')
{
std::cerr << '\n';
}
}
if (!res.success)
{
return 1;
}

Expand Down
Loading
Loading