Repository navigation
Developer Architecture and Extensibility
| Directory | Role |
|---|---|
include/App, src/App
|
Front end: configuration, file resolution, shipped defaults, exit policy, runners |
include/Config |
ProgramConfig, one struct per configuration section |
include/Process, src/Process
|
Process launching, IPC/HTTP layer, tool implementations |
include/ctrace_tools |
Shared helpers: string splitting, language detection, mangling, colors |
-
buildConfig(argc, argv)produces aConfigResult: theProgramConfig, plus any output, warnings, error text and exit code. It never exits the process itself. - Output, warnings and errors are printed; without a config,
mainreturns the reported exit code. - The logger is configured (prefix, minimum level from
output.verbose). - Branch:
runtime.ipc == "serve"→run_server(config, executable), otherwise →run_cli_analysis(config). - Any escaping exception is caught and logged rather than aborting the process.
- Resolve source files (
resolveSourceFiles); a resolution failure is a usage error. - Instantiate
ToolInvoker(thread-pool size from hardware concurrency, capped at 255). - Run the selected groups: static, dynamic, then explicitly invoked tools.
- In SARIF mode, render the merged document and write it to
output.report_file. - Compute the verdict with
exitCodeFor(fail_on, outcome)and return it.
- Build an
HttpServeraround anApiHandler, with theserverconfiguration section. -
POST /apirequests are translated into the same sectioned config document and run through the same orchestration. - The response carries the normalized diagnostics, the summary, the SARIF document when asked,
the same verdict the CLI would exit with (
result.gate), and the captured per-tool output.
The CLI, the config file and an HTTP request all build the same sectioned JSON document and
apply it through the same loader (applyToolConfigObject). There is one validation and
normalization path, so a value behaves identically whichever way it arrived. Diagnostics coming
out of the loader name the internal section key and are rewritten to the spelling the caller
used — the CLI option, or the flat request parameter.
See Developer Build and Configuration.
applyShippedDefaults() runs last, after the config file and the CLI or request, so an explicit
value always wins. It fills two things from what sits next to the running binary — located
through the OS (_NSGetExecutablePath, /proc/self/exe) rather than argv[0], because an
installed binary is usually started through PATH:
-
Stack analyzer models, from the first existing of
<exe dir>/../config/models(install prefix, and abuild/directory inside the repo) and<exe dir>/config/models(any build directory; CMake copies the models there). When the analyzer is selected and a default model is missing, a warning names it, so losing a rule family is visible instead of showing up as a clean report. -
Bundled external tools, from the first executable of
<exe dir>/../libexec/coretrace/<tool>/<tool>and<exe dir>/libexec/coretrace/<tool>/<tool>. Tools without a bundled copy keep being resolved throughPATH, so a release archive runs without anything installed.
Resolution order for a tool executable is therefore: tools.paths → bundled copy → PATH.
Every tool normalizes its own output format into one Diagnostic
(include/Process/Tools/Diagnostic.hpp):
tool, ruleId, file, line, column, severity (info|warning|error), message, cwe
Counting, gating, SARIF rendering and the HTTP response only ever see this model. A tool that never produces structured findings is reported as uninterpreted — distinct from a tool with zero findings — so output that nothing counted stays visible instead of reading as a clean run.
AnalysisOutcome (the severity counters plus "did any tool fail") and FailOn produce the exit
code through exitCodeFor() in include/App/ExitPolicy.hpp: 0 clean, 1 usage, 2 findings,
3 incomplete. An incomplete analysis wins over findings — its counters cannot be trusted, and
--fail-on none only silences findings, never a broken toolchain. The server returns the same
verdict in result.gate rather than exiting.
renderSarif() turns the collected diagnostics into one SARIF 2.1.0 log: one run per tool,
rules collected per tool, a coretrace/v1 partial fingerprint per result, and the CWE as a
result property. Findings that match on file, line and CWE are merged into a single result whose
alsoReportedBy property names the other tools and rules; reports without a CWE cannot be
matched and are all kept.
-
Strategy:
IAnalysisToolfor pluggable tools,IpcStrategyfor the (deprecated) socket transport. -
Factory:
ProcessFactorypicks the platform process implementation —WindowsProcesson_WIN32,UnixProcesselsewhere. -
Template method:
Process::execute()enforcesprepare → run → cleanup.
Tools are integrated independently of orchestration: orchestration stays centralized in
ToolInvoker, tool behavior stays local to its implementation, and process execution and
transport are replaceable without touching the tool interface.
Tool output does not go through the IPC strategy any more. Each execution receives a
ToolOutput sink (include/Process/Tools/ToolOutput.hpp) that:
- records the tool's report text and errors in a
CaptureBufferwhen there is one (the HTTP server turns it intoresult.outputs), - mirrors them to stdout/stderr when asked to,
- collects the structured
Diagnosticlist for the invoker.
Status and progress messages are not tool output: they go through the logger. CaptureBuffer
is mutex-guarded, so async scheduling is safe.
Captured line format: stream (stdout or stderr) and message — a raw string, or parsed
JSON when the captured text is valid JSON.
Registered tools:
| Group | Tools |
|---|---|
| static |
cppcheck, flawfinder, tscancode, ikos, ctrace_stack_analyzer, coretrace-python-analyzer
|
| dynamic |
dyn_tools_1, dyn_tools_2, dyn_tools_3
|
Execution modes: deferred/sequential (std::launch::deferred) or parallel
(std::launch::async) over an internal thread pool.
The invoker detects each file's language (ctrace_tools::detectLanguage) and hands a tool only
the files it declares through analyzes(). The default implementation covers C and C++; the
Python analyzer overrides it for LanguageType::Python. A mixed --input therefore dispatches
.py files and C/C++ files to different tools in the same run.
Tools run per file through execute(file, config, output). A tool opts into one run with the
whole input list by overriding supportsBatchExecution() and executeBatch(files, config, output); the default executeBatch loops over execute. ctrace_stack_analyzer and
coretrace-python-analyzer are the batch-enabled tools.
-
standardIO(default): no IPC strategy is installed; results go to theToolOutputsink. -
socket: deprecated. Installing it prints the deprecation notice and attaches aUnixSocketStrategyto every tool. Only some tools ever wrote to it and nothing in the project reads from it; it will be removed. -
serve: HTTP server mode. Inside a request the analysis runs in-process, so the runtime IPC is normalized tostandardIOand the outputs are captured for the JSON response.
- Implement a class deriving
IAnalysisTool(orAnalysisToolBase). - Implement
execute(const std::string&, const ProgramConfig&, ToolOutput&) constandname() const. - Normalize the tool's output into
Diagnosticvalues and hand them to the sink withoutput.diagnostics(...)— that is what makes findings counted, gated and rendered to SARIF. A tool that only writes text is reported as uninterpreted. - Optional:
supportsBatchExecution()/executeBatch()when the tool handles a multi-file input efficiently;analyzes()when it is not a C/C++ analyzer. - Register it in the
ToolInvokerconstructor (registerTool("tool_name", ...)) and add it to the static or dynamic group if it belongs there. - Add the name to
SUPPORTED_TOOLSininclude/App/SupportedTools.hpp— this is what validates--invokeandparams.invoke, and what the error message lists. - Add its path and arguments handling under the
toolsconfig section if it is an external binary. - Validate through both front ends:
--invoke tool_nameandparams.invoke. - Update
docs/configuration.mdand the wiki pages so the behavior stays discoverable.
-
ikosis registered and part of the static group, but its output is not normalized into diagnostics, so its findings are not counted and do not affect the verdict; it shows up inuninterpreted_tools. Its CMake integration is commented out of the build. - The
--invokehelp text omitscoretrace-python-analyzer, which the validator accepts. -
output.output_fileis carried through the whole configuration path but no tool consumes it. - The Unix socket transport (
--ipc=socket) is kept working for existing callers only.