• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tudasc / TypeART / 20109041671

10 Dec 2025 06:22PM UTC coverage: 90.41% (+0.03%) from 90.38%
20109041671

push

github

web-flow
Improve sanitizer handling (#178)

59 of 66 new or added lines in 4 files covered. (89.39%)

4808 of 5318 relevant lines covered (90.41%)

347058.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.44
/lib/passes/analysis/MemInstFinder.cpp
1
// TypeART library
2
//
3
// Copyright (c) 2017-2025 TypeART Authors
4
// Distributed under the BSD 3-Clause license.
5
// (See accompanying file LICENSE.txt or copy at
6
// https://opensource.org/licenses/BSD-3-Clause)
7
//
8
// Project home: https://github.com/tudasc/TypeART
9
//
10
// SPDX-License-Identifier: BSD-3-Clause
11
//
12

13
#include "MemInstFinder.h"
14

15
#include "MemOpVisitor.h"
16
#include "TypeARTConfiguration.h"
17
#include "analysis/MemOpData.h"
18
#include "configuration/Configuration.h"
19
#include "configuration/TypeARTOptions.h"
20
#include "filter/CGForwardFilter.h"
21
#include "filter/CGInterface.h"
22
#include "filter/Filter.h"
23
#include "filter/Matcher.h"
24
#include "filter/StdForwardFilter.h"
25
#include "support/ConfigurationBase.h"
26
#include "support/Logger.h"
27
#include "support/Table.h"
28
#include "support/TypeUtil.h"
29
#include "support/Util.h"
30

31
#include "llvm/ADT/STLExtras.h"
32
#include "llvm/ADT/Statistic.h"
33
#include "llvm/ADT/StringRef.h"
34
#include "llvm/IR/BasicBlock.h"
35
#include "llvm/IR/DerivedTypes.h"
36
#include "llvm/IR/Function.h"
37
#include "llvm/IR/GlobalValue.h"
38
#include "llvm/IR/Instructions.h"
39
#include "llvm/IR/Module.h"
40
#include "llvm/IR/Type.h"
41
#include "llvm/Support/Casting.h"
42
#include "llvm/Support/raw_ostream.h"
43

44
#include <algorithm>
45
#include <cstdlib>
46
#include <llvm/ADT/ScopeExit.h>
47
#include <sstream>
48
#include <string>
49
#include <utility>
50

51
using namespace llvm;
52

53
#define DEBUG_TYPE "MemInstFinder"
54
ALWAYS_ENABLED_STATISTIC(NumDetectedHeap, "Number of detected heap allocs");
55
ALWAYS_ENABLED_STATISTIC(NumFilteredDetectedHeap, "Number of filtered heap allocs");
56
ALWAYS_ENABLED_STATISTIC(NumDetectedAllocs, "Number of detected allocs");
57
ALWAYS_ENABLED_STATISTIC(NumFilteredPointerAllocs, "Number of filtered pointer allocs");
58
ALWAYS_ENABLED_STATISTIC(NumCallFilteredAllocs, "Number of call filtered allocs");
59
ALWAYS_ENABLED_STATISTIC(NumFilteredMallocAllocs, "Number of  filtered  malloc-related allocs");
60
ALWAYS_ENABLED_STATISTIC(NumFilteredNonArrayAllocs, "Number of filtered non-array allocs");
61
ALWAYS_ENABLED_STATISTIC(NumDetectedGlobals, "Number of detected globals");
62
ALWAYS_ENABLED_STATISTIC(NumFilteredGlobals, "Number of filtered globals");
63
ALWAYS_ENABLED_STATISTIC(NumCallFilteredGlobals, "Number of filtered globals");
64

65
namespace typeart::analysis {
66

67
using MemInstFinderConfig = config::Configuration;
68

69
namespace filter {
70
class CallFilter {
71
  std::unique_ptr<typeart::filter::Filter> fImpl;
72

73
 public:
74
  explicit CallFilter(const MemInstFinderConfig& config);
75
  CallFilter(const CallFilter&) = delete;
76
  CallFilter(CallFilter&&)      = default;
77
  bool operator()(llvm::AllocaInst*);
78
  bool operator()(llvm::GlobalValue*);
79
  CallFilter& operator=(CallFilter&&) noexcept;
80
  CallFilter& operator=(const CallFilter&) = delete;
81
  virtual ~CallFilter();
82
};
83

84
}  // namespace filter
85

86
namespace filter {
87

88
namespace detail {
89
static std::unique_ptr<typeart::filter::Filter> make_filter(const MemInstFinderConfig& config) {
4,750✔
90
  using namespace typeart::filter;
91
  const bool filter                    = config[config::ConfigStdArgs::filter];
4,750✔
92
  const FilterImplementation filter_id = config[config::ConfigStdArgs::filter_impl];
4,750✔
93
  const std::string glob               = config[config::ConfigStdArgs::filter_glob];
4,750✔
94

95
  if (filter_id == FilterImplementation::none || !filter) {
4,750✔
96
    LOG_DEBUG("Return no-op filter")
97
    return std::make_unique<NoOpFilter>();
4,214✔
98
  } else if (filter_id == FilterImplementation::cg) {
536✔
99
    const std::string cg_file = config[config::ConfigStdArgs::filter_cg_file];
52✔
100
    if (cg_file.empty()) {
52✔
101
      LOG_FATAL("CG File not set!");
×
102
      std::exit(1);
×
103
    }
104
    LOG_DEBUG("Return CG filter with CG file @ " << cg_file)
105
    auto json_cg = JSONCG::getJSON(cg_file);
52✔
106
    auto matcher = std::make_unique<DefaultStringMatcher>(util::glob2regex(glob));
52✔
107
    return std::make_unique<CGForwardFilter>(glob, std::move(json_cg), std::move(matcher));
52✔
108
  } else {
52✔
109
    LOG_DEBUG("Return default filter")
110
    auto matcher         = std::make_unique<DefaultStringMatcher>(util::glob2regex(glob));
484✔
111
    const auto deep_glob = config[config::ConfigStdArgs::filter_glob_deep];
484✔
112
    auto deep_matcher    = std::make_unique<DefaultStringMatcher>(util::glob2regex(deep_glob));
484✔
113
    return std::make_unique<StandardForwardFilter>(std::move(matcher), std::move(deep_matcher));
484✔
114
  }
484✔
115
}
4,750✔
116
}  // namespace detail
117

118
CallFilter::CallFilter(const MemInstFinderConfig& config) : fImpl{detail::make_filter(config)} {
4,750✔
119
}
4,750✔
120

121
bool CallFilter::operator()(AllocaInst* allocation) {
3,982✔
122
  LOG_DEBUG("Analyzing value: " << util::dump(*allocation));
123
  fImpl->setMode(/*search mallocs = */ false);
3,982✔
124
  fImpl->setStartingFunction(allocation->getParent()->getParent());
3,982✔
125
  const auto filter_ = fImpl->filter(allocation);
3,982✔
126
  if (filter_) {
3,982✔
127
    LOG_DEBUG("Filtering value: " << util::dump(*allocation) << "\n");
128
  } else {
3,414✔
129
    LOG_DEBUG("Keeping value: " << util::dump(*allocation) << "\n");
130
  }
131
  return filter_;
3,982✔
132
}
133

134
bool CallFilter::operator()(GlobalValue* global_value) {
20,865✔
135
  LOG_DEBUG("Analyzing value: " << util::dump(*global_value));
136
  fImpl->setMode(/*search mallocs = */ false);
20,865✔
137
  fImpl->setStartingFunction(nullptr);
20,865✔
138
  const auto filter_ = fImpl->filter(global_value);
20,865✔
139
  if (filter_) {
20,865✔
140
    LOG_DEBUG("Filtering value: " << util::dump(*global_value) << "\n");
141
  } else {
447✔
142
    LOG_DEBUG("Keeping value: " << util::dump(*global_value) << "\n");
143
  }
144
  return filter_;
20,865✔
145
}
146

147
CallFilter& CallFilter::operator=(CallFilter&&) noexcept = default;
×
148

149
CallFilter::~CallFilter() = default;
4,750✔
150

151
}  // namespace filter
152

153
class MemInstFinderPass : public MemInstFinder {
154
 private:
155
  MemOpVisitor mOpsCollector;
156
  filter::CallFilter filter;
157
  llvm::DenseMap<const llvm::Function*, FunctionData> functionMap;
158
  const MemInstFinderConfig& config;
159

160
 public:
161
  explicit MemInstFinderPass(const MemInstFinderConfig&);
162
  bool runOnModule(llvm::Module&) override;
163
  [[nodiscard]] bool hasFunctionData(const llvm::Function&) const override;
164
  [[nodiscard]] const FunctionData& getFunctionData(const llvm::Function&) const override;
165
  const GlobalDataList& getModuleGlobals() const override;
166
  void printStats(llvm::raw_ostream&) const override;
167
  // void configure(MemInstFinderConfig&) override;
168
  ~MemInstFinderPass() override = default;
9,500✔
169

170
 private:
171
  bool runOnFunction(llvm::Function&);
172
};
173

174
MemInstFinderPass::MemInstFinderPass(const MemInstFinderConfig& conf_)
11,212✔
175
    : mOpsCollector(conf_), filter(conf_), config(conf_) {
11,212✔
176
}
4,750✔
177

178
bool MemInstFinderPass::runOnModule(Module& module) {
4,750✔
179
  mOpsCollector.collectGlobals(module);
4,750✔
180
  auto& globals = mOpsCollector.globals;
4,750✔
181
  NumDetectedGlobals += globals.size();
4,750✔
182
  if (config[config::ConfigStdArgs::analysis_filter_global]) {
4,750✔
183
    globals.erase(
9,500✔
184
        llvm::remove_if(
4,750✔
185
            globals,
4,750✔
186
            [&](const auto gdata) {  // NOLINT
184,950✔
187
              GlobalVariable* global = gdata.global;
184,950✔
188
              const auto name        = global->getName();
184,950✔
189

190
              LOG_DEBUG("Analyzing global: " << name);
191

192
              if (name.empty()) {
184,950✔
193
                return true;
140,075✔
194
              }
195

196
              if (util::starts_with_any_of(name, "llvm.", "__llvm_gcov", "__llvm_gcda", "__profn", "___asan", "__msan",
44,875✔
197
                                           "__tsan", "__typeart", "_typeart", "__tysan", "__dfsan", "__profc")) {
198
                LOG_DEBUG("Prefixed matched on " << name)
199
                return true;
22,552✔
200
              }
201

202
              if (global->hasInitializer()) {
22,323✔
203
                auto* ini            = global->getInitializer();
20,877✔
204
                std::string ini_name = util::dump(*ini);
20,877✔
205

206
                if (llvm::StringRef(ini_name).contains("std::ios_base::Init")) {
20,877✔
207
                  LOG_DEBUG("std::ios");
NEW
208
                  return true;
×
209
                }
210
              }
20,877✔
211

212
              if (global->hasSection()) {
22,323✔
213
                // for instance, filters:
214
                //   a) (Coverage) -fprofile-instr-generate -fcoverage-mapping
215
                //   b) (PGO) -fprofile-instr-generate
216
                StringRef Section = global->getSection();
12✔
217
                // Globals from llvm.metadata aren't emitted, do not instrument them.
218
                if (Section == "llvm.metadata") {
12✔
219
                  LOG_DEBUG("metadata");
NEW
220
                  return true;
×
221
                }
222
                // Do not instrument globals from special LLVM sections.
223
                if (Section.find("__llvm") != StringRef::npos || Section.find("__LLVM") != StringRef::npos) {
12✔
224
                  LOG_DEBUG("llvm section");
225
                  return true;
12✔
226
                }
NEW
227
              }
×
228

229
              if ((global->getLinkage() == GlobalValue::ExternalLinkage && global->isDeclaration())) {
22,311✔
230
                LOG_DEBUG("Linkage: External");
231
                return true;
1,446✔
232
              }
233

234
              Type* global_type = global->getValueType();
20,865✔
235
              if (!global_type->isSized()) {
20,865✔
236
                LOG_DEBUG("not sized");
NEW
237
                return true;
×
238
              }
239

240
              if (global_type->isArrayTy()) {
20,865✔
241
                global_type = global_type->getArrayElementType();
19,311✔
242
              }
19,311✔
243
              if (auto structType = dyn_cast<StructType>(global_type)) {
20,865✔
244
                if (structType->isOpaque()) {
1,173✔
245
                  LOG_DEBUG("Encountered opaque struct " << global_type->getStructName() << " - skipping...");
NEW
246
                  return true;
×
247
                }
248
              }
1,173✔
249
              return false;
20,865✔
250
            }),
184,950✔
251
        globals.end());
4,750✔
252

253
    const auto beforeCallFilter = globals.size();
4,750✔
254
    NumFilteredGlobals          = NumDetectedGlobals - beforeCallFilter;
4,750✔
255

256
    globals.erase(llvm::remove_if(globals, [&](const auto global) { return filter(global.global); }), globals.end());
25,615✔
257

258
    NumCallFilteredGlobals = beforeCallFilter - globals.size();
4,750✔
259
    NumFilteredGlobals += NumCallFilteredGlobals;
4,750✔
260
  }
4,750✔
261

262
  return llvm::count_if(module.functions(), [&](auto& function) { return runOnFunction(function); }) > 0;
144,881✔
263
}  // namespace typeart
×
264

265
bool MemInstFinderPass::runOnFunction(llvm::Function& function) {
140,131✔
266
  if (function.isDeclaration() || util::starts_with_any_of(function.getName(), "__typeart")) {
140,131✔
267
    return false;
106,873✔
268
  }
269

270
  LOG_DEBUG("Running on function: " << function.getName())
271

272
  mOpsCollector.collect(function);
33,258✔
273

274
#if LLVM_VERSION_MAJOR < 15
275
  const auto checkAmbigiousMalloc = [&function](const MallocData& mallocData) {
13,003✔
276
    using namespace typeart::util::type;
277
    auto primaryBitcast = mallocData.primary;
929✔
278
    if (primaryBitcast != nullptr) {
929✔
279
      const auto& bitcasts = mallocData.bitcasts;
815✔
280
      std::for_each(bitcasts.begin(), bitcasts.end(), [&](auto bitcastInst) {
1,699✔
281
        auto dest = bitcastInst->getDestTy();
884✔
282
        if (bitcastInst != primaryBitcast &&
896✔
283
            (!isVoidPtr(dest) && !isi64Ptr(dest) &&
69✔
284
             primaryBitcast->getDestTy() != dest)) {  // void* and i64* are used by LLVM
12✔
285
          // Second non-void* bitcast detected - semantics unclear
286
          LOG_WARNING("Encountered ambiguous pointer type in function: " << util::try_demangle(function));
12✔
287
          LOG_WARNING("  Allocation" << util::dump(*(mallocData.call)));
12✔
288
          LOG_WARNING("  Primary cast: " << util::dump(*primaryBitcast));
12✔
289
          LOG_WARNING("  Secondary cast: " << util::dump(*bitcastInst));
12✔
290
        }
12✔
291
      });
884✔
292
    }
815✔
293
  };
929✔
294
#endif
295

296
  NumDetectedAllocs += mOpsCollector.allocas.size();
33,258✔
297

298
  if (config[config::ConfigStdArgs::analysis_filter_alloca_non_array]) {
33,258✔
299
    auto& allocs = mOpsCollector.allocas;
576✔
300
    allocs.erase(llvm::remove_if(allocs,
1,152✔
301
                                 [&](const auto& data) {
1,638✔
302
                                   if (!data.alloca->getAllocatedType()->isArrayTy() && data.array_size == 1) {
1,638✔
303
                                     ++NumFilteredNonArrayAllocs;
1,584✔
304
                                     return true;
1,584✔
305
                                   }
306
                                   return false;
54✔
307
                                 }),
1,638✔
308
                 allocs.end());
576✔
309
  }
576✔
310

311
  if (config[config::ConfigStdArgs::analysis_filter_heap_alloc]) {
33,258✔
312
    auto& allocs  = mOpsCollector.allocas;
42✔
313
    auto& mallocs = mOpsCollector.mallocs;
42✔
314

315
    const auto filterMallocAllocPairing = [&mallocs](const auto alloc) {
72✔
316
      // Only look for the direct users of the alloc:
317
      // TODO is a deeper analysis required?
318
      for (auto inst : alloc->users()) {
108✔
319
        if (StoreInst* store = dyn_cast<StoreInst>(inst)) {
84✔
320
          const auto source = store->getValueOperand();
30✔
321
          if (isa<BitCastInst>(source)) {
30✔
322
            for (auto& mdata : mallocs) {
18✔
323
              // is it a bitcast we already collected? if yes, we can filter the alloc
324
              return std::any_of(mdata.bitcasts.begin(), mdata.bitcasts.end(),
12✔
325
                                 [&source](const auto bcast) { return bcast == source; });
12✔
326
            }
327
          } else if (isa<CallInst>(source)) {
24✔
328
            return std::any_of(mallocs.begin(), mallocs.end(),
×
329
                               [&source](const auto& mdata) { return mdata.call == source; });
×
330
          }
331
        }
24✔
332
      }
333
      return false;
24✔
334
    };
30✔
335

336
    allocs.erase(llvm::remove_if(allocs,
126✔
337
                                 [&](const auto& data) {
72✔
338
                                   if (filterMallocAllocPairing(data.alloca)) {
30✔
339
                                     ++NumFilteredMallocAllocs;
6✔
340
                                     return true;
6✔
341
                                   }
342
                                   return false;
24✔
343
                                 }),
30✔
344
                 allocs.end());
42✔
345
  }
42✔
346

347
  if (config[config::ConfigStdArgs::analysis_filter_pointer_alloc]) {
33,258✔
348
    auto& allocs = mOpsCollector.allocas;
32,360✔
349
    allocs.erase(llvm::remove_if(allocs,
64,720✔
350
                                 [&](const auto& data) {
27,785✔
351
                                   auto alloca = data.alloca;
27,785✔
352
                                   if (!data.is_vla && isa<llvm::PointerType>(alloca->getAllocatedType())) {
27,785✔
353
                                     ++NumFilteredPointerAllocs;
11,802✔
354
                                     return true;
11,802✔
355
                                   }
356
                                   return false;
15,983✔
357
                                 }),
27,785✔
358
                 allocs.end());
32,360✔
359
  }
32,360✔
360

361
  // if (config.filter.useCallFilter) {
362
  if (config[config::ConfigStdArgs::filter]) {
33,258✔
363
    auto& allocs = mOpsCollector.allocas;
1,970✔
364
    allocs.erase(llvm::remove_if(allocs,
5,910✔
365
                                 [&](const auto& data) {
5,952✔
366
                                   if (filter(data.alloca)) {
3,982✔
367
                                     ++NumCallFilteredAllocs;
3,414✔
368
                                     return true;
3,414✔
369
                                   }
370
                                   return false;
568✔
371
                                 }),
3,982✔
372
                 allocs.end());
1,970✔
373
    //    LOG_DEBUG(allocs.size() << " allocas to instrument : " << util::dump(allocs));
374
  }
1,970✔
375

376
  auto& mallocs = mOpsCollector.mallocs;
33,258✔
377
  NumDetectedHeap += mallocs.size();
33,258✔
378

379
#if LLVM_VERSION_MAJOR < 15
380
  for (const auto& mallocData : mallocs) {
13,003✔
381
    checkAmbigiousMalloc(mallocData);
929✔
382
  }
383
#endif
384

385
  FunctionData data{mOpsCollector.mallocs, mOpsCollector.frees, mOpsCollector.allocas};
33,258✔
386
  functionMap[&function] = data;
33,258✔
387

388
  mOpsCollector.clear();
33,258✔
389

390
  return true;
33,258✔
391
}  // namespace typeart
140,131✔
392

393
void MemInstFinderPass::printStats(llvm::raw_ostream& out) const {
4,696✔
394
  const auto scope_exit_cleanup_counter = llvm::make_scope_exit([&]() {
9,392✔
395
    NumDetectedAllocs         = 0;
4,696✔
396
    NumFilteredNonArrayAllocs = 0;
4,696✔
397
    NumFilteredMallocAllocs   = 0;
4,696✔
398
    NumCallFilteredAllocs     = 0;
4,696✔
399
    NumFilteredPointerAllocs  = 0;
4,696✔
400
    NumDetectedHeap           = 0;
4,696✔
401
    NumFilteredGlobals        = 0;
4,696✔
402
    NumDetectedGlobals        = 0;
4,696✔
403
  });
4,696✔
404
  auto all_stack                        = double(NumDetectedAllocs);
4,696✔
405
  auto nonarray_stack                   = double(NumFilteredNonArrayAllocs);
4,696✔
406
  auto malloc_alloc_stack               = double(NumFilteredMallocAllocs);
4,696✔
407
  auto call_filter_stack                = double(NumCallFilteredAllocs);
4,696✔
408
  auto filter_pointer_stack             = double(NumFilteredPointerAllocs);
4,696✔
409

410
  const auto call_filter_stack_p =
4,696✔
411
      (call_filter_stack /
4,696✔
412
       std::max<double>(1.0, all_stack - nonarray_stack - malloc_alloc_stack - filter_pointer_stack)) *
4,696✔
413
      100.0;
414

415
  const auto call_filter_heap_p =
4,696✔
416
      (double(NumFilteredDetectedHeap) / std::max<double>(1.0, double(NumDetectedHeap))) * 100.0;
4,696✔
417

418
  const auto call_filter_global_p =
4,696✔
419
      (double(NumCallFilteredGlobals) / std::max(1.0, double(NumDetectedGlobals))) * 100.0;
4,696✔
420

421
  const auto call_filter_global_nocallfilter_p =
4,696✔
422
      (double(NumFilteredGlobals) / std::max(1.0, double(NumDetectedGlobals))) * 100.0;
4,696✔
423

424
  Table stats("MemInstFinderPass");
4,696✔
425
  stats.wrap_header_ = true;
4,696✔
426
  stats.wrap_length_ = true;
4,696✔
427
  std::string glob   = config[config::ConfigStdArgs::filter_glob];
4,696✔
428
  stats.put(Row::make("Filter string", glob));
4,696✔
429
  stats.put(Row::make_row("> Heap Memory"));
4,696✔
430
  stats.put(Row::make("Heap alloc", NumDetectedHeap.getValue()));
4,696✔
431
  stats.put(Row::make("Heap call filtered %", call_filter_heap_p));
4,696✔
432
  stats.put(Row::make_row("> Stack Memory"));
4,696✔
433
  stats.put(Row::make("Alloca", all_stack));
4,696✔
434
  stats.put(Row::make("Stack call filtered %", call_filter_stack_p));
4,696✔
435
  stats.put(Row::make("Alloca of pointer discarded", filter_pointer_stack));
4,696✔
436
  stats.put(Row::make_row("> Global Memory"));
4,696✔
437
  stats.put(Row::make("Global", NumDetectedGlobals.getValue()));
4,696✔
438
  stats.put(Row::make("Global filter total", NumFilteredGlobals.getValue()));
4,696✔
439
  stats.put(Row::make("Global call filtered %", call_filter_global_p));
4,696✔
440
  stats.put(Row::make("Global filtered %", call_filter_global_nocallfilter_p));
4,696✔
441

442
  std::ostringstream stream;
4,696✔
443
  stats.print(stream);
4,696✔
444
  out << stream.str();
4,696✔
445
}
4,696✔
446

447
bool MemInstFinderPass::hasFunctionData(const Function& function) const {
33,246✔
448
  auto iter = functionMap.find(&function);
33,246✔
449
  return iter != functionMap.end();
33,246✔
450
}
451

452
const FunctionData& MemInstFinderPass::getFunctionData(const Function& function) const {
33,246✔
453
  auto iter = functionMap.find(&function);
33,246✔
454
  return iter->second;
33,246✔
455
}
456

457
const GlobalDataList& MemInstFinderPass::getModuleGlobals() const {
2,314✔
458
  return mOpsCollector.globals;
2,314✔
459
}
460

461
std::unique_ptr<MemInstFinder> create_finder(const config::Configuration& config) {
4,750✔
462
  LOG_DEBUG("Constructing MemInstFinder")
463
  // const auto meminst_conf = config::helper::config_to_options(config);
464
  return std::make_unique<MemInstFinderPass>(config);
4,750✔
465
}
466

467
}  // namespace typeart::analysis
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc