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

tudasc / TypeART / 13091819531

01 Feb 2025 07:39PM UTC coverage: 88.36%. First build
13091819531

Pull #155

github

web-flow
Merge 9129593c8 into 904382c9c
Pull Request #155: LLVM 18 testing

83 of 89 new or added lines in 8 files covered. (93.26%)

4031 of 4562 relevant lines covered (88.36%)

112157.1 hits per line

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

94.54
/lib/passes/analysis/MemOpVisitor.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 "MemOpVisitor.h"
14

15
#include "analysis/MemOpData.h"
16
#include "compat/CallSite.h"
17
#include "configuration/Configuration.h"
18
#include "support/ConfigurationBase.h"
19
#include "support/Error.h"
20
#include "support/Logger.h"
21
#include "support/TypeUtil.h"
22
#include "support/Util.h"
23

24
#include "llvm/ADT/STLExtras.h"
25
#include "llvm/ADT/SmallPtrSet.h"
26
#include "llvm/ADT/StringRef.h"
27

28
#include <llvm/Support/Error.h>
29
#include <type_traits>
30

31
#if LLVM_VERSION_MAJOR >= 12
32
#include "llvm/Analysis/ValueTracking.h"  // llvm::findAllocaForValue
33
#else
34
#include "llvm/Transforms/Utils/Local.h"  // llvm::findAllocaForValue
35
#endif
36
#include "llvm/IR/Constants.h"
37
#include "llvm/IR/Function.h"
38
#include "llvm/IR/InstrTypes.h"
39
#include "llvm/IR/Instructions.h"
1,609✔
40
#include "llvm/IR/Module.h"
41
#include "llvm/IR/Value.h"
42
#include "llvm/Support/Casting.h"
43
#include "llvm/Support/raw_ostream.h"
44

45
#include <cstddef>
46
#include <optional>
47

