Turning on Thread Sanitizer Results in Signal Sigabrt

Turning on Thread Sanitizer results in signal SIGABRT

issue resolved in Xcode 11.3.1

Resolved an issue that prevented Xcode from launching processes with
Thread Sanitizer enabled on macOS Catalina 10.15.2. (57822138)

see Xcode 11.3.1 Release Notes

Xcode 11.3 throws exception when running app on iOS 13 device, but not on simulator

Sometimes Apple's frameworks use exceptions as an internal messaging technique. They probably shouldn't do that, but the frameworks contain a lot of old code that doesn't always follow best practices. There is no harm done, because the framework, having thrown the exception, also catches it, which is why you run fine if the app just runs without the debugger. But if you run from Xcode and you have set an Exception Breakpoint, you will pause after the throw but before the catch. It looks like that's what's happening. So just remove that breakpoint.

Why does boost split cause double free or corruption issue

So, boost::split doesn't crash. You have undefined behavior elsewhere.

Regardless, why are you parsing through a string, allocating a vector of strings, comparing to a temporary string etc. all the time? You could do this on the integer-domain.

Four takes. Starting from a simple skeleton:

#include <shared_mutex>
#include <string>

struct MyClass1 {
MyClass1(uint32_t owner, std::string admins)
: m_familyOwner(owner)
, m_familyAdmins(std::move(admins)) {}

bool hasFamilyAdminPermission(uint32_t uid) const;

private:
mutable std::shared_mutex m_mtx; // guards m_familyOwner and m_familyAdmins
uint32_t m_familyOwner;
std::string m_familyAdmins;
};

1. Comparing Ints, No Allocations

I'll use Boost Spirit X3:

#include <boost/spirit/home/x3.hpp>
bool MyClass1::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
if (uid == m_familyOwner)
return true;

bool matched = false;
auto element = boost::spirit::x3::uint32;
auto check = [uid, &matched](auto& ctx) {
if (_attr(ctx) == uid) {
matched = true;
_pass(ctx) = false; // short circuit for perf
}
};

parse(begin(m_familyAdmins), end(m_familyAdmins), element[check] % ',');
return matched;
}

This still does quite a lot of work under the lock, but certainly never allocates. Also, it does early-out, which helps if the collection of owners can be very large.

2. Comparing Text, But Without Allocations

With a nifty regex you can match the number as text on a constant string (or string view). The overhead here is the allocation(s) for the regex. But arguably, it's much simpler:

#include <regex>
bool MyClass2::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
if (uid == m_familyOwner)
return true;

return regex_search(m_familyAdmins, std::regex("(^|,)" + std::to_string(uid) + "(,|$)"));
}

3. Parse Once, At Construction

Why are we dealing with text? We could keep the admins in a set:

#include <set>
struct MyClass3 {
MyClass3(uint32_t owner, std::string_view admins) : m_familyOwner(owner) {
parse(admins.begin(), end(admins), boost::spirit::x3::uint32 % ',', m_familyAdmins);
}
bool hasFamilyAdminPermission(uint32_t uid) const;

private:
mutable std::shared_mutex m_mtx; // guards m_familyOwner and m_familyAdmins
uint32_t m_familyOwner;
std::set<uint32_t> m_familyAdmins;
};

bool MyClass3::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
return uid == m_familyOwner || m_familyAdmins.contains(uid);
}

That's even simpler. However, there's some overhead in the set which can be optimized.

4. Parse Once, No Allocations, Speed

std::set has the right semantics. However, for small sets it's sad that there's no locality of reference, and relatively high node allocation overhead. We could replace with:

boost::container::flat_set< //
uint32_t, //
std::less<>, //
boost::container::small_vector<uint32_t, 10>>
m_familyAdmins;

This makes it so that sets <= 10 elements do not allocate at all, and lookup benefits from contiguous storage. However, at this rate - unless you want to deal with duplicate entries - you might keep a linear search and store:

boost::container::small_vector<uint32_t, 10>
m_familyAdmins;

Combined Demo

Showing all the subtle edge cases. Note that only with the X3 parser

  • it will be easy to perform input validation on the comma-separated string
  • it will be easy to reliably compare differently formatted uid numbers

I snuck in one number that has a leading 0 (089 instead of 89) just to highlight this issue with the std::regex approach. Note that your original code has the same problem.

Live On Coliru/Compiler Explorer

#include <shared_mutex>
#include <string>

struct MyClass1 {
MyClass1(uint32_t owner, std::string admins)
: m_familyOwner(owner)
, m_familyAdmins(std::move(admins)) {}

bool hasFamilyAdminPermission(uint32_t uid) const;

private:
mutable std::shared_mutex m_mtx; // guards m_familyOwner and m_familyAdmins
uint32_t m_familyOwner;
std::string m_familyAdmins;
};

