Added ASan/UBSan to CI, and fixed issues it found. (#445)

This commit is contained in:
Branimir Karadžić
2026-10-05 08:55:08 -07:00
committed by GitHub
parent 9521d0b5e2
commit 09cf98d55c
11 changed files with 154 additions and 32 deletions

View File

@@ -85,8 +85,9 @@ jobs:
{ compiler: gcc, genie: linux-gcc, bindir: linux64_gcc, config: release, binsuffix: Release },
{ compiler: clang, genie: linux-clang, bindir: linux64_clang, config: debug, binsuffix: Debug },
{ compiler: clang, genie: linux-clang, bindir: linux64_clang, config: release, binsuffix: Release },
{ compiler: clang, genie: linux-clang, bindir: linux64_clang, config: debug, binsuffix: Debug, genie-options: --with-sanitizers, suffix: -sanitizers },
]
name: linux-${{ matrix.compiler }}-${{ matrix.config }}64
name: linux-${{ matrix.compiler }}-${{ matrix.config }}64${{ matrix.suffix }}
runs-on: ubuntu-24.04
steps:
- name: Checkout bx
@@ -97,9 +98,12 @@ jobs:
- name: Build
run: |
cd bx
tools/bin/linux/genie --gcc=${{ matrix.genie }} gmake
tools/bin/linux/genie ${{ matrix.genie-options }} --gcc=${{ matrix.genie }} gmake
make -R -C .build/projects/gmake-${{ matrix.genie }} config=${{ matrix.config }}64 -j$(nproc)
- name: Check
env:
ASAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: |
cd bx
".build/${{ matrix.bindir }}/bin/bx.test${{ matrix.binsuffix }}" -d yes
@@ -110,8 +114,9 @@ jobs:
include: [
{ runs-on: macos-15, arch: osx-arm64, bindir: osx-arm64, config: debug, binsuffix: Debug },
{ runs-on: macos-15, arch: osx-arm64, bindir: osx-arm64, config: release, binsuffix: Release },
{ runs-on: macos-15, arch: osx-arm64, bindir: osx-arm64, config: debug, binsuffix: Debug, genie-options: --with-sanitizers, suffix: -sanitizers },
]
name: osx-${{ matrix.arch }}-${{ matrix.config }}
name: osx-${{ matrix.arch }}-${{ matrix.config }}${{ matrix.suffix }}
runs-on: ${{ matrix.runs-on }}
steps:
- name: Checkout bx
@@ -122,9 +127,12 @@ jobs:
- name: Build
run: |
cd bx
tools/bin/darwin/genie --gcc=${{ matrix.arch }} gmake
tools/bin/darwin/genie ${{ matrix.genie-options }} --gcc=${{ matrix.arch }} gmake
make -C .build/projects/gmake-${{ matrix.arch }} config=${{ matrix.config }} -j$(sysctl -n hw.physicalcpu)
- name: Check
env:
ASAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: |
cd bx
".build/${{ matrix.bindir }}/bin/bx.test${{ matrix.binsuffix }}" -d yes

View File

@@ -247,9 +247,6 @@ namespace bx
/// Default constructor. Constructs an empty bit array.
BitArrayBaseT();
/// Destructor. Releases storage.
~BitArrayBaseT();
/// Constructs a bit array by copying from a bit array view.
///
/// @param[in] _src Source bit array view to copy from.

View File

