Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
--format=json|sarif|human
--quiet disables diagnostics entirely
--warnings-only keeps only important diagnostics
--stack-limit=<value> overrides stack limit (bytes, or KiB/MiB/GiB)
--compile-arg=<arg> passes an extra argument to the compiler
-I<dir> or -I <dir> adds an include directory
-D<name>[=value] or -D <name>[=value] defines a macro
Expand Down
113 changes: 113 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include "StackUsageAnalyzer.hpp"

#include <algorithm>
#include <charconv>
#include <cctype>
#include <cstring> // strncmp, strcmp
#include <filesystem>
#include <iostream>
#include <limits>
#include <unordered_set>
#include <vector>
#include <llvm/IR/LLVMContext.h>
Expand Down Expand Up @@ -39,6 +41,7 @@ static void printHelp()
<< " --only-file=<path> Only report functions from this source file\n"
<< " --only-dir=<path> Only report functions under this directory\n"
<< " --only-func=<name> Only report functions with this name (comma-separated)\n"
<< " --stack-limit=<value> Override stack size limit (bytes, or KiB/MiB/GiB)\n"
<< " --dump-filter Print filter decisions to stderr\n"
<< " --quiet Suppress per-function diagnostics\n"
<< " --warnings-only Show warnings and errors only\n"
Expand Down Expand Up @@ -253,6 +256,87 @@ static std::string trimCopy(const std::string& input)
return input.substr(start, end - start);
}

static bool parseStackLimitValue(const std::string& input, StackSize& out, std::string& error)
{
std::string trimmed = trimCopy(input);
if (trimmed.empty())
{
error = "stack limit is empty";
return false;
}

std::size_t digitCount = 0;
while (digitCount < trimmed.size() &&
std::isdigit(static_cast<unsigned char>(trimmed[digitCount])))
{
++digitCount;
}
if (digitCount == 0)
{
error = "stack limit must start with a number";
return false;
}

const std::string numberPart = trimmed.substr(0, digitCount);
std::string suffix = trimCopy(trimmed.substr(digitCount));

unsigned long long base = 0;
auto [ptr, ec] =
std::from_chars(numberPart.data(), numberPart.data() + numberPart.size(), base, 10);
if (ec != std::errc() || ptr != numberPart.data() + numberPart.size())
{
error = "invalid numeric value";
return false;
}
if (base == 0)
{
error = "stack limit must be greater than zero";
return false;
}

StackSize multiplier = 1;
if (!suffix.empty())
{
std::string lowered;
lowered.reserve(suffix.size());
for (char c : suffix)
{
lowered.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));
}

if (lowered == "b")
{
multiplier = 1;
}
else if (lowered == "k" || lowered == "kb" || lowered == "kib")
{
multiplier = 1024ull;
}
else if (lowered == "m" || lowered == "mb" || lowered == "mib")
{
multiplier = 1024ull * 1024ull;
}
else if (lowered == "g" || lowered == "gb" || lowered == "gib")
{
multiplier = 1024ull * 1024ull * 1024ull;
}
else
{
error = "unsupported suffix (use bytes, KiB, MiB, or GiB)";
return false;
}
}

if (base > std::numeric_limits<StackSize>::max() / multiplier)
{
error = "stack limit is too large";
return false;
}

out = static_cast<StackSize>(base) * multiplier;
return true;
}

static void addFunctionFilters(std::vector<std::string>& dest, const std::string& input)
{
std::string current;
Expand Down Expand Up @@ -421,6 +505,35 @@ int main(int argc, char** argv)
cfg.onlyDirs.emplace_back(argv[++i]);
continue;
}
if (argStr == "--stack-limit")
{
if (i + 1 >= argc)
{
llvm::errs() << "Missing argument for --stack-limit\n";
return 1;
}
std::string error;
StackSize value = 0;
if (!parseStackLimitValue(argv[++i], value, error))
{
llvm::errs() << "Invalid --stack-limit value: " << error << "\n";
return 1;
}
cfg.stackLimit = value;
continue;
}
if (argStr.rfind("--stack-limit=", 0) == 0)
{
std::string error;
StackSize value = 0;
if (!parseStackLimitValue(argStr.substr(std::strlen("--stack-limit=")), value, error))
{
llvm::errs() << "Invalid --stack-limit value: " << error << "\n";
return 1;
}
cfg.stackLimit = value;
continue;
}
if (argStr == "--dump-filter")
{
cfg.dumpFilter = true;
Expand Down
26 changes: 20 additions & 6 deletions run_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def extract_expectations(c_path: Path):
"""
expectations = []
negative_expectations = []
stack_limit = None
lines = c_path.read_text().splitlines()
i = 0
n = len(lines)
Expand All @@ -47,6 +48,12 @@ def extract_expectations(c_path: Path):
raw = lines[i]
stripped = raw.lstrip()

stack_match = re.match(r"//\s*stack-limit\s*[:=]\s*(\S+)", stripped, re.IGNORECASE)
if stack_match:
stack_limit = stack_match.group(1)
i += 1
continue

stripped_line = stripped
if stripped_line.startswith("// not contains:"):
negative = stripped_line[len("// not contains:"):].strip()
Expand Down Expand Up @@ -77,15 +84,18 @@ def extract_expectations(c_path: Path):
else:
i += 1

return expectations, negative_expectations
return expectations, negative_expectations, stack_limit


def run_analyzer_on_file(c_path: Path) -> str:
def run_analyzer_on_file(c_path: Path, stack_limit=None) -> str:
"""
Lance ton analyseur sur un fichier C et récupère stdout+stderr.
"""
args = [str(ANALYZER), str(c_path)]
if stack_limit:
args.append(f"--stack-limit={stack_limit}")
result = subprocess.run(
[str(ANALYZER), str(c_path)],
args,
capture_output=True,
text=True,
)
Expand Down Expand Up @@ -185,8 +195,12 @@ def parse_human_functions(output: str):
functions[name]["isRecursive"] = True
elif "unconditional self recursion detected" in stripped:
functions[name]["hasInfiniteSelfRecursion"] = True
elif "potential stack overflow: exceeds limit of" in stripped:

# Stack overflow diagnostics can appear after a location line.
for block_line in block[1:]:
if "potential stack overflow: exceeds limit of" in block_line:
functions[name]["exceedsLimit"] = True
break

i = j
return functions
Expand Down Expand Up @@ -712,12 +726,12 @@ def check_file(c_path: Path):
dans la sortie de l'analyseur.
"""
print(f"=== Testing {c_path} ===")
expectations, negative_expectations = extract_expectations(c_path)
expectations, negative_expectations, stack_limit = extract_expectations(c_path)
if not expectations and not negative_expectations:
print(" (no expectations found, skipping)\n")
return True, 0, 0

analyzer_output = run_analyzer_on_file(c_path)
analyzer_output = run_analyzer_on_file(c_path, stack_limit=stack_limit)
norm_output = normalize(analyzer_output)

all_ok = True
Expand Down
Loading
Loading