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

tudasc / TypeART / 13528988609

25 Feb 2025 07:06PM UTC coverage: 88.854% (-1.9%) from 90.735%
13528988609

Pull #163

github

web-flow
Merge e4a2d80f6 into d2e14acc5
Pull Request #163: LLVM 18 support

974 of 1122 new or added lines in 38 files covered. (86.81%)

30 existing lines in 6 files now uncovered.

4201 of 4728 relevant lines covered (88.85%)

190054.62 hits per line

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

88.75
/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) {
2,350✔
90
  using namespace typeart::filter;
91
  const bool filter                    = config[config::ConfigStdArgs::filter];
2,350✔
92
  const FilterImplementation filter_id = config[config::ConfigStdArgs::filter_impl];
2,350✔
93
  const std::string glob               = config[config::ConfigStdArgs::filter_glob];
2,350✔
94

95
  if (filter_id == FilterImplementation::none || !filter) {
2,350✔
96
    LOG_DEBUG("Return no-op filter")
97
    return std::make_unique<NoOpFilter>();
2,006✔
98
  } else if (filter_id == FilterImplementation::cg) {
344✔
99
    const std::string cg_file = config[config::ConfigStdArgs::filter_cg_file];
34✔
100
    if (cg_file.empty()) {
34✔
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);
34✔
106
    auto matcher = std::make_unique<DefaultStringMatcher>(util::glob2regex(glob));
34✔
107
    return std::make_unique<CGForwardFilter>(glob, std::move(json_cg), std::move(matcher));
34✔
108
  } else {
34✔
109
    LOG_DEBUG("Return default filter")
110
    auto matcher         = std::make_unique<DefaultStringMatcher>(util::glob2regex(glob));
310✔
111
    const auto deep_glob = config[config::ConfigStdArgs::filter_glob_deep];
310✔
112
    auto deep_matcher    = std::make_unique<DefaultStringMatcher>(util::glob2regex(deep_glob));
310✔
113
    return std::make_unique<StandardForwardFilter>(std::move(matcher), std::move(deep_matcher));
310✔
114
  }
310✔
115
}
2,350✔
116
}  // namespace detail
117

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

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

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

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

149
CallFilter::~CallFilter() = default;
2,350✔
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;
4,700✔
169

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

174
MemInstFinderPass::MemInstFinderPass(const MemInstFinderConfig& conf_)
6,354✔
175
    : mOpsCollector(conf_), filter(conf_), config(conf_) {
6,354✔
176
}
2,350✔
177

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

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

191
                        if (name.empty()) {
80,750✔
192
                          return true;
69,964✔
193
                        }
194

195
                        if (util::starts_with_any_of(name, "llvm.", "__llvm_gcov", "__llvm_gcda", "__profn", "___asan",
10,786✔
196
                                                     "__msan", "__tsan")) {
197
                          LOG_DEBUG("Prefixed matched on " << name)
198
                          return true;
12✔
199
                        }
200

201
                        if (global->hasInitializer()) {
10,774✔
202
                          auto* ini            = global->getInitializer();
10,048✔
203
                          std::string ini_name = util::dump(*ini);
10,048✔
204

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

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

228
                        if ((global->getLinkage() == GlobalValue::ExternalLinkage && global->isDeclaration())) {
10,762✔
229
                          LOG_DEBUG("Linkage: External");
230
                          return true;
726✔
231
                        }
232

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

239
                        if (global_type->isArrayTy()) {
10,036✔
240
                          global_type = global_type->getArrayElementType();
9,247✔
241
                        }
9,247✔
242
                        if (auto structType = dyn_cast<StructType>(global_type)) {
10,036✔
243
                          if (structType->isOpaque()) {
543✔
244
                            LOG_DEBUG("Encountered opaque struct " << global_type->getStructName() << " - skipping...");
245
                            return true;
×
246
                          }
247
                        }
543✔
248
                        return false;
10,036✔
249
                      }),
80,750✔
250
                  globals.end());
2,350✔
251

252
    const auto beforeCallFilter = globals.size();
2,350✔
253
    NumFilteredGlobals          = NumDetectedGlobals - beforeCallFilter;
2,350✔
254

255
    globals.erase(llvm::remove_if(globals, [&](const auto global) { return filter(global.global); }), globals.end());
12,386✔
256

257
    NumCallFilteredGlobals = beforeCallFilter - globals.size();
2,350✔
258
    NumFilteredGlobals += NumCallFilteredGlobals;
2,350✔
259
  }
2,350✔
260

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