@@ -251,11 +251,17 @@ namespace bx
template<typename StorageT>
inline void BitArrayBaseT<StorageT>::set(const BitArrayViewBase& _src)
{
if (this != &_src)
if (this == &_src)
{
this->setCount(_src.getCount() );
return;
}
const uint32_t sizeInBytes = min(this->getCapacity(), _src.getCapacity() ) / 8;
this->setCount(_src.getCount() );
const uint32_t sizeInBytes = min(this->getCapacity(), _src.getCapacity() ) / 8;
if (0 != sizeInBytes)
{
bx::memCopy(this->m_ptr, _src.getPtr(), sizeInBytes);
}
}
@@ -278,12 +284,6 @@ namespace bx
MutableBitArrayView::set(_beginBit, _endBit, _value);
}
template<typename StorageT>
inline BitArrayBaseT<StorageT>::~BitArrayBaseT()
{
this->clear();
}
template<typename StorageT>
template<typename StorageU>
inline BitArrayBaseT<StorageT>& BitArrayBaseT<StorageT>::operator=(const BitArrayBaseT<StorageU>& _rhs)
@@ -400,7 +400,6 @@ namespace bx
{
if (NULL != this->m_storage.getAllocator() )
{
this->clear();
this->m_storage.setCapacity(0, 0);
}

View File

@@ -444,7 +444,10 @@ BX_FP_PRECISE_BEGIN()
const float scaled = _a * (2.0f*kInvPi);
const float real = floor(scaled);
const float xx = _a - real * kPiHalf;
const int32_t bits = int32_t(real) & 3;
const int32_t bits = isFinite(real)
? int32_t(mod(real, 4.0f) ) & 3
: 0
;
constexpr float kSinC2 = -0.16666667163372039794921875f;
constexpr float kSinC4 = 8.333347737789154052734375e-3f;
@@ -702,6 +705,18 @@ BX_FP_PRECISE_BEGIN()
return 0.0f;
}
constexpr float expMax = log(kFloatLargest);
if (_a >= expMax)
{
return kFloatInfinity;
}
if (isNan(_a) )
{
return _a;
}
constexpr float kExpC0 = 1.66666666666666019037e-01f;
constexpr float kExpC1 = -2.77777777770155933842e-03f;
constexpr float kExpC2 = 6.61375632143793436117e-05f;

View File

@@ -246,7 +246,12 @@ namespace bx
int64_t remainder = m_size-m_pos;
int32_t size = int32_t(max<int64_t>(0, min<int64_t>(_size, remainder, INT32_MAX) ) );
memCopy(&m_data[m_pos], _data, size);
if (0 < size)
{
memCopy(&m_data[m_pos], _data, size);
}
m_pos += size;
m_top = max(m_top, m_pos);
if (size != _size)

View File

@@ -856,6 +856,9 @@ BX_FP_PRECISE_BEGIN()
const Ty near_result = simd_f32_add(_a, one);
const Ty exp_min = simd_splat<Ty>(-87.33654475f);
const Ty clamp_mask = simd_f32_cmple(_a, exp_min);
const Ty exp_max = simd_splat<Ty>(88.72283905f);
const Ty inf = simd_splat<Ty>(kFloatInfinity);
const Ty inf_mask = simd_f32_cmpge(_a, exp_max);
const Ty kC0 = simd_splat<Ty>( 1.66666666666666019037e-01f);
const Ty kC1 = simd_splat<Ty>(-2.77777777770155933842e-03f);
const Ty kC2 = simd_splat<Ty>( 6.61375632143793436117e-05f);
@@ -887,7 +890,8 @@ BX_FP_PRECISE_BEGIN()
const Ty ikk = simd_f32_ftoi_trunc(kk);
const Ty raw = simd_f32_ldexp_ni(tmp6, ikk);
const Ty rc = simd_selb(clamp_mask, zero, raw);
const Ty result = simd_selb(near_mask, near_result, rc);
const Ty ri = simd_selb(inf_mask, inf, rc);
const Ty result = simd_selb(near_mask, near_result, ri);
return result;
}

View File

