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
47 changes: 41 additions & 6 deletions src/analysis/ResourceLifetimeAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <llvm/ADT/SmallPtrSet.h>
#include <llvm/ADT/SmallVector.h>
#include <llvm/ADT/StringMap.h>
#include <llvm/Analysis/CFG.h>
#include <llvm/Analysis/CaptureTracking.h>
#include <llvm/Analysis/ValueTracking.h>
Expand Down Expand Up @@ -1966,20 +1967,54 @@ namespace ctrace::stack::analysis

using namespace lifetime_detail;

namespace
{
struct DemangledNames
{
std::string demangled;
std::string demangledBase;
};

// A rule is matched against a callee once per rule of the model, at every
// iteration: demangle each name once instead. The entry is keyed on the mangled
// name, of which it is a pure function, so it stays valid after the module that
// held the callee is freed. One cache per thread: the analysis threads share
// nothing. Cleared when full, which only bounds its memory.
const DemangledNames& demangledNamesOf(llvm::StringRef mangled)
{
constexpr std::size_t kMaxCachedNames = 1u << 16;
static thread_local llvm::StringMap<DemangledNames> cache;

const auto it = cache.find(mangled);
if (it != cache.end())
return it->second;
if (cache.size() >= kMaxCachedNames)
cache.clear();

const std::string mangledName = mangled.str();
DemangledNames names;
names.demangled = ctrace_tools::demangle(mangledName.c_str());
names.demangledBase = names.demangled;
if (const std::size_t pos = names.demangledBase.find('('); pos != std::string::npos)
names.demangledBase = names.demangledBase.substr(0, pos);

return cache.try_emplace(mangled, std::move(names)).first->second;
}
} // namespace

bool ruleMatchesFunction(const ResourceRule& rule, const llvm::Function& callee)
{
const std::string calleeName = callee.getName().str();
const std::string demangled = ctrace_tools::demangle(calleeName.c_str());
std::string demangledBase = demangled;
if (const std::size_t pos = demangledBase.find('('); pos != std::string::npos)
demangledBase = demangledBase.substr(0, pos);
const llvm::StringRef calleeName = callee.getName();
const DemangledNames& names = demangledNamesOf(calleeName);
const std::string& demangled = names.demangled;
const std::string& demangledBase = names.demangledBase;

const llvm::StringRef pattern(rule.functionPattern);
const bool hasGlob =
pattern.contains('*') || pattern.contains('?') || pattern.contains('[');
if (!hasGlob)
{
return rule.functionPattern == calleeName || rule.functionPattern == demangled ||
return pattern == calleeName || rule.functionPattern == demangled ||
rule.functionPattern == demangledBase;
}

Expand Down
92 changes: 92 additions & 0 deletions test/unit/analyzer_module_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,97 @@ namespace
"ResourceModel: an unknown qualifier is an error naming the line");
return report.failures == 0;
}
// Pins which names a rule's pattern is compared with: the mangled name, the
// demangled name and the demangled name up to its first '(', exactly or as a glob.
bool testResourceRuleMatchesFunction(TestReport& report)
{
using namespace ctrace::stack::analysis;

struct Case
{
const char* pattern;
const char* function;
bool expected;
};
// _ZN2ns6Handle4openEPKc -> ns::Handle::open(char const*)
// _ZN2ns6Handle5closeEv -> ns::Handle::close()
// _Z11acquire_bufPPv -> acquire_buf(void**)
// fopen, _Zbogus -> left unchanged by the demangler
// f -> float (the demangler reads it as a type)
const Case cases[] = {
// A C name the demangler leaves unchanged.
{"fopen", "fopen", true},
{"fope", "fopen", false},
{"FOPEN", "fopen", false},
{"fop*", "fopen", true},
{"f?pen", "fopen", true},
{"*open", "fopen", true},
{"fopen*", "fopen", true},
{"?open", "fopen", true},
{"??open", "fopen", false},
{"_Zbogus", "_Zbogus", true},
{"_Zbog*", "_Zbogus", true},
// Exact patterns against each of the three names.
{"_ZN2ns6Handle4openEPKc", "_ZN2ns6Handle4openEPKc", true},
{"ns::Handle::open(char const*)", "_ZN2ns6Handle4openEPKc", true},
{"ns::Handle::open", "_ZN2ns6Handle4openEPKc", true},
{"ns::Handle::open", "_ZN2ns6Handle5closeEv", false},
{"open", "_ZN2ns6Handle4openEPKc", false},
{"Handle::open", "_ZN2ns6Handle4openEPKc", false},
{"ns::Handle::open(", "_ZN2ns6Handle4openEPKc", false},
{"acquire_buf", "_Z11acquire_bufPPv", true},
{"acquire_buf(void**)", "_Z11acquire_bufPPv", true},
{"ns::Handle::close()", "_ZN2ns6Handle5closeEv", true},
{"float", "f", true},
{"f", "f", true},
// Globs against each of the three names.
{"_ZN2ns*", "_ZN2ns6Handle4openEPKc", true},
{"*4openEPKc", "_ZN2ns6Handle4openEPKc", true},
{"ns::Handle::*", "_ZN2ns6Handle4openEPKc", true},
{"ns::Handle::*", "_ZN2ns6Handle5closeEv", true},
{"*(char const*)", "_ZN2ns6Handle4openEPKc", true},
{"*(char const*)", "_ZN2ns6Handle5closeEv", false},
{"ns::*::open", "_ZN2ns6Handle4openEPKc", true},
{"ns::*::open", "_ZN2ns6Handle5closeEv", false},
{"*open", "_ZN2ns6Handle4openEPKc", true},
{"*::clos?", "_ZN2ns6Handle5closeEv", true},
{"acquire_*", "_Z11acquire_bufPPv", true},
{"release_*", "_Z11acquire_bufPPv", false},
{"flo*", "f", true},
// '[' selects glob matching but is compared literally.
{"a[b", "a[b", true},
{"a[b", "ab", false},
{"a[b*", "a[bc", true},
};

const auto check = [&](const char* round)
{
llvm::LLVMContext context;
llvm::Module module("rule-match", context);
llvm::FunctionType* type =
llvm::FunctionType::get(llvm::Type::getVoidTy(context), false);
for (const Case& c : cases)
{
llvm::Function* callee = module.getFunction(c.function);
if (callee == nullptr)
{
callee = llvm::Function::Create(type, llvm::Function::ExternalLinkage,
c.function, &module);
}
ResourceRule rule;
rule.functionPattern = c.pattern;
report.expect(ruleMatchesFunction(rule, *callee) == c.expected,
std::string("ResourceModel: ") + round + " pattern '" + c.pattern +
(c.expected ? "' matches '" : "' does not match '") + c.function +
"'");
}
};
// Twice, each time in a fresh module: results do not depend on what was matched
// before nor on which llvm::Function carries the name.
check("first round,");
check("second round,");
return report.failures == 0;
}
bool testOwnershipFactCollector(const std::filesystem::path& repoRoot, TestReport& report)
{
using namespace ctrace::stack::analysis;
Expand Down Expand Up @@ -2924,6 +3015,7 @@ int main(int argc, char** argv)
(void)testStackBufferPhiCycle(repoRoot, report);
(void)testStackBufferPointerRing(report);
(void)testResourceModelConditions(report);
(void)testResourceRuleMatchesFunction(report);
(void)testOwnershipFactCollector(repoRoot, report);
(void)testOwnershipCollectorExceptions(repoRoot, report);
(void)testOwnershipSummaryIndex(repoRoot, report);
Expand Down
Loading