48
namespace typeart::analysis {
49

50
using namespace llvm;
51

52
MemOpVisitor::MemOpVisitor() : MemOpVisitor(true, true) {
×
53
}
×
54

55
MemOpVisitor::MemOpVisitor(const config::Configuration& config)
1,609✔
56
    : MemOpVisitor(config[config::ConfigStdArgs::stack], config[config::ConfigStdArgs::heap]) {
1,609✔
57
}
1,609✔
58
MemOpVisitor::MemOpVisitor(bool stack, bool heap) : collect_allocas(stack), collect_heap(heap) {
3,218✔
59
}
1,609✔
60

61
void MemOpVisitor::collect(llvm::Function& function) {
12,279✔
62
  visit(function);
12,279✔
63

64
  for (auto& [lifetime, alloc] : lifetime_starts) {
239,127✔
65
    auto* data = llvm::find_if(
226,848✔
66
        allocas, [alloc = std::ref(alloc)](const AllocaData& alloca_data) { return alloca_data.alloca == alloc; });
568,980✔
67
    if (data != std::end(allocas)) {
226,848✔
68
      data->lifetime_start.insert(lifetime);
9,430✔
69
    }
4,715✔
70
  }
71

72
  for (const auto& alloc : allocas) {
24,683✔
73
    if (alloc.lifetime_start.size() > 1) {
12,404✔
74
      LOG_DEBUG("Lifetime: " << alloc.lifetime_start.size());
75
      LOG_DEBUG(*alloc.alloca);
76
      for (auto* lifetime : alloc.lifetime_start) {
×
77
        LOG_DEBUG(*lifetime);
78
      }
79
    }
×
80
  }
81
}
12,279✔
82

83
void MemOpVisitor::collectGlobals(Module& module) {
1,609✔
84
  for (auto& g : module.globals()) {
56,114✔
85
    globals.emplace_back(GlobalData{&g});
54,505✔
86
  }
87
}
1,609✔
88

89
void MemOpVisitor::visitCallBase(llvm::CallBase& cb) {
63,906✔
90
  if (!collect_heap) {
63,906✔
91
    return;
21,063✔
92
  }
93
  const auto isInSet = [&](const auto& fMap) -> std::optional<MemOpKind> {
127,626✔
94
    const auto* f = cb.getCalledFunction();
84,783✔
95
    if (!f) {
84,783✔
96
      // TODO handle calls through, e.g., function pointers? - seems infeasible
97
      // LOG_INFO("Encountered indirect call, skipping.");
98
      return {};
896✔
99
    }
100
    const auto name = f->getName().str();
83,887✔
101

102
    const auto res = fMap.find(name);
83,887✔
103
    if (res != fMap.end()) {
83,887✔
104
      return {(*res).second};
1,631✔
105
    }
106
    return {};
82,256✔
107
  };
84,783✔
108

109
  if (auto alloc_val = isInSet(mem_operations.allocs())) {
42,843✔
110
    visitMallocLike(cb, alloc_val.value());
903✔
111
  } else if (auto dealloc_val = isInSet(mem_operations.deallocs())) {
42,843✔
112
    visitFreeLike(cb, dealloc_val.value());
728✔
113
  }
728✔
114
}
63,906✔
115

116
template <class InstTy>
117
llvm::Expected<InstTy*> getSingleUserAs(llvm::Value* value) {
132✔
118
  auto users            = value->users();
132✔
119
  const auto num_stores = llvm::count_if(users, [](llvm::User* use) { return llvm::isa<InstTy>(*use); });
282✔
120
  RETURN_ERROR_IF((num_stores == 0), "Expected a single store on call \"{}\". It has no users!", *value);
132✔
121

122
  const auto num_asan_call = llvm::count_if(users, [](llvm::User* user) {
282✔
123
    CallSite csite(user);
150✔
124
    if (!(csite.isCall() || csite.isInvoke()) || csite.getCalledFunction() == nullptr) {
150✔
125
      return false;
132✔
126
    }
127
    const auto name = csite.getCalledFunction()->getName();
18✔
128
    return util::starts_with_any_of(name, "__asan");
18✔
129
  });
150✔
130

131
  RETURN_ERROR_IF(num_asan_call > 1, "Expected one ASAN call for array cookie.");
132✔
132

133
  auto* target_instruction =
132✔
134
      dyn_cast<InstTy>(*llvm::find_if(users, [](llvm::User* use) { return llvm::isa<InstTy>(*use); }));
282✔
135

136
  if (num_asan_call != 0) {
132✔
137
    const auto* asan_call = dyn_cast<CallBase>(*llvm::find_if(users, [](llvm::User* user) {
36✔
138
      CallSite csite(user);
18✔
139
      if (!(csite.isCall() || csite.isInvoke()) || csite.getCalledFunction() == nullptr) {
18✔
NEW
140
        return false;
×
141
      }
142
      const auto name = csite.getCalledFunction()->getName();
18✔
143
      return util::starts_with_any_of(name, "__asan");
18✔
144
    }));
18✔
145
    if constexpr (std::is_same_v<InstTy, llvm::StoreInst>) {
146
      RETURN_ERROR_IF(target_instruction->getPointerOperand() != asan_call->getArgOperand(0),
18✔
147
                      "Expected a single user on value \"{}\" but found multiple potential candidates!", *value);
148
    } else {
149
      if constexpr (std::is_same_v<InstTy, llvm::BitCastInst>) {
NEW
150
        RETURN_ERROR_IF(target_instruction != asan_call->getArgOperand(0),
×
151
                        "Expected a single user on value \"{}\" but found multiple potential candidates!", *value);
12,404✔
152
      }
153
    }
154
  }
18✔
155

156
  return {target_instruction};
132✔
157
}
132✔
158

159
using MallocGeps   = SmallPtrSet<GetElementPtrInst*, 2>;
160
using MallocBcasts = SmallPtrSet<BitCastInst*, 4>;
161

162
std::pair<MallocGeps, MallocBcasts> collectRelevantMallocUsers(llvm::CallBase& ci) {
903✔
163
  auto geps   = MallocGeps{};
903✔
164
  auto bcasts = MallocBcasts{};
903✔
165
  for (auto user : ci.users()) {
1,987✔
166
    // Simple case: Pointer is immediately casted
167
    if (auto inst = dyn_cast<BitCastInst>(user)) {
1,084✔
168
      bcasts.insert(inst);
771✔
169
    }
771✔
170
    // Pointer is first stored, then loaded and subsequently casted
171
    if (auto storeInst = dyn_cast<StoreInst>(user)) {
1,084✔
172
      auto storeAddr = storeInst->getPointerOperand();
120✔
173
      for (auto storeUser : storeAddr->users()) {  // TODO: Ensure that load occurs after store?
636✔
174
        if (auto loadInst = dyn_cast<LoadInst>(storeUser)) {
516✔
175
          for (auto loadUser : loadInst->users()) {
450✔
176
            if (auto bcastInst = dyn_cast<BitCastInst>(loadUser)) {
294✔
177
              // LOG_MSG(*bcastInst)
178
              bcasts.insert(bcastInst);
24✔
179
            }
24✔
180
          }
181
        }
156✔
182
      }
183
    }
120✔
184
    // GEP indicates that an array cookie is added to the allocation. (Fixes #13)
185
    if (auto gep = dyn_cast<GetElementPtrInst>(user)) {
1,084✔
186
      geps.insert(gep);
69✔
187
    }
69✔
188
  }
189
  return {geps, bcasts};
903✔
190
}
903✔
191

192
llvm::Expected<ArrayCookieData> handleUnpaddedArrayCookie(llvm::CallBase& ci, const MallocGeps& geps,
57✔
193
                                                          MallocBcasts& bcasts, BitCastInst*& primary_cast) {
194
  using namespace util::type;
195
#if LLVM_VERSION_MAJOR < 15
196
  // We expect only the bitcast to size_t for the array cookie store.
197
  RETURN_ERROR_IF(bcasts.size() != 1, "Couldn't identify bitcast instruction of an unpadded array cookie!");
57✔
198
  auto cookie_bcast = *bcasts.begin();
57✔
199
  RETURN_ERROR_IF(!isi64Ptr(cookie_bcast->getDestTy()), "Found non-i64Ptr bitcast instruction for an array cookie!");
57✔
200

201
  auto cookie_store = getSingleUserAs<StoreInst>(cookie_bcast);
57✔
202
  RETURN_ON_ERROR(cookie_store);
57✔
203

204
  auto array_gep = *geps.begin();
57✔
205
  RETURN_ERROR_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
57✔
206

207
  auto array_bcast = getSingleUserAs<BitCastInst>(array_gep);
57✔
208
  RETURN_ON_ERROR(array_bcast);
57✔
209

210
  bcasts.insert(*array_bcast);
57✔
211
  primary_cast = *array_bcast;
57✔
212
#else
213
  auto cookie_store = getSingleUserAs<StoreInst>(&ci);
214
  RETURN_ON_ERROR(cookie_store);
215
  auto array_gep = *geps.begin();
216
  RETURN_ERROR_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
217
#endif
218
  return {ArrayCookieData{*cookie_store, array_gep}};
57✔
219
}
57✔
220

221
llvm::Expected<ArrayCookieData> handlePaddedArrayCookie(llvm::CallBase& ci, const MallocGeps& geps,
6✔
222
                                                        MallocBcasts& bcasts, BitCastInst*& primary_cast) {
223
  using namespace util::type;
224
#if LLVM_VERSION_MAJOR < 15
225
  // We expect bitcasts only after the GEP instructions in this case.
226
  RETURN_ERROR_IF(!bcasts.empty(), "Found unrelated bitcast instructions on a padded array cookie!");
6✔
227

228
  auto gep_it     = geps.begin();
6✔
229
  auto array_gep  = *gep_it++;
6✔
230
  auto cookie_gep = *gep_it++;
6✔
231

232
  auto cookie_bcast = getSingleUserAs<BitCastInst>(cookie_gep);
6✔
233
  RETURN_ON_ERROR(cookie_bcast);
6✔
234
  RETURN_ERROR_IF(!isi64Ptr((*cookie_bcast)->getDestTy()), "Found non-i64Ptr bitcast instruction for an array cookie!");
6✔
235

236
  auto cookie_store = getSingleUserAs<StoreInst>(*cookie_bcast);
6✔
237
  RETURN_ON_ERROR(cookie_store);
6✔
238
  RETURN_ERROR_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
6✔
239

240
  auto array_bcast = getSingleUserAs<BitCastInst>(array_gep);
6✔
241
  RETURN_ON_ERROR(array_bcast);
6✔
242

243
  bcasts.insert(*array_bcast);
6✔
244
  primary_cast = *array_bcast;
6✔
245
#else
246
  auto gep_it       = geps.begin();
247
  auto array_gep    = *gep_it++;
248
  auto cookie_gep   = *gep_it++;
249
  auto cookie_store = getSingleUserAs<StoreInst>(cookie_gep);
250
  RETURN_ON_ERROR(cookie_store);
251
  RETURN_ERROR_IF(array_gep->getNumIndices() != 1, "Found multidimensional array cookie gep!");
252
#endif
253
  return {ArrayCookieData{*cookie_store, array_gep}};
6✔
254
}
6✔
255

256
std::optional<ArrayCookieData> handleArrayCookie(llvm::CallBase& ci, const MallocGeps& geps, MallocBcasts& bcasts,
903✔
257
                                                 BitCastInst*& primary_cast) {
258
  auto exit_on_error = llvm::ExitOnError{"Array Cookie Detection failed!"};
903✔
259
  if (geps.size() == 1) {
903✔
260
    return exit_on_error(handleUnpaddedArrayCookie(ci, geps, bcasts, primary_cast));
57✔
261
  } else if (geps.size() == 2) {
846✔
262
    return exit_on_error(handlePaddedArrayCookie(ci, geps, bcasts, primary_cast));
6✔
263
  } else if (geps.size() > 2) {
840✔
264
    // Found a case where the address of an allocation is used more than two
265
    // times as an argument to a GEP instruction. This is unexpected as at most
266
    // two GEPs, for calculating the offsets of an array cookie itself and the
267
    // array pointer, are expected.
268
    auto err = "Expected at most two GEP instructions!";
×
269
    LOG_FATAL(err);
×
270
    exit_on_error({error::make_string_error(err)});
×
271
  }
×
272
  return {};
840✔
273
}
903✔
274

275
void MemOpVisitor::visitMallocLike(llvm::CallBase& ci, MemOpKind k) {
903✔
276
  auto [geps, bcasts] = collectRelevantMallocUsers(ci);
3,492✔
277
  auto primary_cast   = bcasts.empty() ? nullptr : *bcasts.begin();
903✔
278
  auto array_cookie   = handleArrayCookie(ci, geps, bcasts, primary_cast);
1,806✔
279
  if (primary_cast == nullptr) {
903✔
280
    LOG_DEBUG("Primary bitcast null: " << ci)
281
  }
114✔
282
  mallocs.push_back(MallocData{&ci, array_cookie, primary_cast, bcasts, k, isa<InvokeInst>(ci)});
1,806✔
283
}
903✔
284

285
void MemOpVisitor::visitFreeLike(llvm::CallBase& ci, MemOpKind k) {
728✔
286
  //  LOG_DEBUG(ci.getCalledFunction()->getName());
287
  MemOpKind kind = k;
728✔
288

289
  // FIXME is that superfluous?
290
  if (auto f = ci.getCalledFunction()) {
728✔
291
    auto dkind = mem_operations.deallocKind(f->getName());
728✔
292
    if (dkind) {
728✔
293
      kind = dkind.value();
728✔
294
    }
728✔
295
  }
728✔
296

297
  auto gep              = dyn_cast<GetElementPtrInst>(ci.getArgOperand(0));
728✔
298
  auto array_cookie_gep = gep != nullptr ? std::optional<llvm::GetElementPtrInst*>{gep} : std::nullopt;
728✔
299
  frees.emplace_back(FreeData{&ci, array_cookie_gep, kind, isa<InvokeInst>(ci)});
728✔
300
}
728✔
301

302
// void MemOpVisitor::visitIntrinsicInst(llvm::IntrinsicInst& ii) {
303
//
304
//}
305

306
void MemOpVisitor::visitAllocaInst(llvm::AllocaInst& ai) {
32,998✔
307
  if (!collect_allocas) {
32,998✔
308
    return;
20,594✔
309
  }
310
  //  LOG_DEBUG("Found alloca " << ai);
311
  Value* arraySizeOperand = ai.getArraySize();
12,404✔
312
  size_t arraySize{0};
12,404✔
313
  bool is_vla{false};
12,404✔
314
  if (auto arraySizeConst = llvm::dyn_cast<ConstantInt>(arraySizeOperand)) {
12,404✔
315
    arraySize = arraySizeConst->getZExtValue();
12,350✔
316
  } else {
12,350✔
317
    is_vla = true;
54✔
318
  }
319

320
  allocas.push_back({&ai, arraySize, is_vla});
24,808✔
321
  //  LOG_DEBUG("Alloca: " << util::dump(ai) << " -> lifetime marker: " << util::dump(lifetimes));
322
}
32,998✔
323

324
void MemOpVisitor::visitIntrinsicInst(llvm::IntrinsicInst& inst) {
88,422✔
325
  if (inst.getIntrinsicID() == Intrinsic::lifetime_start) {
88,422✔
326
#if LLVM_VERSION_MAJOR >= 12
327
    auto alloca = llvm::findAllocaForValue(inst.getOperand(1));
9,430✔
328
#else
329
    DenseMap<Value*, AllocaInst*> alloca_for_value;
330
    auto* alloca = llvm::findAllocaForValue(inst.getOperand(1), alloca_for_value);
331
#endif
332
    if (alloca != nullptr) {
9,430✔
333
      lifetime_starts.emplace_back(&inst, alloca);
9,427✔
334
    }
9,427✔
335
  }
9,430✔
336
}
88,422✔
337

338
void MemOpVisitor::clear() {
12,279✔
339
  allocas.clear();
12,279✔
340
  mallocs.clear();
12,279✔
341
  frees.clear();
12,279✔
342
}
12,279✔
343

344
}  // 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