@@ -171,6 +171,11 @@ function toolchain(_buildDir, _libDir)
description = "Use AVX extension.",
}
newoption {
trigger = "with-sanitizers",
description = "Build with address and undefined behavior sanitizers (gcc/clang).",
}
-- Avoid error when invoking genie --help.
if (_ACTION == nil) then return false end
@@ -516,6 +521,20 @@ function toolchain(_buildDir, _libDir)
crtNone()
end
if _OPTIONS["with-sanitizers"] then
configuration { "linux-* or osx*" }
buildoptions {
"-fsanitize=address,undefined",
"-fno-sanitize-recover=all",
"-fno-omit-frame-pointer",
}
linkoptions {
"-fsanitize=address,undefined",
}
configuration {}
end
flags {
"NoPCH",
"NativeWChar",

View File

@@ -54,7 +54,7 @@ namespace bx
const simd64_t zero = simd64_zero();
const simd64_t allOnes = simd64_splat(UINT64_MAX);
const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) );
const simd64_t maskE = simd64_x64_srl(allOnes, int(64-shiftE) );
const simd64_t maskE = simd64_x64_srl(allOnes, int( (64-shiftE) & 63) );
const simd64_t valVec = simd64_splat(uint64_t(_value) );
const simd64_t orbits = simd64_u64_sub(zero, valVec);
const simd64_t orbitsB = simd64_and(maskB, orbits);
@@ -132,7 +132,7 @@ namespace bx
const uint32_t shiftE = shiftEVec.u32;
const simd64_t allOnes = simd64_splat(UINT64_MAX);
const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) );
const simd64_t maskE = simd64_x64_srl(allOnes, int(64-shiftE) );
const simd64_t maskE = simd64_x64_srl(allOnes, int( (64-shiftE) & 63) );
if (idxB == idxE)
{
@@ -180,8 +180,7 @@ namespace bx
const uint32_t shiftE = shiftEVec.u32;
const simd64_t allOnes = simd64_splat(UINT64_MAX);
const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) );
const simd64_t tmp0 = simd64_x64_srl(allOnes, int(63-shiftE) );
const simd64_t maskE = simd64_x64_srl(tmp0, 1);
const simd64_t maskE = simd64_x64_srl(allOnes, int( (64-shiftE) & 63) );
if (idxB == idxE)
{
@@ -235,8 +234,7 @@ namespace bx
const uint32_t shiftE = shiftEVec.u32;
const simd64_t allOnes = simd64_splat(UINT64_MAX);
const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) );
const simd64_t tmp0 = simd64_x64_srl(allOnes, int(63-shiftE) );
const simd64_t maskE = simd64_x64_srl(tmp0, 1);
const simd64_t maskE = simd64_x64_srl(allOnes, int( (64-shiftE) & 63) );
if (idxB == idxE)
{
@@ -290,8 +288,7 @@ namespace bx
const uint32_t shiftE = shiftEVec.u32;
const simd64_t allOnes = simd64_splat(UINT64_MAX);
const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) );
const simd64_t tmp0 = simd64_x64_srl(allOnes, int(63-shiftE) );
const simd64_t maskE = simd64_x64_srl(tmp0, 1);
const simd64_t maskE = simd64_x64_srl(allOnes, int( (64-shiftE) & 63) );
if (idxB == idxE)
{
@@ -362,7 +359,7 @@ namespace bx
_ptr[idxB] = replaceBits(_ptr[idxB], shiftB, maskB, _bits);
const uint64_t bitsE = _bits >> (64 - shiftB);
const uint64_t maskE = UINT64_MAX >> (64 - shiftE);
const uint64_t maskE = UINT64_MAX >> ( (64 - shiftE) & 63);
_ptr[idxE] = replaceBits(_ptr[idxE], 0, maskE, bitsE);
}
@@ -389,7 +386,7 @@ namespace bx
const uint64_t maskB = UINT64_MAX >> shiftB;
const uint64_t bitsB = extractBits(_ptr[idxB], shiftB, maskB);
const uint64_t maskE = UINT64_MAX >> (64 - shiftE);
const uint64_t maskE = UINT64_MAX >> ( (64 - shiftE) & 63);
const uint64_t bitsE = extractBits(_ptr[idxE], 0, maskE);
return (bitsE << (64-shiftB) ) | bitsB;