#include <boost/spirit/home/x3.hpp>
bool MyClass1::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
if (uid == m_familyOwner)
return true;

bool matched = false;
auto element = boost::spirit::x3::uint32;
auto check = [uid, &matched](auto& ctx) {
if (_attr(ctx) == uid) {
matched = true;
_pass(ctx) = false; // short circuit for perf
}
};

parse(begin(m_familyAdmins), end(m_familyAdmins), element[check] % ',');
return matched;
}

struct MyClass2 {
MyClass2(uint32_t owner, std::string admins)
: m_familyOwner(owner)
, m_familyAdmins(std::move(admins)) {}
bool hasFamilyAdminPermission(uint32_t uid) const;

private:
mutable std::shared_mutex m_mtx; // guards m_familyOwner and m_familyAdmins
uint32_t m_familyOwner;
std::string m_familyAdmins;
};

#include <regex>
bool MyClass2::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
if (uid == m_familyOwner)
return true;

return std::regex_search(m_familyAdmins, std::regex("(^|,)" + std::to_string(uid) + "(,|$)"));
}

#include <set>
struct MyClass3 {
MyClass3(uint32_t owner, std::string_view admins) : m_familyOwner(owner) {
parse(admins.begin(), end(admins), boost::spirit::x3::uint32 % ',', m_familyAdmins);
}
bool hasFamilyAdminPermission(uint32_t uid) const;

private:
mutable std::shared_mutex m_mtx; // guards m_familyOwner and m_familyAdmins
uint32_t m_familyOwner;
std::set<uint32_t> m_familyAdmins;
};

bool MyClass3::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
return uid == m_familyOwner || m_familyAdmins.contains(uid);
}

#include <boost/container/flat_set.hpp>
#include <boost/container/small_vector.hpp>
struct MyClass4 {
MyClass4(uint32_t owner, std::string_view admins) : m_familyOwner(owner) {
parse(admins.begin(), end(admins), boost::spirit::x3::uint32 % ',', m_familyAdmins);
}
bool hasFamilyAdminPermission(uint32_t uid) const;

private:
mutable std::shared_mutex m_mtx; // guards m_familyOwner and m_familyAdmins
uint32_t m_familyOwner;
#ifdef LINEAR_SEARCH
// likely faster with small sets, anyways
boost::container::small_vector<uint32_t, 10> m_familyAdmins;
#else
boost::container::flat_set< //
uint32_t, //
std::less<>, //
boost::container::small_vector<uint32_t, 10>>
m_familyAdmins;
#endif
};

bool MyClass4::hasFamilyAdminPermission(uint32_t uid) const {
std::shared_lock mutex(m_mtx);
return uid == m_familyOwner ||
#ifndef LINEAR_SEARCH
std::find(begin(m_familyAdmins), end(m_familyAdmins), uid) != end(m_familyAdmins);
#else
m_familyAdmins.contains(uid);
#endif
}

#include <iostream>
int main() {
MyClass1 const mc1{42, "21,377,34,233,55,089,144"};
MyClass2 const mc2{42, "21,377,34,233,55,089,144"};
MyClass3 const mc3{42, "21,377,34,233,55,089,144"};
MyClass4 const mc4{42, "21,377,34,233,55,089,144"};

std::cout << "uid\tdynamic\tregex\tset\tflat_set\n"
<< "\t(x3)\t-\t(x3)\t(x3)\n"
<< std::string(5 * 8, '-') << "\n";

auto compare = [&](uint32_t uid) {
std::cout << uid << "\t" << std::boolalpha
<< mc1.hasFamilyAdminPermission(uid) << "\t"
<< mc2.hasFamilyAdminPermission(uid) << "\t"
<< mc3.hasFamilyAdminPermission(uid) << "\t"
<< mc4.hasFamilyAdminPermission(uid) << "\n";
};

compare(42);
// https://en.wikipedia.org/wiki/Fibonacci_number
for (auto i = 3, j = 5; i < 800; std::tie(i, j) = std::tuple{j, i + j}) {
compare(i);
}
}

Prints

id      dynamic regex   set     flat_set
(x3) - (x3) (x3)
----------------------------------------
42 true true true true
3 false false false false
5 false false false false
8 false false false false
13 false false false false
21 true true true true
34 true true true true
55 true true true true
89 true false true true
144 true true true true
233 true true true true
377 true true true true
610 false false false false


Related Topics



Leave a reply



Submit