264
bool MemInstFinderPass::runOnFunction(llvm::Function& function) {
41,982✔
265
  if (function.isDeclaration() || util::starts_with_any_of(function.getName(), "__typeart")) {
41,982✔
266
    return false;
24,312✔
267
  }
268

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

271
  mOpsCollector.collect(function);
17,670✔
272

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

295
  NumDetectedAllocs += mOpsCollector.allocas.size();
17,670✔
296

297
  if (config[config::ConfigStdArgs::analysis_filter_alloca_non_array]) {
17,670✔
298
    auto& allocs = mOpsCollector.allocas;
198✔
299
    allocs.erase(llvm::remove_if(allocs,
396✔
300
                                 [&](const auto& data) {
540✔
301
                                   if (!data.alloca->getAllocatedType()->isArrayTy() && data.array_size == 1) {
540✔
302
                                     ++NumFilteredNonArrayAllocs;
513✔
303
                                     return true;
513✔
304
                                   }
305
                                   return false;
27✔
306
                                 }),
540✔
307
                 allocs.end());
198✔
308
  }
198✔
309

310
  if (config[config::ConfigStdArgs::analysis_filter_heap_alloc]) {
17,670✔
311
    auto& allocs  = mOpsCollector.allocas;
9✔
312
    auto& mallocs = mOpsCollector.mallocs;
9✔
313

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

335
    allocs.erase(llvm::remove_if(allocs,
27✔
336
                                 [&](const auto& data) {
9✔
UNCOV
337
                                   if (filterMallocAllocPairing(data.alloca)) {
×
UNCOV
338
                                     ++NumFilteredMallocAllocs;
×
UNCOV
339
                                     return true;
×
340
                                   }
UNCOV
341
                                   return false;
×
UNCOV
342
                                 }),
×
343
                 allocs.end());
9✔
344
  }
9✔
345

346
  if (config[config::ConfigStdArgs::analysis_filter_pointer_alloc]) {
17,670✔
347
    auto& allocs = mOpsCollector.allocas;
17,111✔
348
    allocs.erase(llvm::remove_if(allocs,
34,222✔
349
                                 [&](const auto& data) {
14,935✔
350
                                   auto alloca = data.alloca;
14,935✔
351
                                   if (!data.is_vla && isa<llvm::PointerType>(alloca->getAllocatedType())) {
14,935✔
352
                                     ++NumFilteredPointerAllocs;
6,672✔
353
                                     return true;
6,672✔
354
                                   }
355
                                   return false;
8,263✔
356
                                 }),
14,935✔
357
                 allocs.end());
17,111✔
358
  }
17,111✔
359

360
  // if (config.filter.useCallFilter) {
361
  if (config[config::ConfigStdArgs::filter]) {
17,670✔
362
    auto& allocs = mOpsCollector.allocas;
1,331✔
363
    allocs.erase(llvm::remove_if(allocs,
3,993✔
364
                                 [&](const auto& data) {
4,242✔
365
                                   if (filter(data.alloca)) {
2,911✔
366
                                     ++NumCallFilteredAllocs;
2,526✔
367
                                     return true;
2,526✔
368
                                   }
369
                                   return false;
385✔
370
                                 }),
2,911✔
371
                 allocs.end());
1,331✔
372
    //    LOG_DEBUG(allocs.size() << " allocas to instrument : " << util::dump(allocs));
373
  }
1,331✔
374

375
  auto& mallocs = mOpsCollector.mallocs;
17,670✔
376
  NumDetectedHeap += mallocs.size();
17,670✔
377

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

384
  FunctionData data{mOpsCollector.mallocs, mOpsCollector.frees, mOpsCollector.allocas};
17,670✔
385
  functionMap[&function] = data;
17,670✔
386

387
  mOpsCollector.clear();
17,670✔
388

389
  return true;
17,670✔
390
}  // namespace typeart
41,982✔
391

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

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

414
  const auto call_filter_heap_p =
2,323✔
415
      (double(NumFilteredDetectedHeap) / std::max<double>(1.0, double(NumDetectedHeap))) * 100.0;
2,323✔
416

417
  const auto call_filter_global_p =
2,323✔
418
      (double(NumCallFilteredGlobals) / std::max(1.0, double(NumDetectedGlobals))) * 100.0;
2,323✔
419

420
  const auto call_filter_global_nocallfilter_p =
2,323✔
421
      (double(NumFilteredGlobals) / std::max(1.0, double(NumDetectedGlobals))) * 100.0;
2,323✔
422

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

441
  std::ostringstream stream;
2,323✔
442
  stats.print(stream);
2,323✔
443
  out << stream.str();
2,323✔
444
}
2,323✔
445

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

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

456
const GlobalDataList& MemInstFinderPass::getModuleGlobals() const {
1,171✔
457
  return mOpsCollector.globals;
1,171✔
458
}
459

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

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