View File

@@ -478,7 +478,7 @@ namespace bx
return 0;
}
_max = toString(_dst + 1, _max - 1, asUnsigned(-_value), _base, _separator);
_max = toString(_dst + 1, _max - 1, 0 - asUnsigned(_value), _base, _separator);
if (_max == 0)
{

View File

@@ -149,7 +149,12 @@ namespace bx
const FilePath uri(_name);
const StringView path(strTrim(uri.getPath(), "/") );
_propertyName = uri.getFileName();
const StringView slash = strRFind(_name, '/');
_propertyName = slash.isEmpty()
? _name
: StringView(slash.getPtr()+1, _name.getTerm() )
;
if (path.isEmpty() )
{

View File

@@ -903,3 +903,76 @@ TEST_CASE("MippedBitArray-dynamic", "[container]")
REQUIRE(65 == array.findClear(64, 300) );
REQUIRE(bx::kInvalid == array.findClear(64, 65) );
}
TEST_CASE("BitArray-ranges-ending-on-word-boundary", "[container]")
{
uint64_t words[4];
bx::MutableBitArrayView bs(words, sizeof(words), 256);
bs.clear();
bs.set(0, 64, true);
REQUIRE(UINT64_MAX == words[0]);
REQUIRE(0 == words[1]);
bs.clear();
bs.set(10, 192, true);
REQUIRE( (UINT64_MAX >> 10) == (words[0] >> 10) );
REQUIRE(UINT64_MAX == words[1]);
REQUIRE(UINT64_MAX == words[2]);
REQUIRE(0 == words[3]);
REQUIRE(182 == bs.countBits(10, 192) );
REQUIRE(64 == bs.countBits(128, 192) );
bs.set(0, 256, true);
bs.set(0, 256, false);
REQUIRE(bs.testNone() );
bs.set(70, true);
REQUIRE( bs.testAny(64, 128) );
REQUIRE(!bs.testNone(64, 128) );
REQUIRE(!bs.testAll(64, 128) );
REQUIRE( bs.testNone(0, 64) );
REQUIRE(!bs.testAny(0, 64) );
REQUIRE(1 == bs.countBits(64, 128) );
bs.set(127, true);
REQUIRE( bs.testAny(64, 128) );
REQUIRE(2 == bs.countBits(64, 128) );
bs.set(64, 128, true);
REQUIRE( bs.testAll(64, 128) );
REQUIRE(!bs.testAll(0, 128) );
bs.set(0, 256, true);
REQUIRE( bs.testAll(0, 256) );
REQUIRE(256 == bs.countBits(0, 256) );
bs.set(255, false);
REQUIRE(!bs.testAll(0, 256) );
REQUIRE( bs.testAny(192, 256) );
REQUIRE( bs.testAll(192, 255) );
bs.set(192, 256, false);
REQUIRE( bs.testNone(192, 256) );
REQUIRE(!bs.testAny(192, 256) );
REQUIRE( bs.testAll(0, 192) );
bs.clear();
bs.set(60, 124, UINT64_MAX);
REQUIRE(0xf == (words[0] >> 60) );
REQUIRE( (UINT64_MAX >> 4) == words[1]);
REQUIRE(UINT64_MAX == bs.get(60, 124) );
bs.clear();
bs.set(64, 128, UINT64_C(0x1234567890abcdef) );
REQUIRE(UINT64_C(0x1234567890abcdef) == words[1]);
REQUIRE(UINT64_C(0x1234567890abcdef) == bs.get(64, 128) );
REQUIRE(0 == words[0]);
REQUIRE(0 == words[2]);
bs.clear();
bs.set(100, 164, UINT64_MAX);
REQUIRE(UINT64_MAX == bs.get(100, 164) );
REQUIRE( (UINT64_MAX >> 36) == (words[1] >> 36) );
REQUIRE( (UINT64_MAX >> 28) == words[2]);
}