├── scripts ├── c++ ├── g++ ├── gcc ├── x86_64-linux-gnu-g++ ├── x86_64-linux-gnu-gcc ├── install_deps.sh ├── travis.sh └── cc ├── tests ├── bsearch │ ├── equal.ref │ ├── good.ref │ ├── bad-full.ref │ ├── bad-full-array.ref │ ├── equal.cpp │ ├── good.cpp │ ├── bad-full-array.cpp │ ├── bad-full.cpp │ └── run.sh ├── map │ ├── inherited.ref │ ├── array.ref │ ├── clear.ref │ ├── dtor.ref │ ├── map.ref │ ├── inherited.cpp │ ├── dtor.cpp │ ├── map.cpp │ ├── clear.cpp │ ├── array.cpp │ └── run.sh ├── lower_bound │ ├── good.ref │ ├── bad-full.ref │ ├── equal_range.ref │ ├── good.cpp │ ├── bad-full.cpp │ ├── equal_range.cpp │ └── run.sh ├── primitive │ ├── repro.ref │ ├── repro.cc │ └── run.sh ├── basic │ ├── reflex.ref │ ├── symmetry.ref │ ├── trans.ref │ ├── equivalence.ref │ ├── reflex.cpp │ ├── symmetry.cpp │ ├── equivalence.cpp │ ├── trans.cpp │ └── run.sh ├── checks │ ├── reflex.ref │ ├── reflex.cpp │ └── run.sh ├── max_element │ ├── bad.ref │ ├── bad.cpp │ └── run.sh ├── shuffle │ ├── repro.ref │ ├── repro.cpp │ └── run.sh ├── output │ ├── example.ref │ ├── example.cpp │ └── run.sh ├── spaceship │ ├── repro.ref │ ├── repro.cpp │ └── run.sh ├── stable_sort │ ├── repro.ref │ ├── repro.cpp │ └── run.sh ├── bsearch-different-types │ ├── repro.ref │ ├── repro.cc │ └── run.sh ├── abort │ ├── abort.ref │ ├── abort.cpp │ └── run.sh ├── rock-scissors-paper │ ├── repro.ref │ ├── run.sh │ └── repro.cpp ├── wrapper │ ├── example.cc │ └── run.sh ├── errors │ ├── error.cc │ └── run.sh ├── bsearch-error │ ├── repro.cc │ └── run.sh ├── builtin │ ├── map.cpp │ ├── builtin.cpp │ ├── string.cpp │ └── run.sh ├── runtests.sh └── verbose │ ├── example.cpp │ └── run.sh ├── .clang-format ├── .gitignore ├── .github └── workflows │ ├── coverity.yml │ └── ci.yml ├── LICENSE.txt ├── include ├── set ├── map ├── multiset ├── multimap └── sortcheck.h ├── Makefile ├── README.md └── src └── SortChecker.cpp /scripts/c++: -------------------------------------------------------------------------------- 1 | cc -------------------------------------------------------------------------------- /scripts/g++: -------------------------------------------------------------------------------- 1 | cc -------------------------------------------------------------------------------- /scripts/gcc: -------------------------------------------------------------------------------- 1 | cc -------------------------------------------------------------------------------- /tests/bsearch/equal.ref: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/bsearch/good.ref: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/map/inherited.ref: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/lower_bound/good.ref: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/x86_64-linux-gnu-g++: -------------------------------------------------------------------------------- 1 | cc -------------------------------------------------------------------------------- /scripts/x86_64-linux-gnu-gcc: -------------------------------------------------------------------------------- 1 | cc -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | --- 3 | Language: Cpp 4 | -------------------------------------------------------------------------------- /tests/map/array.ref: -------------------------------------------------------------------------------- 1 | sortcheck: set:35: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/map/clear.ref: -------------------------------------------------------------------------------- 1 | sortcheck: set:35: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/map/dtor.ref: -------------------------------------------------------------------------------- 1 | sortcheck: map:39: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/map/map.ref: -------------------------------------------------------------------------------- 1 | sortcheck: map:35: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/primitive/repro.ref: -------------------------------------------------------------------------------- 1 | sortcheck: repro.cc:17: unsorted range at position 1 2 | -------------------------------------------------------------------------------- /tests/basic/reflex.ref: -------------------------------------------------------------------------------- 1 | sortcheck: reflex.cpp:21: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/bsearch/bad-full.ref: -------------------------------------------------------------------------------- 1 | sortcheck: bad-full.cpp:24: unsorted range at position 1 2 | -------------------------------------------------------------------------------- /tests/checks/reflex.ref: -------------------------------------------------------------------------------- 1 | sortcheck: reflex.cpp:21: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/max_element/bad.ref: -------------------------------------------------------------------------------- 1 | sortcheck: bad.cpp:21: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/shuffle/repro.ref: -------------------------------------------------------------------------------- 1 | sortcheck: repro.cpp:23: reflexive comparator at position 12 2 | -------------------------------------------------------------------------------- /tests/lower_bound/bad-full.ref: -------------------------------------------------------------------------------- 1 | sortcheck: bad-full.cpp:24: unsorted range at position 1 2 | -------------------------------------------------------------------------------- /tests/output/example.ref: -------------------------------------------------------------------------------- 1 | sortcheck: example.cpp:21: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/spaceship/repro.ref: -------------------------------------------------------------------------------- 1 | sortcheck: repro.cpp:24: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/stable_sort/repro.ref: -------------------------------------------------------------------------------- 1 | sortcheck: repro.cpp:21: reflexive comparator at position 0 2 | -------------------------------------------------------------------------------- /tests/bsearch-different-types/repro.ref: -------------------------------------------------------------------------------- 1 | sortcheck: repro.cc:16: unsorted range at position 2 2 | -------------------------------------------------------------------------------- /tests/lower_bound/equal_range.ref: -------------------------------------------------------------------------------- 1 | sortcheck: equal_range.cpp:24: unsorted range at position 1 2 | -------------------------------------------------------------------------------- /tests/basic/symmetry.ref: -------------------------------------------------------------------------------- 1 | sortcheck: symmetry.cpp:23: non-asymmetric comparator at positions 1 and 0 2 | -------------------------------------------------------------------------------- /tests/basic/trans.ref: -------------------------------------------------------------------------------- 1 | sortcheck: trans.cpp:31: non-transitive comparator at positions 1, 0 and 2 2 | -------------------------------------------------------------------------------- /tests/bsearch/bad-full-array.ref: -------------------------------------------------------------------------------- 1 | sortcheck: bad-full-array.cpp:21: unsorted range at position 1 2 | -------------------------------------------------------------------------------- /tests/abort/abort.ref: -------------------------------------------------------------------------------- 1 | sortcheck: abort.cpp:21: reflexive comparator at position 0 2 | Aborted (core dumped) 3 | -------------------------------------------------------------------------------- /tests/rock-scissors-paper/repro.ref: -------------------------------------------------------------------------------- 1 | sortcheck: repro.cpp:35: non-transitive comparator at positions 1, 0 and 2 2 | -------------------------------------------------------------------------------- /tests/basic/equivalence.ref: -------------------------------------------------------------------------------- 1 | sortcheck: equivalence.cpp:24: non-transitive equivalent comparator at positions 1, 0 and 2 2 | -------------------------------------------------------------------------------- /tests/wrapper/example.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | printf("Hello world!\n"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /scripts/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | set -x 5 | 6 | sudo apt-get -y install libclang-dev llvm llvm-dev python3 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | bin/* 3 | 4 | # Vim files 5 | .*swp 6 | 7 | # Tests 8 | a.out 9 | test.log 10 | *.o 11 | 12 | # GDB 13 | .gdb_history 14 | 15 | # Coverage 16 | *.gcov 17 | *.gcda 18 | *.gcno 19 | -------------------------------------------------------------------------------- /tests/errors/error.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace foo { 4 | 5 | struct Compare { 6 | bool operator()(int, int) { 7 | return false; 8 | } 9 | }; 10 | 11 | void foo(int *p) { 12 | std::sort(p, p + 100, Compare()); 13 | } 14 | -------------------------------------------------------------------------------- /tests/map/inherited.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Key { 4 | public: 5 | bool operator <(const Key &rhs) const; 6 | }; 7 | 8 | class A : public std::map { 9 | void foo() { 10 | clear(); 11 | } 12 | }; 13 | 14 | int main() { return 0; } 15 | -------------------------------------------------------------------------------- /tests/bsearch-error/repro.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct A { 5 | int x; 6 | bool operator<(const A& that) const { 7 | return x < that.x; 8 | } 9 | 10 | }; 11 | 12 | void foo() { 13 | std::vector v; 14 | std::lower_bound(v.begin(), v.end(), A()); 15 | } 16 | -------------------------------------------------------------------------------- /tests/builtin/map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | int main() { 10 | std::map v; 11 | v.clear(); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | set -eu 11 | #set -x 12 | 13 | for d in $(dirname $0)/*; do 14 | ! test -d $d || $d/run.sh 15 | done 16 | -------------------------------------------------------------------------------- /tests/builtin/builtin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | int main() { 10 | std::vector v; 11 | v.push_back(3); 12 | v.push_back(2); 13 | v.push_back(1); 14 | std::sort(v.begin(), v.end()); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /tests/builtin/string.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int main() { 11 | std::vector v; 12 | v.push_back("a"); 13 | v.push_back("b"); 14 | v.push_back("c"); 15 | std::sort(v.begin(), v.end()); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /tests/map/dtor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | 8 | struct Compare { 9 | bool operator()(int lhs, int rhs) const { 10 | return lhs == rhs; 11 | } 12 | }; 13 | 14 | int main() { 15 | std::map v; 16 | v[1] = 1; 17 | v[3] = 3; 18 | v[2] = 2; 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /tests/map/map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | 8 | struct Compare { 9 | bool operator()(int lhs, int rhs) const { 10 | return lhs == rhs; 11 | } 12 | }; 13 | 14 | int main() { 15 | std::map v; 16 | v[1] = 1; 17 | v[3] = 3; 18 | v[2] = 2; 19 | v.clear(); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/map/clear.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | 8 | struct Compare { 9 | bool operator()(int lhs, int rhs) const { 10 | return lhs == rhs; 11 | } 12 | }; 13 | 14 | int main() { 15 | std::set v; 16 | v.insert(1); 17 | v.insert(3); 18 | v.insert(2); 19 | v.clear(); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/bsearch-different-types/repro.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool checkForImplicitGCCall(const char *name) { 5 | static const std::string names[] = { 6 | "A", 7 | #ifdef INVALID 8 | "C", 9 | "B", 10 | #else 11 | "B", 12 | "C", 13 | #endif 14 | }; 15 | return std::binary_search(&names[0], &names[sizeof(names) / sizeof(std::string)], name); 16 | } 17 | 18 | int main() { 19 | return checkForImplicitGCCall("B") ? 0 : 1; 20 | } 21 | -------------------------------------------------------------------------------- /tests/primitive/repro.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | int main() { 10 | std::vector v; 11 | // Vector is not correctly sorted 12 | // but partiality check alone does not detect it. 13 | v.push_back(1); 14 | v.push_back(3); 15 | v.push_back(2); 16 | std::binary_search(v.begin(), v.end(), 0); 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /tests/bsearch-error/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that primitive types are not instrumented. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | c++ -c $CXXFLAGS repro.cc 23 | 24 | echo SUCCESS 25 | -------------------------------------------------------------------------------- /tests/map/array.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | 8 | struct Compare { 9 | bool operator()(int lhs, int rhs) const { 10 | return lhs == rhs; 11 | } 12 | }; 13 | 14 | int main() { 15 | std::set *v[2] = {new std::set(), 0}; 16 | v[0]->insert(1); 17 | v[0]->insert(3); 18 | v[0]->insert(2); 19 | v[0]->clear(); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: Coverity 2 | on: 3 | schedule: 4 | # Run on Mondays 5 | - cron: '0 5 * * MON' 6 | jobs: 7 | Coverity: 8 | runs-on: ubuntu-latest 9 | environment: secrets 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install deps 13 | run: scripts/install_deps.sh 14 | - uses: vapier/coverity-scan-action@v0 15 | with: 16 | project: yugr%2Fsortcheckxx 17 | token: ${{ secrets.COVERITY_SCAN_TOKEN }} 18 | email: ${{ secrets.EMAIL }} 19 | -------------------------------------------------------------------------------- /tests/stable_sort/repro.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct Compare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs == rhs; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(1); 18 | v.push_back(3); 19 | v.push_back(2); 20 | std::stable_sort(v.begin(), v.end(), Compare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/abort/abort.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs != rhs ? lhs < rhs : true; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(3); 18 | v.push_back(2); 19 | v.push_back(1); 20 | std::sort(v.begin(), v.end(), BadCompare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/basic/reflex.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs != rhs ? lhs < rhs : true; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(3); 18 | v.push_back(2); 19 | v.push_back(1); 20 | std::sort(v.begin(), v.end(), BadCompare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/checks/reflex.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs != rhs ? lhs < rhs : true; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(3); 18 | v.push_back(2); 19 | v.push_back(1); 20 | std::sort(v.begin(), v.end(), BadCompare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/output/example.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs != rhs ? lhs < rhs : true; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(3); 18 | v.push_back(2); 19 | v.push_back(1); 20 | std::sort(v.begin(), v.end(), BadCompare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/verbose/example.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs != rhs ? lhs < rhs : true; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(3); 18 | v.push_back(2); 19 | v.push_back(1); 20 | std::sort(v.begin(), v.end(), BadCompare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/max_element/bad.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | return lhs != rhs ? lhs < rhs : true; 12 | } 13 | }; 14 | 15 | int main() { 16 | std::vector v; 17 | v.push_back(3); 18 | v.push_back(2); 19 | v.push_back(1); 20 | std::max_element(v.begin(), v.end(), BadCompare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/verbose/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that -v works. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | 19 | $ROOT/bin/SortChecker -v example.cpp -- > test.log 2>&1 20 | if ! grep -q 'Found relevant function' test.log; then 21 | echo >&2 'Missing verbose print' 22 | exit 1 23 | fi 24 | 25 | echo SUCCESS 26 | -------------------------------------------------------------------------------- /tests/errors/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | set -eu 11 | #set -x 12 | 13 | cd $(dirname $0) 14 | 15 | ROOT=$PWD/../.. 16 | PATH=$ROOT/scripts:$PATH 17 | 18 | if $ROOT/bin/SortChecker error.cc -- 2>/dev/null; then 19 | echo >&2 "Error not reported correctly" 20 | exit 1 21 | fi 22 | 23 | $ROOT/bin/SortChecker --ignore-parse-errors error.cc -- 24 | 25 | echo SUCCESS 26 | -------------------------------------------------------------------------------- /tests/basic/symmetry.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | if (lhs != rhs) 12 | return true; 13 | return false; 14 | } 15 | }; 16 | 17 | int main() { 18 | std::vector v; 19 | v.push_back(3); 20 | v.push_back(2); 21 | v.push_back(1); 22 | std::sort(v.begin(), v.end(), BadCompare()); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tests/shuffle/repro.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct Compare { 10 | bool operator()(int a, int b) { 11 | if (a == 100) 12 | return true; 13 | return a != b; 14 | } 15 | }; 16 | 17 | int main() { 18 | std::vector v; 19 | for (size_t i = 0; i < 32; ++i) 20 | v.push_back(0); 21 | v.push_back(100); 22 | std::sort(v.begin(), v.end(), Compare()); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tests/builtin/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that primitive types are not instrumented. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | 19 | for file in builtin.cpp string.cpp map.cpp; do 20 | $ROOT/bin/SortChecker $file -- 21 | if grep -q sortcheck $file; then 22 | echo >&2 'Unexpected modifications' 23 | exit 1 24 | fi 25 | done 26 | 27 | echo SUCCESS 28 | -------------------------------------------------------------------------------- /tests/bsearch/equal.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | std::vector v; 18 | v.push_back(10); 19 | v.push_back(10); 20 | v.push_back(20); 21 | v.push_back(20); 22 | std::binary_search(v.begin(), v.end(), 15, Compare()); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tests/bsearch/good.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | std::vector v; 18 | v.push_back(0); 19 | v.push_back(10); 20 | v.push_back(20); 21 | v.push_back(30); 22 | std::binary_search(v.begin(), v.end(), 10, Compare()); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tests/lower_bound/good.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | std::vector v; 18 | v.push_back(0); 19 | v.push_back(10); 20 | v.push_back(20); 21 | v.push_back(30); 22 | std::lower_bound(v.begin(), v.end(), 10, Compare()); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tests/bsearch/bad-full-array.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | // Vector is not correctly sorted 18 | // but partiality check alone does not detect it. 19 | int v[] = {1, 3, 2}; 20 | std::binary_search(&v[0], &v[3], 0, Compare()); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/spaceship/repro.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | struct A { 11 | int x; 12 | A(int x): x(x) {} 13 | std::weak_ordering operator <=>(A rhs) const { 14 | return x == rhs.x ? std::weak_ordering::less : std::weak_ordering::equivalent; 15 | } 16 | }; 17 | 18 | int main() { 19 | std::vector v; 20 | v.push_back(1); 21 | v.push_back(3); 22 | v.push_back(2); 23 | std::sort(v.begin(), v.end()); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tests/wrapper/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that wrapper handles various weird cases. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | # Separate compilation 21 | cc example.cc -c 22 | cc example.o 23 | ./a.out | grep -q 'Hello world' 24 | 25 | # CMake-style object names 26 | cc example.cc -c -o example.cc.o 27 | cc example.cc.o 28 | ./a.out | grep -q 'Hello world' 29 | 30 | echo SUCCESS 31 | -------------------------------------------------------------------------------- /tests/basic/equivalence.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | // Example from https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings 11 | bool operator()(int lhs, int rhs) { 12 | if (lhs == 2 && rhs == 3) 13 | return true; 14 | return false; 15 | } 16 | }; 17 | 18 | int main() { 19 | std::vector v; 20 | v.push_back(1); 21 | v.push_back(2); 22 | v.push_back(3); 23 | std::sort(v.begin(), v.end(), BadCompare()); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tests/bsearch/bad-full.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | std::vector v; 18 | // Vector is not correctly sorted 19 | // but partiality check alone does not detect it. 20 | v.push_back(1); 21 | v.push_back(3); 22 | v.push_back(2); 23 | std::binary_search(v.begin(), v.end(), 0, Compare()); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tests/lower_bound/bad-full.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | std::vector v; 18 | // Vector is not correctly sorted 19 | // but partiality check alone does not detect it. 20 | v.push_back(1); 21 | v.push_back(3); 22 | v.push_back(2); 23 | std::lower_bound(v.begin(), v.end(), 0, Compare()); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tests/lower_bound/equal_range.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | // Use non-default comparison to trigger instrumentation 10 | struct Compare { 11 | bool operator()(int lhs, int rhs) { 12 | return lhs < rhs; 13 | } 14 | }; 15 | 16 | int main() { 17 | std::vector v; 18 | // Vector is not correctly sorted 19 | // but partiality check alone does not detect it. 20 | v.push_back(1); 21 | v.push_back(3); 22 | v.push_back(2); 23 | std::equal_range(v.begin(), v.end(), 0, Compare()); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tests/primitive/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check bsearch instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | c++ repro.cc $CXXFLAGS 25 | ./a.out > test.log 2>&1 || true 26 | 27 | if ! diff -q repro.ref test.log; then 28 | echo >&2 'Test did not produce expected output:' 29 | diff repro.ref test.log >&2 30 | exit 1 31 | fi 32 | 33 | echo SUCCESS 34 | -------------------------------------------------------------------------------- /tests/stable_sort/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check stable_sort instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | c++ repro.cpp $CXXFLAGS 25 | ./a.out > test.log 2>&1 || true 26 | if ! diff -q repro.ref test.log; then 27 | echo >&2 'Test did not produce expected output:' 28 | diff repro.ref test.log >&2 29 | exit 1 30 | fi 31 | 32 | echo SUCCESS 33 | -------------------------------------------------------------------------------- /tests/rock-scissors-paper/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2023 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check bsearch instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | c++ repro.cpp $CXXFLAGS 25 | ./a.out > test.log 2>&1 || true 26 | if ! diff -q repro.ref test.log; then 27 | echo >&2 'Test did not produce expected output:' 28 | diff repro.ref test.log >&2 29 | exit 1 30 | fi 31 | 32 | echo SUCCESS 33 | -------------------------------------------------------------------------------- /tests/abort/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that SORTCHECK_ABORT works. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | ulimit -c 1024 23 | 24 | c++ $CXXFLAGS abort.cpp 25 | if ./a.out > test.log 2>&1; then 26 | echo >&2 'Test did not fail as expected' 27 | exit 1 28 | fi 29 | if ! diff -q abort.ref test.log; then 30 | echo >&2 'Test did not produce expected output:' 31 | diff abort.ref test.log >&2 32 | exit 1 33 | fi 34 | 35 | echo SUCCESS 36 | -------------------------------------------------------------------------------- /tests/basic/trans.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | struct BadCompare { 10 | bool operator()(int lhs, int rhs) { 11 | if (lhs == rhs) 12 | return false; 13 | if (lhs > rhs) 14 | return ! operator()(rhs, lhs); 15 | if (lhs == 1 && rhs == 2) 16 | return true; 17 | if (lhs == 2 && rhs == 3) 18 | return true; 19 | if (lhs == 1 && rhs == 3) 20 | return false; 21 | return false; 22 | } 23 | }; 24 | 25 | int main() { 26 | std::vector v; 27 | v.push_back(3); 28 | v.push_back(2); 29 | v.push_back(1); 30 | std::sort(v.begin(), v.end(), BadCompare()); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /tests/spaceship/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2023 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check spaceship instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g -std=c++20' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | if ! g++ $CXXFLAGS -S -x c++ /dev/null 2>/dev/null; then 25 | # C++20 is not supported 26 | exit 0 27 | fi 28 | 29 | c++ repro.cpp $CXXFLAGS 30 | ./a.out > test.log 2>&1 || true 31 | if ! diff -q repro.ref test.log; then 32 | echo >&2 'Test did not produce expected output:' 33 | diff repro.ref test.log >&2 34 | exit 1 35 | fi 36 | 37 | echo SUCCESS 38 | -------------------------------------------------------------------------------- /tests/max_element/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check std::sort instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | if test -n "${COVERAGE:-}"; then 23 | CXXFLAGS="$CXXFLAGS --coverage" 24 | fi 25 | 26 | c++ $CXXFLAGS bad.cpp 27 | 28 | export SORTCHECK_ABORT=0 29 | 30 | if ./a.out > test.log 2>&1; then 31 | echo >&2 'Test did not fail as expected' 32 | exit 1 33 | fi 34 | if ! diff -q bad.ref test.log; then 35 | echo >&2 'Test did not produce expected output:' 36 | diff bad.ref test.log >&2 37 | exit 1 38 | fi 39 | 40 | echo SUCCESS 41 | -------------------------------------------------------------------------------- /tests/map/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check bsearch instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | for std in gnu++11 c++98 c++11 c++14 c++17; do 25 | for t in *.cpp; do 26 | stem=$(echo $t | sed 's/\.cpp//') 27 | c++ $t $CXXFLAGS -std=$std 28 | ./a.out > test.log 2>&1 || true 29 | if ! diff -q $stem.ref test.log; then 30 | echo >&2 'Test did not produce expected output:' 31 | diff $stem.ref test.log >&2 32 | exit 1 33 | fi 34 | done 35 | done 36 | 37 | echo SUCCESS 38 | -------------------------------------------------------------------------------- /tests/rock-scissors-paper/repro.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include 7 | #include 8 | 9 | enum class RSP { 10 | Rock, 11 | Scissors, 12 | Paper 13 | }; 14 | 15 | struct Compare { 16 | bool operator()(RSP a, RSP b) { 17 | if (a == b) 18 | return false; 19 | if (a == RSP::Scissors && b == RSP::Rock) 20 | return false; 21 | if (a == RSP::Rock && b == RSP::Paper) 22 | return false; 23 | if (a == RSP::Paper && b == RSP::Scissors) 24 | return false; 25 | return ! operator()(b, a); 26 | } 27 | }; 28 | 29 | int main() { 30 | std::vector v; 31 | v.push_back(RSP::Rock); 32 | v.push_back(RSP::Scissors); 33 | v.push_back(RSP::Paper); 34 | std::stable_sort(v.begin(), v.end(), Compare()); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /tests/shuffle/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2023 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check bsearch instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | c++ repro.cpp $CXXFLAGS 25 | 26 | ./a.out > test.log 2>&1 || true 27 | if grep 'reflexive comparator' test.log; then 28 | echo >&2 'Reference reported unexpected error' 29 | cat test.log >&2 30 | exit 1 31 | fi 32 | 33 | SORTCHECK_SHUFFLE=0 ./a.out > test.log 2>&1 || true 34 | if ! diff -q repro.ref test.log; then 35 | echo >&2 'Modified test did not produce expected output:' 36 | diff repro.ref test.log >&2 37 | exit 1 38 | fi 39 | 40 | echo SUCCESS 41 | -------------------------------------------------------------------------------- /tests/output/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that -v works. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | if test "${COVERAGE:-}"; then 22 | CXXFLAGS="$CXXFLAGS --coverage" 23 | fi 24 | 25 | g++ $CXXFLAGS example.cpp 26 | 27 | export SORTCHECK_OUTPUT=file.txt 28 | rm -f $SORTCHECK_OUTPUT 29 | 30 | if ./a.out > test.log 2>&1; then 31 | echo >&2 'Test did not fail as expected' 32 | exit 1 33 | fi 34 | if ! diff -q example.ref $SORTCHECK_OUTPUT; then 35 | echo >&2 'Test did not produce expected output:' 36 | diff example.ref $SORTCHECK_OUTPUT >&2 37 | exit 1 38 | fi 39 | 40 | rm -f $SORTCHECK_OUTPUT 41 | 42 | echo SUCCESS 43 | -------------------------------------------------------------------------------- /tests/bsearch-different-types/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check that primitive types are not instrumented. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | c++ $CXXFLAGS repro.cc 25 | if ! ./a.out > test.log 2>&1; then 26 | echo >&2 'Valid test did not run as expected' 27 | cat test.log >&2 28 | exit 1 29 | fi 30 | 31 | c++ $CXXFLAGS -DINVALID repro.cc 32 | if ./a.out > test.log 2>&1; then 33 | echo >&2 'Test did not fail as expected' 34 | exit 1 35 | fi 36 | if ! diff -q repro.ref test.log; then 37 | echo >&2 'Test did not produce expected output:' 38 | diff repro.ref test.log >&2 39 | exit 1 40 | fi 41 | 42 | echo SUCCESS 43 | -------------------------------------------------------------------------------- /tests/checks/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check std::sort instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | if test -n "${COVERAGE:-}"; then 23 | CXXFLAGS="$CXXFLAGS --coverage" 24 | fi 25 | 26 | c++ $CXXFLAGS reflex.cpp 27 | 28 | export SORTCHECK_ABORT=0 29 | 30 | if ./a.out > test.log 2>&1; then 31 | echo >&2 'Test did not fail as expected' 32 | exit 1 33 | fi 34 | if ! diff -q reflex.ref test.log; then 35 | echo >&2 'Test did not produce expected output:' 36 | diff reflex.ref test.log >&2 37 | exit 1 38 | fi 39 | 40 | export SORTCHECK_CHECKS=0xfe 41 | 42 | if ! ./a.out > test.log 2>&1; then 43 | echo >&2 'Test did not succeed as expected' 44 | exit 1 45 | fi 46 | 47 | echo SUCCESS 48 | -------------------------------------------------------------------------------- /scripts/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2022 Yury Gribov 4 | # 5 | # Use of this source code is governed by MIT license that can be 6 | # found in the LICENSE.txt file. 7 | 8 | set -eu 9 | 10 | if test -n "${TRAVIS:-}" -o -n "${GITHUB_ACTIONS:-}"; then 11 | set -x 12 | fi 13 | 14 | if test -n "${GITHUB_ACTIONS:-}" && grep -q 20.04 /etc/lsb-release; then 15 | # Versions of clang and libclang in Github's Ubuntu 20.04 mismatch 16 | # which causes header search issues so fix this 17 | V=$(llvm-config --version | awk -F. '{print $1}') 18 | sudo ln -fs /usr/bin/clang-$V /usr/bin/clang 19 | fi 20 | 21 | cd $(dirname $0)/.. 22 | 23 | export ASAN_OPTIONS='detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:strict_string_checks=1' 24 | 25 | make "$@" clean all 26 | make "$@" check 27 | 28 | # Upload coverage 29 | if test -n "${COVERAGE:-}"; then 30 | # TODO: collect coverage for sortcheck.h and tests 31 | curl --retry 5 -s https://codecov.io/bash > codecov.bash 32 | bash codecov.bash -Z 33 | fi 34 | -------------------------------------------------------------------------------- /tests/bsearch/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check bsearch instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | for std in c++98 c++11 c++14 c++17; do 25 | for test in *.cpp; do 26 | stem=$(echo $test | sed 's/\.cpp//') 27 | if test $std = c++17 -a -n "${COVERAGE:-}"; then 28 | CXXFLAGS_EXTRA=--coverage 29 | else 30 | CXXFLAGS_EXTRA= 31 | fi 32 | c++ $test $CXXFLAGS $CXXFLAGS_EXTRA -std=$std 33 | ./a.out > test.log 2>&1 || true 34 | if ! diff -q $stem.ref test.log; then 35 | echo >&2 'Test did not produce expected output:' 36 | diff $stem.ref test.log >&2 37 | exit 1 38 | fi 39 | done 40 | done 41 | 42 | echo SUCCESS 43 | -------------------------------------------------------------------------------- /tests/lower_bound/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check bsearch instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | INC=$ROOT/include 20 | 21 | CXXFLAGS='-Wall -Wextra -Werror -g' 22 | 23 | export SORTCHECK_ABORT=0 24 | 25 | for std in c++98 c++11 c++14 c++17; do 26 | for test in *.cpp; do 27 | stem=$(echo $test | sed 's/\.cpp//') 28 | if test $std = c++17 -a -n "${COVERAGE:-}"; then 29 | CXXFLAGS_EXTRA=--coverage 30 | else 31 | CXXFLAGS_EXTRA= 32 | fi 33 | c++ $test $CXXFLAGS $CXXFLAGS_EXTRA -std=$std 34 | ./a.out > test.log 2>&1 || true 35 | if ! diff -q $stem.ref test.log; then 36 | echo >&2 'Test did not produce expected output:' 37 | diff $stem.ref test.log >&2 38 | exit 1 39 | fi 40 | done 41 | done 42 | 43 | echo SUCCESS 44 | -------------------------------------------------------------------------------- /tests/basic/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2022 Yury Gribov 6 | # 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | # Check std::sort instrumentation. 11 | 12 | set -eu 13 | #set -x 14 | 15 | cd $(dirname $0) 16 | 17 | ROOT=$PWD/../.. 18 | PATH=$ROOT/scripts:$PATH 19 | 20 | CXXFLAGS='-Wall -Wextra -Werror -g' 21 | 22 | export SORTCHECK_ABORT=0 23 | 24 | for std in gnu++11 c++98 c++11 c++14 c++17; do 25 | for test in *.cpp; do 26 | stem=$(echo $test | sed 's/\.cpp//') 27 | if test $std = gnu++11 -a -n "${COVERAGE:-}"; then 28 | CXXFLAGS_EXTRA=--coverage 29 | else 30 | CXXFLAGS_EXTRA= 31 | fi 32 | c++ $test $CXXFLAGS $CXXFLAGS_EXTRA -std=$std 33 | if ./a.out > test.log 2>&1; then 34 | echo >&2 'Test did not fail as expected' 35 | exit 1 36 | fi 37 | if ! diff -q $stem.ref test.log; then 38 | echo >&2 'Test did not produce expected output:' 39 | diff $stem.ref test.log >&2 40 | exit 1 41 | fi 42 | done 43 | done 44 | 45 | echo SUCCESS 46 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2024 Yury Gribov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/set: -------------------------------------------------------------------------------- 1 | #ifndef SORTCHECK_SET_H 2 | #define SORTCHECK_SET_H 3 | 4 | // Do not warn on include_next 5 | #pragma GCC system_header 6 | 7 | #define set set_impl 8 | #include_next 9 | #undef set 10 | 11 | #include 12 | 13 | namespace std { 14 | 15 | template , 16 | class Allocator = std::allocator > 17 | class set : public set_impl { 18 | typedef set_impl _Parent; 19 | 20 | public: 21 | #if __cplusplus >= 201100L 22 | using _Parent::set_impl; 23 | #else 24 | set() {} 25 | explicit set(const Compare &comp, const Allocator &alloc = Allocator()) 26 | : _Parent(comp, alloc) {} 27 | template 28 | set(InputIt first, InputIt last, const Compare &comp = Compare(), 29 | const Allocator &alloc = Allocator()) 30 | : _Parent(first, last, comp, alloc) {} 31 | set(const set &other) : _Parent(other) {} 32 | #endif 33 | 34 | void clear() SORTCHECK_NOEXCEPT(0) { 35 | sortcheck::check_set(this, "set", __LINE__); 36 | set_impl::clear(); 37 | } 38 | 39 | ~set() { sortcheck::check_set(this, "set", __LINE__); } 40 | }; 41 | 42 | } // namespace std 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/map: -------------------------------------------------------------------------------- 1 | #ifndef SORTCHECK_MAP_H 2 | #define SORTCHECK_MAP_H 3 | 4 | // Do not warn on include_next 5 | #pragma GCC system_header 6 | 7 | #define map map_impl 8 | #include_next 9 | #undef map 10 | 11 | #include 12 | 13 | namespace std { 14 | 15 | template , 16 | class Allocator = std::allocator > > 17 | class map : public map_impl { 18 | typedef map_impl _Parent; 19 | 20 | public: 21 | #if __cplusplus >= 201100L 22 | using _Parent::map_impl; 23 | #else 24 | map() {} 25 | explicit map(const Compare &comp, const Allocator &alloc = Allocator()) 26 | : _Parent(comp, alloc) {} 27 | template 28 | map(InputIt first, InputIt last, const Compare &comp = Compare(), 29 | const Allocator &alloc = Allocator()) 30 | : _Parent(first, last, comp, alloc) {} 31 | map(const map &other) : _Parent(other) {} 32 | #endif 33 | 34 | void clear() SORTCHECK_NOEXCEPT(0) { 35 | sortcheck::check_map(this, "map", __LINE__); 36 | _Parent::clear(); 37 | } 38 | 39 | ~map() { sortcheck::check_map(this, "map", __LINE__); } 40 | }; 41 | 42 | } // namespace std 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/multiset: -------------------------------------------------------------------------------- 1 | #ifndef SORTCHECK_MULTISET_H 2 | #define SORTCHECK_MULTISET_H 3 | 4 | // Do not warn on include_next 5 | #pragma GCC system_header 6 | 7 | #define multiset multiset_impl 8 | #include_next 9 | #undef multiset 10 | 11 | #include 12 | 13 | namespace std { 14 | 15 | template , 16 | class Allocator = std::allocator > 17 | class multiset : public multiset_impl { 18 | typedef multiset_impl _Parent; 19 | 20 | public: 21 | #if __cplusplus >= 201100L 22 | using _Parent::multiset_impl; 23 | #else 24 | multiset() {} 25 | explicit multiset(const Compare &comp, const Allocator &alloc = Allocator()) 26 | : _Parent(comp, alloc) {} 27 | template 28 | multiset(InputIt first, InputIt last, const Compare &comp = Compare(), 29 | const Allocator &alloc = Allocator()) 30 | : _Parent(first, last, comp, alloc) {} 31 | multiset(const multiset &other) : _Parent(other) {} 32 | #endif 33 | 34 | void clear() SORTCHECK_NOEXCEPT(0) { 35 | sortcheck::check_multiset(this, "multiset", __LINE__); 36 | multiset_impl::clear(); 37 | } 38 | 39 | ~multiset() { sortcheck::check_multiset(this, "multiset", __LINE__); } 40 | }; 41 | 42 | } // namespace std 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/multimap: -------------------------------------------------------------------------------- 1 | #ifndef SORTCHECK_MULTIMAP_H 2 | #define SORTCHECK_MULTIMAP_H 3 | 4 | // Do not warn on include_next 5 | #pragma GCC system_header 6 | 7 | #define multimap multimap_impl 8 | #include_next 9 | #undef multimap 10 | 11 | #include 12 | 13 | namespace std { 14 | 15 | template , 16 | class Allocator = std::allocator > > 17 | class multimap : public multimap_impl { 18 | typedef multimap_impl _Parent; 19 | 20 | public: 21 | #if __cplusplus >= 201100L 22 | using _Parent::multimap_impl; 23 | #else 24 | multimap() {} 25 | explicit multimap(const Compare &comp, const Allocator &alloc = Allocator()) 26 | : _Parent(comp, alloc) {} 27 | template 28 | multimap(InputIt first, InputIt last, const Compare &comp = Compare(), 29 | const Allocator &alloc = Allocator()) 30 | : _Parent(first, last, comp, alloc) {} 31 | multimap(const multimap &other) : _Parent(other) {} 32 | #endif 33 | 34 | void clear() SORTCHECK_NOEXCEPT(0) { 35 | sortcheck::check_multimap(this, "multimap", __LINE__); 36 | multimap_impl::clear(); 37 | } 38 | 39 | ~multimap() { sortcheck::check_multimap(this, "multimap", __LINE__); } 40 | }; 41 | 42 | } // namespace std 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | paths-ignore: 5 | - 'LICENSE.txt' 6 | - 'README.md' 7 | - '.gitignore' 8 | pull_request: 9 | paths-ignore: 10 | - 'LICENSE.txt' 11 | - 'README.md' 12 | - '.gitignore' 13 | jobs: 14 | Tests: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-20.04, ubuntu-22.04, ubuntu-latest] 19 | cxx: [g++, clang++] 20 | runs-on: ${{ matrix.os }} 21 | env: 22 | CXX: ${{ matrix.cxx }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Install deps 26 | run: scripts/install_deps.sh 27 | - name: Run tests 28 | run: scripts/travis.sh 29 | CSA: 30 | runs-on: ubuntu-latest 31 | env: 32 | CXX: clang 33 | steps: 34 | - uses: actions/checkout@v2 35 | - name: Install deps 36 | run: | 37 | scripts/install_deps.sh 38 | sudo apt-get install clang-tools 39 | - name: Run tests 40 | run: scan-build --keep-going --status-bugs scripts/travis.sh 41 | Asan: 42 | runs-on: ubuntu-latest 43 | env: 44 | CXX: g++ # Clang's interceptor runs comparator prior to our interceptor and ruins some tests 45 | ASAN: 1 46 | steps: 47 | - uses: actions/checkout@v2 48 | - name: Install deps 49 | run: scripts/install_deps.sh 50 | - name: Run tests 51 | run: scripts/travis.sh 52 | UBsan: 53 | runs-on: ubuntu-latest 54 | env: 55 | CXX: clang++ 56 | UBSAN: 1 57 | steps: 58 | - uses: actions/checkout@v2 59 | - name: Install deps 60 | run: scripts/install_deps.sh 61 | - name: Run tests 62 | run: scripts/travis.sh 63 | Coverage: 64 | needs: Tests 65 | runs-on: ubuntu-latest 66 | environment: secrets 67 | env: 68 | COVERAGE: 1 69 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 70 | steps: 71 | - uses: actions/checkout@v2 72 | - name: Install deps 73 | run: scripts/install_deps.sh 74 | - name: Run tests 75 | run: scripts/travis.sh 76 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2022 Yury Gribov 4 | # 5 | # Use of this source code is governed by The MIT License (MIT) 6 | # that can be found in the LICENSE.txt file. 7 | 8 | CXX ?= g++ 9 | LLVM_CONFIG ?= llvm-config 10 | DESTDIR ?= /usr/local 11 | 12 | CPPFLAGS = $(shell $(LLVM_CONFIG) --cppflags) -isystem $(shell $(LLVM_CONFIG) --includedir) -DLLVM_ENABLE_ASSERTIONS 13 | CXXFLAGS = $(shell $(LLVM_CONFIG) --cxxflags) -std=c++17 -g -Wall -Wextra -Werror 14 | LDFLAGS = $(shell $(LLVM_CONFIG) --ldflags) -Wl,--warn-common 15 | 16 | ifneq (,$(shell $(CXX) --version | grep clang)) 17 | # LLVM flags on 18.04 are terribly broken 18 | CXXFLAGS += -Wno-unused-command-line-argument -Wno-unknown-warning-option 19 | endif 20 | 21 | LLVM_LIBDIR = $(shell $(LLVM_CONFIG) --libdir) 22 | LIBS = -Wl,--start-group $(shell find $(LLVM_LIBDIR) -name 'libclang[A-Z]*.a') -Wl,--end-group $(shell $(LLVM_CONFIG) --libs --system-libs) 23 | 24 | ifneq (,$(COVERAGE)) 25 | DEBUG = 1 26 | CXXFLAGS += --coverage -DNDEBUG 27 | LDFLAGS += --coverage 28 | endif 29 | ifeq (,$(DEBUG)) 30 | CXXFLAGS += -O2 31 | LDFLAGS += -Wl,-O2 32 | else 33 | CXXFLAGS += -O0 34 | endif 35 | ifneq (,$(ASAN)) 36 | CXXFLAGS += -fsanitize=address -fsanitize-address-use-after-scope -U_FORTIFY_SOURCE -fno-common -D_GLIBCXX_SANITIZE_VECTOR 37 | LDFLAGS += -fsanitize=address 38 | endif 39 | ifneq (,$(UBSAN)) 40 | # Isan finds issues in Clang itself so disable it 41 | CXXFLAGS += -fsanitize=undefined -fno-sanitize-recover=undefined 42 | LDFLAGS += -fsanitize=undefined -fno-sanitize-recover=undefined 43 | endif 44 | 45 | $(shell mkdir -p bin) 46 | 47 | all: bin/SortChecker 48 | 49 | bin/SortChecker: bin/SortChecker.o Makefile bin/FLAGS 50 | $(CXX) $(LDFLAGS) -o $@ $(filter %.o, $^) $(LIBS) 51 | 52 | bin/%.o: src/%.cpp Makefile bin/FLAGS 53 | $(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ -c $< 54 | 55 | bin/FLAGS: FORCE 56 | if test x"$(CFLAGS) $(CXXFLAGS) $(LDFLAGS)" != x"$$(cat $@)"; then \ 57 | echo "$(CFLAGS) $(CXXFLAGS) $(LDFLAGS)" > $@; \ 58 | fi 59 | 60 | check: 61 | @tests/runtests.sh 62 | 63 | clean: 64 | rm -f bin/* 65 | find -name \*.gcov -o -name \*.gcda -o -name \*.gcno | xargs rm -f 66 | 67 | .PHONY: clean all check FORCE 68 | 69 | -------------------------------------------------------------------------------- /scripts/cc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # The MIT License (MIT) 4 | #▫ 5 | # Copyright (c) 2022 Yury Gribov 6 | #▫ 7 | # Use of this source code is governed by The MIT License (MIT) 8 | # that can be found in the LICENSE.txt file. 9 | 10 | import os.path 11 | import re 12 | import subprocess 13 | import sys 14 | 15 | me = os.path.basename(__file__) 16 | 17 | def warn(msg): 18 | "Print nicely-formatted warning message" 19 | sys.stderr.write('%s: warning: %s\n' % (me, msg)) 20 | 21 | def error(msg): 22 | "Print nicely-formatted error message and exit." 23 | sys.stderr.write('%s: error: %s\n' % (me, msg)) 24 | sys.exit(1) 25 | 26 | def run(cmd, **kwargs): 27 | "Simple wrapper for subprocess" 28 | if 'fatal' in kwargs: 29 | fatal = kwargs['fatal'] 30 | del kwargs['fatal'] 31 | else: 32 | fatal = False 33 | if 'tee' in kwargs: 34 | tee = kwargs['tee'] 35 | del kwargs['tee'] 36 | else: 37 | tee = False 38 | if isinstance(cmd, str): 39 | cmd = cmd.split(' ') 40 | # print(cmd) 41 | p = subprocess.run(cmd, stdin=None, stdout=subprocess.PIPE, 42 | stderr=subprocess.PIPE, **kwargs) 43 | out = p.stdout.decode() 44 | err = p.stderr.decode() 45 | if fatal and p.returncode != 0: 46 | error("'%s' failed:\n%s%s" % (' '.join(cmd), out, err)) 47 | if tee: 48 | sys.stdout.write(out) 49 | sys.stderr.write(err) 50 | return p.returncode, out, err 51 | 52 | def check_versions(): 53 | "Check that versions of clang and libclang match" 54 | 55 | _, out, _ = run("clang --version", fatal=True) 56 | m = re.search('clang version ([0-9.]+)', out) 57 | if m is None: 58 | error("failed to get clang version") 59 | clang_version = m[1] 60 | 61 | _, libclang_version, _ = run("llvm-config --version", fatal=True) 62 | libclang_version = libclang_version.strip() 63 | 64 | if clang_version != libclang_version: 65 | warn(f"clang and libclang versions do not match: {clang_version} vs {libclang_version}") 66 | 67 | def is_bad_flag(f): 68 | "GCC-specific flags (-flto=jobserver causes clang to malfunction)" 69 | return re.match(r'-f(lifetime-dse|abi-version|var-tracking|lto)', f) 70 | 71 | def get_std_includes(typ): 72 | "Get paths to std headers from clang driver" 73 | check_versions() 74 | _, _, err = run(f"clang -x {typ} -E -Wp,-v /dev/null", fatal=True) 75 | return [line.strip() for line in err.split('\n') if line.startswith(" /")] 76 | 77 | if "++" in me: 78 | real_exe = "g++" 79 | typ = "c++" 80 | else: 81 | real_exe = "gcc" 82 | typ = "c" 83 | 84 | root = os.path.join(os.path.dirname(__file__), '..') 85 | 86 | # Why is libtooling interface so useless... 87 | opts = [] 88 | files = [] 89 | instrument = True 90 | for arg in sys.argv[1:]: 91 | _, ext = os.path.splitext(arg) 92 | if ext in ('.cc', '.cpp', '.c', '.cxx', '.C'): 93 | files.append(arg) 94 | continue 95 | 96 | if arg in ('-E', '-M', '-MM'): 97 | instrument = True 98 | break 99 | 100 | if is_bad_flag(arg): 101 | continue 102 | 103 | opts.append(arg) 104 | 105 | if files and instrument: 106 | # Run wrapper 107 | 108 | hdr_path = os.path.join(root, 'include') 109 | incs = [f"-I{inc}" for inc in get_std_includes(typ) + [hdr_path]] 110 | 111 | rc, _, _ = run([os.path.join(root, 'bin/SortChecker')] 112 | + files 113 | + ["--"] 114 | + opts 115 | + incs 116 | + ["-fpermissive", "-w", "-Wno-everything", "-Wno-error"], 117 | tee=True) 118 | if rc != 0: 119 | sys.stderr.write(f"SortChecker: failed to compile {files}\n") 120 | 121 | sys.argv.append(f"-I{hdr_path}") 122 | 123 | # Run real compiler 124 | 125 | args = [os.path.join("/usr/bin", real_exe)] + sys.argv[1:] 126 | rc, _, _ = run(args, tee=True) 127 | sys.exit(rc) 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](http://img.shields.io/:license-MIT-blue.svg)](https://github.com/yugr/sortcheckxx/blob/master/LICENSE.txt) 2 | [![Build Status](https://github.com/yugr/sortcheckxx/actions/workflows/ci.yml/badge.svg)](https://github.com/yugr/sortcheckxx/actions) 3 | [![codecov](https://codecov.io/gh/yugr/sortcheckxx/branch/master/graph/badge.svg)](https://codecov.io/gh/yugr/sortcheckxx) 4 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/yugr/sortcheckxx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/yugr/sortcheckxx/alerts/) 5 | [![Coverity Scan](https://scan.coverity.com/projects/yugr-sortcheckxx/badge.svg)](https://scan.coverity.com/projects/yugr-sortcheckxx) 6 | 7 | SortChecker++ verifies that comparators in C++ APIs like `std::sort` or `std::binary_search` 8 | satisfy the [Strict Weak Ordering](https://medium.com/@shiansu/strict-weak-ordering-and-the-c-stl-f7dcfa4d4e07) 9 | axioms. 10 | Violation of these axioms is undefined behavior and may lead to all sorts of runtime 11 | errors including [aborts](https://stackoverflow.com/questions/2441045/bewildering-segfault-involving-stl-sort-algorithm) 12 | (also [here](https://stackoverflow.com/questions/46670734/erratic-behavior-of-gccs-stdsort-with-lambdas), 13 | see [this answer](https://stackoverflow.com/a/24048654/2170527), 14 | [Qualys analysis](https://www.openwall.com/lists/oss-security/2024/01/30/7) and 15 | [my slides](https://github.com/yugr/CppRussia/blob/master/2023/EN.pdf) for explanations). 16 | 17 | SortChecker++ is an extension of [SortChecker](https://github.com/yugr/sortcheck) tool which does similar job to C sorting APIs. 18 | 19 | The tool has been tested on LLVM 6.0 (Ubuntu 18.04), 10.0 (Ubuntu 20.04) and 14.0 (Ubuntu 22.04). 20 | 21 | List of found issues: 22 | * [Libosmium: Missing prepare\_for\_lookup in test\_members\_database.cpp](https://github.com/osmcode/libosmium/issues/351) (fixed) 23 | * [ZeroC Ice: Unsorted array in lookupKwd](https://github.com/zeroc-ice/ice/issues/1362) (fixed) 24 | * [GiNaC: Potential issue in comparator in test\_antipode](https://www.ginac.de/pipermail/ginac-list/2022-June/002390.html) (fixed) 25 | * ArrayFire: Irreflexive comparator (already [fixed](https://github.com/arrayfire/arrayfire/commit/77181f1d9c860144554cd61e4de69b9dd82ccad9) in latest) 26 | * Giac: Non-asymmetric comparator in polynome\_less (already [fixed](https://github.com/geogebra/giac/commit/efbde32d614aed9833903f93084f76bbf61cf418) in latest) 27 | 28 | Note that some (but not all!) errors found by Sortchecker++ can also be found via [`-D_LIBCPP_DEBUG_STRICT_WEAK_ORDERING_CHECK`](https://reviews.llvm.org/D150264). 29 | 30 | # How to build 31 | 32 | To use, first install dependencies: 33 | ``` 34 | $ sudo apt install libclang-dev llvm-dev 35 | ``` 36 | and then build the tool 37 | ``` 38 | $ make clean all 39 | ``` 40 | 41 | # How to use 42 | 43 | SortChecker works by instrumenting the input file, i.e. inserting additional checking code into it. 44 | You can run it manually via 45 | ``` 46 | $ SortChecker file.cpp -- $CXXFLAGS 47 | ``` 48 | then compile via 49 | ``` 50 | $ g++ file.cpp $CXXFLAGS -Ipath/to/sortcheck.h 51 | ``` 52 | 53 | Finally run the resulting executable and it will report any errors e.g. 54 | ``` 55 | $ ./a.out 56 | sortcheck: file.cpp:23: non-asymmetric comparator at positions 1 and 0 57 | ``` 58 | (you'll probably want to combine this with some kind of regression 59 | or random/fuzz testing to achieve good coverage, 60 | also see the `SORTCHECK_SHUFFLE` option below). 61 | 62 | You could also use compiler wrappers in `scripts/` folder to combine instrumentation and compilation: 63 | ``` 64 | $ PATH=path/to/scripts:$PATH make clean all 65 | ``` 66 | 67 | Instrumented program may be controlled with environment variables: 68 | * `SORTCHECK_VERBOSE=N` - verbosity (`N` is an integer) 69 | * `SORTCHECK_SYSLOG=1` - dump messages to syslog (in addition to stderr) 70 | * `SORTCHECK_ABORT_ON_ERROR=1` - call `abort()` on detected error 71 | * `SORTCHECK_EXIT_CODE=N` - call `exit(CODE)` on detected error (`N` is an integer) 72 | * `SORTCHECK_OUTPUT=path/to/logfile` - write detected errors to file instead of stdout 73 | * `SORTCHECK_CHECKS=mask` - set which checks are enabled via bitmask 74 | (e.g. `mask=0xfffe` would disable the generally uninteresting irreflexivity checks) 75 | * `SORTCHECK_SHUFFLE=val` - reshuffle containers before checking with given seed; 76 | a value of `rand` will use random seed 77 | (helps to find bugs which are not located at start of array) 78 | 79 | # Interpreting the error messages 80 | 81 | tbd 82 | -------------------------------------------------------------------------------- /src/SortChecker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #include "clang/Tooling/CommonOptionsParser.h" 7 | #include "clang/Tooling/Tooling.h" 8 | 9 | #include "clang/Rewrite/Core/Rewriter.h" 10 | 11 | #include "clang/Frontend/CompilerInstance.h" 12 | #include "clang/Frontend/FrontendAction.h" 13 | 14 | #include "clang/AST/RecursiveASTVisitor.h" 15 | #include "clang/AST/Type.h" 16 | #include "clang/Basic/Version.inc" 17 | 18 | #include "llvm/ADT/StringSwitch.h" 19 | #include "llvm/Support/raw_ostream.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace clang; 27 | using namespace clang::driver; 28 | using namespace clang::tooling; 29 | 30 | // Compatibility layer 31 | #if CLANG_VERSION_MAJOR <= 6 32 | #define getBeginLoc getLocStart 33 | #define getEndLoc getLocEnd 34 | #endif 35 | 36 | #ifndef LLVM_NODISCARD 37 | #define LLVM_NODISCARD [[nodiscard]] 38 | #endif 39 | 40 | namespace { 41 | 42 | static llvm::cl::OptionCategory Category("SortChecker"); 43 | static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); 44 | static llvm::cl::extrahelp MoreHelp("\n\ 45 | SortChecker instruments input source files by replacing calls to compare-related APIs\n\ 46 | (like std::sort or std::binary_search) with their equivalents that check code for violations\n\ 47 | of Strict Weak Ordering axioms at runtime.\n"); 48 | 49 | llvm::cl::opt Verbose("v", llvm::cl::desc("Turn on verbose output")); 50 | llvm::cl::opt IgnoreParseErrors("ignore-parse-errors", llvm::cl::desc("Ignore parser errors")); 51 | 52 | class Visitor : public RecursiveASTVisitor { 53 | ASTContext &Ctx; 54 | Rewriter &RW; 55 | std::set ChangedFiles; 56 | 57 | Expr *skipImplicitCasts(Expr *E) const { 58 | while (auto *CE = dyn_cast(E)) { 59 | E = CE->getSubExpr(); 60 | } 61 | return E; 62 | } 63 | 64 | void replaceCallee(DeclRefExpr *DRE, llvm::StringRef Replacement) const { 65 | SourceRange Range = {DRE->getBeginLoc(), DRE->getEndLoc()}; 66 | RW.ReplaceText(Range, Replacement); 67 | } 68 | 69 | void appendLocationParams(CallExpr *E) const { 70 | SourceLocation Loc = E->getRParenLoc(); 71 | RW.InsertTextBefore(Loc, ", __FILE__, __LINE__"); 72 | } 73 | 74 | // Locate operator*() in D if it's a CXX class 75 | CXXMethodDecl *findOperator(CXXRecordDecl *RD, 76 | OverloadedOperatorKind OpKind) const { 77 | for (auto *Method : RD->methods()) { 78 | if (Method->getOverloadedOperator() == OpKind) { 79 | return Method; 80 | } 81 | } 82 | for (auto &Base : RD->bases()) { 83 | // TODO: consider access specifier? 84 | auto *BaseRD = Base.getType()->getAsCXXRecordDecl(); 85 | if (auto *MD = findOperator(BaseRD, OpKind)) 86 | return MD; 87 | } 88 | return nullptr; 89 | } 90 | 91 | bool isRandomAccessIterator(QualType Ty) const { 92 | if (isa(Ty.getTypePtr())) 93 | return true; 94 | if (auto *RD = Ty->getAsCXXRecordDecl()) 95 | return findOperator(RD, OO_Plus); 96 | return false; 97 | } 98 | 99 | QualType canonize(QualType Ty) const { 100 | return Ty->getCanonicalTypeInternal(); 101 | } 102 | 103 | // Return type of Ty's dereference 104 | QualType getDereferencedType(QualType Ty) const { 105 | if (auto *PTy = dyn_cast(Ty.getTypePtr())) { 106 | return PTy->getPointeeType(); 107 | } 108 | 109 | if (auto *RD = Ty->getAsCXXRecordDecl()) { 110 | if (auto *StarOp = findOperator(RD, OO_Star)) 111 | return canonize(StarOp->getReturnType()); 112 | } 113 | 114 | return QualType(); 115 | } 116 | 117 | Decl *getDecl(QualType Ty) const { 118 | if (auto *TypedefTy = dyn_cast(Ty.getTypePtr())) 119 | return TypedefTy->getDecl(); 120 | if (auto *TagTy = dyn_cast(Ty.getTypePtr())) 121 | return TagTy->getDecl(); 122 | return nullptr; 123 | } 124 | 125 | llvm::StringRef getRootNamespace(const Decl *D) const { 126 | llvm::StringRef RootNSName; 127 | for (auto *DC = D->getDeclContext(); DC; DC = DC->getParent()) 128 | if (const auto *NS = dyn_cast(DC); 129 | NS && NS->getIdentifier()) 130 | RootNSName = NS->getIdentifier()->getName(); 131 | return RootNSName; 132 | } 133 | 134 | bool isStdType(QualType Ty) const { 135 | Ty = dropReferences(Ty); 136 | if (auto *D = getDecl(Ty)) { 137 | return getRootNamespace(D) == "std"; 138 | } 139 | return false; 140 | } 141 | 142 | bool isStdLess(QualType Ty) const { 143 | if (auto *D = dyn_cast_or_null(getDecl(Ty))) { 144 | std::string S; 145 | llvm::raw_string_ostream OS(S); 146 | D->printQualifiedName(OS); 147 | return OS.str() == "std::less"; 148 | } 149 | return false; 150 | } 151 | 152 | bool isBuiltinType(QualType Ty) const { 153 | Ty = dropReferences(Ty); 154 | return isa(Ty); 155 | } 156 | 157 | QualType dropReferences(QualType Ty) const { 158 | while (auto *RTy = dyn_cast(Ty.getTypePtr())) { 159 | Ty = RTy->getPointeeType(); 160 | } 161 | return Ty; 162 | } 163 | 164 | bool areTypesCompatible(QualType HaystackTy, QualType NeedleTy) const { 165 | HaystackTy = dropReferences(HaystackTy); 166 | NeedleTy = dropReferences(NeedleTy); 167 | return HaystackTy.getTypePtr() == NeedleTy.getTypePtr(); 168 | } 169 | 170 | enum CompareFunction { 171 | CMP_FUNC_UNKNOWN = 0, 172 | CMP_FUNC_SORT, 173 | CMP_FUNC_STABLE_SORT, 174 | CMP_FUNC_BINARY_SEARCH, 175 | CMP_FUNC_LOWER_BOUND, 176 | CMP_FUNC_UPPER_BOUND, 177 | CMP_FUNC_EQUAL_RANGE, 178 | CMP_FUNC_MAX_ELEMENT, 179 | CMP_FUNC_MIN_ELEMENT, 180 | // TODO: other APIs from 181 | // https://en.cppreference.com/w/cpp/named_req/Compare 182 | CMP_FUNC_NUM 183 | }; 184 | 185 | LLVM_NODISCARD bool isKindOfBinarySearch(CompareFunction func) const { 186 | switch (func) { 187 | case CMP_FUNC_BINARY_SEARCH: 188 | case CMP_FUNC_LOWER_BOUND: 189 | case CMP_FUNC_UPPER_BOUND: 190 | case CMP_FUNC_EQUAL_RANGE: 191 | return true; 192 | default: 193 | return false; 194 | } 195 | } 196 | 197 | LLVM_NODISCARD bool isKindOfMaxElement(CompareFunction func) const { 198 | switch (func) { 199 | case CMP_FUNC_MAX_ELEMENT: 200 | case CMP_FUNC_MIN_ELEMENT: 201 | return true; 202 | default: 203 | return false; 204 | } 205 | } 206 | 207 | CompareFunction getCompareFunction(const std::string &Name) { 208 | return llvm::StringSwitch(Name) 209 | .Case("std::sort", CMP_FUNC_SORT) 210 | .Case("std::stable_sort", CMP_FUNC_STABLE_SORT) 211 | .Case("std::binary_search", CMP_FUNC_BINARY_SEARCH) 212 | .Case("std::lower_bound", CMP_FUNC_LOWER_BOUND) 213 | .Case("std::upper_bound", CMP_FUNC_UPPER_BOUND) 214 | .Case("std::equal_range", CMP_FUNC_EQUAL_RANGE) 215 | .Case("std::max_element", CMP_FUNC_MAX_ELEMENT) 216 | .Case("std::min_element", CMP_FUNC_MIN_ELEMENT) 217 | .Default(CMP_FUNC_UNKNOWN); 218 | } 219 | 220 | bool isBuiltinCompare(QualType KeyTy, bool HasDefaultCmp) const { 221 | return HasDefaultCmp && (isBuiltinType(KeyTy) || isStdType(KeyTy)); 222 | } 223 | 224 | bool canInstrument(SourceLocation Loc, SourceManager &SM) const { 225 | if (SM.isInSystemHeader(Loc)) 226 | return false; 227 | if (!Loc.isValid() || !Loc.isFileID()) 228 | return false; 229 | return true; 230 | } 231 | 232 | public: 233 | Visitor(ASTContext &Ctx, Rewriter &RW) : Ctx(Ctx), RW(RW) {} 234 | 235 | #if 0 236 | bool VisitExpr(Expr *E) { 237 | llvm::errs() << "Expr at "; 238 | E->getExprLoc().dump(Ctx.getSourceManager()); 239 | E->dump(); 240 | return true; 241 | } 242 | #endif 243 | 244 | bool VisitCallExpr(CallExpr *E) { 245 | auto &SM = Ctx.getSourceManager(); 246 | auto Loc = E->getExprLoc(); 247 | if (!canInstrument(Loc, SM)) 248 | return true; 249 | 250 | auto *Callee = skipImplicitCasts(E->getCallee()); 251 | if (auto *DRE = dyn_cast(Callee)) { 252 | std::string S; 253 | llvm::raw_string_ostream OS(S); 254 | DRE->getDecl()->printQualifiedName(OS); 255 | 256 | auto LocStr = Loc.printToString(SM); 257 | if (Verbose) { 258 | llvm::errs() << "Found call to " << OS.str() << " at " << LocStr 259 | << '\n'; 260 | } 261 | 262 | do { 263 | auto CmpFunc = getCompareFunction(OS.str()); 264 | if (!CmpFunc) 265 | break; 266 | 267 | if (Verbose) { 268 | llvm::errs() << "Found relevant function " << OS.str() << "() at " 269 | << LocStr << ":\n"; 270 | Callee->dump(); 271 | } 272 | 273 | static struct { 274 | const char *WrapperName; 275 | unsigned NumArgs; 276 | } CompareFunctionInfo[CMP_FUNC_NUM] = { 277 | {nullptr, 0}, 278 | {"sortcheck::sort_checked", 2}, 279 | {"sortcheck::stable_sort_checked", 2}, 280 | {"sortcheck::binary_search_checked", 3}, 281 | {"sortcheck::lower_bound_checked", 3}, 282 | {"sortcheck::upper_bound_checked", 3}, 283 | {"sortcheck::equal_range_checked", 3}, 284 | {"sortcheck::max_element_checked", 2}, 285 | {"sortcheck::min_element_checked", 2}}; 286 | 287 | std::string WrapperName = CompareFunctionInfo[CmpFunc].WrapperName; 288 | 289 | auto IterTy = canonize(E->getArg(0)->getType()); 290 | auto DerefTy = canonize(getDereferencedType(IterTy)); 291 | 292 | const bool HasDefaultCmp = 293 | E->getNumArgs() == CompareFunctionInfo[CmpFunc].NumArgs; 294 | const bool IsBuiltinCompare = isBuiltinCompare(DerefTy, HasDefaultCmp); 295 | const bool IsRandomAccess = isRandomAccessIterator(IterTy); 296 | 297 | std::optional CheckRangeFlag; 298 | if (isKindOfBinarySearch(CmpFunc)) { 299 | // Enable additional checks if typeof(*__first) == _Tp 300 | auto ValueTy = canonize(E->getArg(2)->getType()); 301 | if (IsRandomAccess && areTypesCompatible(ValueTy, DerefTy)) { 302 | WrapperName += "_full"; 303 | CheckRangeFlag = !IsBuiltinCompare; 304 | } 305 | } else if (isKindOfMaxElement(CmpFunc)) { 306 | if (IsBuiltinCompare || !IsRandomAccess) 307 | break; 308 | } else if (IsBuiltinCompare) { 309 | // Do not instrument std::sort for primitive types 310 | break; 311 | } 312 | 313 | replaceCallee(DRE, WrapperName); 314 | appendLocationParams(E); 315 | ChangedFiles.insert(SM.getFileID(Loc)); 316 | 317 | if (CheckRangeFlag) { 318 | SourceLocation Loc = E->getRParenLoc(); 319 | RW.InsertTextBefore(Loc, *CheckRangeFlag ? ", true" : ", false"); 320 | } 321 | } while (0); 322 | } 323 | return true; 324 | } 325 | 326 | const std::set &getChangedFiles() const { return ChangedFiles; } 327 | }; 328 | 329 | class Consumer : public ASTConsumer { 330 | public: 331 | Consumer(CompilerInstance &) {} 332 | 333 | void HandleTranslationUnit(ASTContext &Ctx) override { 334 | auto &SM = Ctx.getSourceManager(); 335 | Rewriter RW(SM, Ctx.getLangOpts()); 336 | 337 | // Replace calls 338 | Visitor V(Ctx, RW); 339 | V.TraverseDecl(Ctx.getTranslationUnitDecl()); 340 | 341 | // Insert includes 342 | for (auto &FID : V.getChangedFiles()) { 343 | auto Loc = SM.getLocForStartOfFile(FID); 344 | RW.InsertText(Loc, "#include \n"); 345 | } 346 | 347 | // Modify files 348 | RW.overwriteChangedFiles(); 349 | } 350 | }; 351 | 352 | class InstrumentingAction : public ASTFrontendAction { 353 | public: 354 | std::unique_ptr 355 | CreateASTConsumer(CompilerInstance &CI, 356 | LLVM_ATTRIBUTE_UNUSED llvm::StringRef InFile) override { 357 | if (IgnoreParseErrors) 358 | CI.getDiagnostics().setClient(new IgnoringDiagConsumer()); 359 | return std::make_unique(CI); 360 | } 361 | 362 | void PrintHelp(llvm::raw_ostream &OS) { OS << "TODO\n"; } 363 | }; 364 | 365 | } // namespace 366 | 367 | int main(int argc, const char **argv) { 368 | auto Op = CommonOptionsParser::create(argc, argv, Category, llvm::cl::OneOrMore); 369 | ClangTool Tool(Op->getCompilations(), Op->getSourcePathList()); 370 | return Tool.run(newFrontendActionFactory().get()); 371 | } 372 | -------------------------------------------------------------------------------- /include/sortcheck.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2024 Yury Gribov 2 | // 3 | // Use of this source code is governed by MIT license that can be 4 | // found in the LICENSE.txt file. 5 | 6 | #ifndef SORTCHECK_H 7 | #define SORTCHECK_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // Alas, syslog.h defines very popular symbols like LOG_ERROR 25 | // so we can't include it 26 | extern "C" void syslog(int __pri, const char *__fmt, ...); 27 | 28 | namespace sortcheck { 29 | 30 | #if __cplusplus >= 201100L 31 | #define SORTCHECK_NOEXCEPT(expr) noexcept(expr) 32 | #else 33 | #define SORTCHECK_NOEXCEPT(expr) 34 | #endif 35 | 36 | enum { SORTCHECK_LESS = -1, SORTCHECK_EQUAL = 0, SORTCHECK_GREATER = 1 }; 37 | 38 | struct Compare { 39 | template 40 | bool operator()(const A &lhs, const B &rhs) const 41 | SORTCHECK_NOEXCEPT(noexcept(lhs < rhs)) { 42 | return lhs < rhs; 43 | } 44 | }; 45 | 46 | struct Options { 47 | bool abort; 48 | int verbose; 49 | bool syslog; 50 | int exit_code; 51 | int out; 52 | unsigned long checks; 53 | unsigned shuffle; 54 | }; 55 | 56 | // SORTCHECK_CHECKS bits 57 | #define SORTCHECK_CHECK_REFLEXIVITY (1 << 0) 58 | #define SORTCHECK_CHECK_SYMMETRY (1 << 1) 59 | #define SORTCHECK_CHECK_TRANSITIVITY (1 << 2) 60 | #define SORTCHECK_CHECK_SORTED (1 << 3) 61 | #define SORTCHECK_CHECK_ORDERED (1 << 4) 62 | 63 | inline const Options &get_options() { 64 | static Options opts; 65 | static bool opts_initialized; 66 | if (!opts_initialized) { 67 | const char *verbose = getenv("SORTCHECK_VERBOSE"); 68 | opts.verbose = verbose ? atoi(verbose) : 0; 69 | 70 | const char *slog = getenv("SORTCHECK_SYSLOG"); 71 | opts.syslog = slog ? atoi(slog) : 0; 72 | 73 | const char *abrt = getenv("SORTCHECK_ABORT"); 74 | opts.abort = abrt ? atoi(abrt) : 1; 75 | 76 | const char *exit_code = getenv("SORTCHECK_EXIT_CODE"); 77 | opts.exit_code = exit_code ? atoi(exit_code) : 1; 78 | 79 | if (const char *checks = getenv("SORTCHECK_CHECKS")) { 80 | const bool is_binary = 81 | checks && checks[0] == '0' && (checks[1] == 'b' || checks[1] == 'B'); 82 | opts.checks = strtoul(checks, (char **)0, is_binary ? 2 : 0); 83 | if (!opts.checks) { 84 | std::cerr << "sortcheck: all checks disabled in SORTCHECK_CHECKS\n"; 85 | } 86 | } else { 87 | opts.checks = ~0ul; 88 | } 89 | 90 | if (const char *out = getenv("SORTCHECK_OUTPUT")) { 91 | opts.out = open(out, O_WRONLY | O_CREAT | O_APPEND, 0777); 92 | if (opts.out < 0) { 93 | std::cerr << "sortcheck: failed to open " << out << " (errno " << errno 94 | << ")\n"; 95 | abort(); 96 | } 97 | } else { 98 | opts.out = STDOUT_FILENO; 99 | } 100 | 101 | if (const char *shuffle = getenv("SORTCHECK_SHUFFLE")) { 102 | if (strcmp(shuffle, "rand") == 0 || strcmp(shuffle, "random") == 0) { 103 | opts.shuffle = rand(); 104 | } else { 105 | opts.shuffle = atoi(shuffle); 106 | } 107 | } else { 108 | opts.shuffle = UINT_MAX; // Disable 109 | } 110 | 111 | opts_initialized = true; 112 | } 113 | return opts; 114 | } 115 | 116 | inline void report_error(const std::string &msg, const Options &opts) { 117 | // LOG_ERR==3 from syslog.h conflicts with some packages 118 | if (opts.syslog) 119 | syslog(3, "%s", msg.c_str()); 120 | 121 | char c = '\n'; 122 | if (write(opts.out, msg.c_str(), msg.size()) >= 0 && 123 | write(opts.out, &c, 1) >= 0) { 124 | fsync(opts.out); 125 | } else { 126 | std::cerr << "sortcheck: failed to write to " << opts.out << " (errno " 127 | << errno << ")\n"; 128 | abort(); 129 | } 130 | 131 | if (opts.abort) { 132 | close(opts.out); 133 | abort(); 134 | } 135 | 136 | if (opts.exit_code) 137 | exit(opts.exit_code); 138 | } 139 | 140 | template 141 | inline void shuffle(_RandomAccessIterator __first, 142 | _RandomAccessIterator __last) { 143 | unsigned &seed = const_cast(get_options().shuffle); // FIXME 144 | size_t n = __last - __first; 145 | for (_RandomAccessIterator lhs = __first; lhs != __last; ++lhs) { 146 | _RandomAccessIterator rhs = __first + seed % n; 147 | seed = seed * 1664525u + 1013904223u; 148 | std::swap(*lhs, *rhs); 149 | } 150 | } 151 | 152 | template 153 | inline void check_range(_RandomAccessIterator __first, 154 | _RandomAccessIterator __last, _Compare __comp, 155 | const char *file, int line) { 156 | const Options &opts = get_options(); 157 | 158 | signed char cmp[32u][32u]; 159 | const size_t n = 160 | std::min(size_t(__last - __first), sizeof(cmp) / sizeof(cmp[0])); 161 | for (size_t i = 0; i < n; ++i) { 162 | for (size_t j = 0; j < n; ++j) { 163 | cmp[i][j] = __comp(*(__first + i), *(__first + j)) ? SORTCHECK_LESS 164 | : SORTCHECK_GREATER; 165 | } 166 | } 167 | 168 | for (size_t i = 0; i < n; ++i) { 169 | for (size_t j = 0; j <= i; ++j) { 170 | if (cmp[i][j] == SORTCHECK_GREATER && cmp[j][i] == SORTCHECK_GREATER) 171 | cmp[i][j] = cmp[j][i] = SORTCHECK_EQUAL; 172 | } 173 | } 174 | 175 | if (opts.checks & SORTCHECK_CHECK_REFLEXIVITY) { 176 | for (size_t i = 0; i < n; ++i) { 177 | if (cmp[i][i] != SORTCHECK_EQUAL) { 178 | std::ostringstream os; 179 | os << "sortcheck: " << file << ':' << line << ": " 180 | << "reflexive comparator at position " << i; 181 | report_error(os.str(), opts); 182 | } 183 | } 184 | } 185 | 186 | if (opts.checks & SORTCHECK_CHECK_SYMMETRY) { 187 | for (size_t i = 0; i < n; ++i) { 188 | for (size_t j = 0; j < i; ++j) { 189 | if (cmp[i][j] != -cmp[j][i]) { 190 | std::ostringstream os; 191 | os << "sortcheck: " << file << ':' << line << ": " 192 | << "non-asymmetric comparator at positions " << i << " and " << j; 193 | report_error(os.str(), opts); 194 | } 195 | } 196 | } 197 | } 198 | 199 | if (opts.checks & SORTCHECK_CHECK_TRANSITIVITY) { 200 | for (size_t i = 0; i < n; ++i) { 201 | for (size_t j = 0; j < i; ++j) { 202 | for (size_t k = 0; k < n; ++k) { 203 | if (cmp[i][j] == cmp[j][k] && cmp[i][k] != cmp[i][j]) { 204 | std::ostringstream os; 205 | os << "sortcheck: " << file << ':' << line << ": " 206 | << "non-transitive " << (cmp[i][j] ? "" : "equivalent ") 207 | << "comparator at positions " << i << ", " << j << " and " << k; 208 | report_error(os.str(), opts); 209 | } 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | template 217 | inline void check_sorted(_ForwardIterator __first, _ForwardIterator __last, 218 | _Compare __comp, const char *file, int line) { 219 | const Options &opts = get_options(); 220 | if (!(opts.checks & SORTCHECK_CHECK_SORTED) || __first == __last) 221 | return; 222 | 223 | unsigned pos = 0; 224 | for (_ForwardIterator cur = __first, prev = cur++; cur != __last; 225 | ++prev, ++cur, ++pos) { 226 | if (__comp(*cur, *prev)) { 227 | std::ostringstream os; 228 | os << "sortcheck: " << file << ':' << line << ": " 229 | << "unsorted range at position " << pos; 230 | report_error(os.str(), opts); 231 | } 232 | } 233 | } 234 | 235 | template 236 | inline void check_ordered(_ForwardIterator __first, _ForwardIterator __last, 237 | _Compare __comp, const _Tp &__val, const char *file, 238 | int line) { 239 | const Options &opts = get_options(); 240 | if (!(opts.checks & SORTCHECK_CHECK_ORDERED) || __first == __last) 241 | return; 242 | 243 | int prev = SORTCHECK_LESS; 244 | unsigned pos = 0; 245 | for (_ForwardIterator it = __first; it != __last; ++it, ++pos) { 246 | const int dir = __comp(*it, __val) ? SORTCHECK_LESS 247 | : __comp(__val, *it) ? SORTCHECK_GREATER 248 | : SORTCHECK_EQUAL; 249 | if (dir < prev) { 250 | std::ostringstream os; 251 | os << "sortcheck: " << file << ':' << line << ": unsorted range " 252 | << "at position " << pos; 253 | report_error(os.str(), opts); 254 | } 255 | prev = dir; 256 | } 257 | } 258 | 259 | // A simpler version of check_ordered when presense of __comp(__val, *iter) 260 | // is not guaranteed (e.g. in std::lower_bound). 261 | template 262 | inline void check_ordered_simple(_ForwardIterator __first, 263 | _ForwardIterator __last, _Compare __comp, 264 | const _Tp &__val, const char *file, int line) { 265 | const Options &opts = get_options(); 266 | if (!(opts.checks & SORTCHECK_CHECK_ORDERED) || __first == __last) 267 | return; 268 | 269 | int prev = SORTCHECK_LESS; 270 | unsigned pos = 0; 271 | for (_ForwardIterator it = __first; it != __last; ++it, ++pos) { 272 | const int dir = __comp(*it, __val) ? SORTCHECK_LESS : SORTCHECK_GREATER; 273 | if (dir < prev) { 274 | std::ostringstream os; 275 | os << "sortcheck: " << file << ':' << line << ": unsorted range " 276 | << "at position " << pos; 277 | report_error(os.str(), opts); 278 | } 279 | prev = dir; 280 | } 281 | } 282 | 283 | // binary_search overloads 284 | 285 | template 286 | inline bool binary_search_checked(_ForwardIterator __first, 287 | _ForwardIterator __last, const _Tp &__val, 288 | _Compare __comp, const char *file, int line) { 289 | check_ordered(__first, __last, __comp, __val, file, line); 290 | return std::binary_search(__first, __last, __val, __comp); 291 | } 292 | 293 | template 294 | inline bool binary_search_checked(_ForwardIterator __first, 295 | _ForwardIterator __last, const _Tp &__val, 296 | const char *file, int line) { 297 | return binary_search_checked(__first, __last, __val, Compare(), file, line); 298 | } 299 | 300 | template 301 | inline bool 302 | binary_search_checked_full(_ForwardIterator __first, _ForwardIterator __last, 303 | const _Tp &__val, _Compare __comp, 304 | bool do_check_range, const char *file, int line) { 305 | if (do_check_range) 306 | check_range(__first, __last, __comp, file, line); 307 | check_sorted(__first, __last, __comp, file, line); 308 | return binary_search_checked(__first, __last, __val, __comp, file, line); 309 | } 310 | 311 | template 312 | inline bool binary_search_checked_full(_ForwardIterator __first, 313 | _ForwardIterator __last, 314 | const _Tp &__val, bool do_check_range, 315 | const char *file, int line) { 316 | return binary_search_checked_full(__first, __last, __val, Compare(), 317 | do_check_range, file, line); 318 | } 319 | 320 | // lower_bound overloads 321 | 322 | template 323 | inline _ForwardIterator lower_bound_checked(_ForwardIterator __first, 324 | _ForwardIterator __last, 325 | const _Tp &__val, _Compare __comp, 326 | const char *file, int line) { 327 | check_ordered_simple(__first, __last, __comp, __val, file, line); 328 | return std::lower_bound(__first, __last, __val, __comp); 329 | } 330 | 331 | template 332 | inline _ForwardIterator 333 | lower_bound_checked(_ForwardIterator __first, _ForwardIterator __last, 334 | const _Tp &__val, const char *file, int line) { 335 | return lower_bound_checked(__first, __last, __val, Compare(), file, line); 336 | } 337 | 338 | template 339 | inline _ForwardIterator 340 | lower_bound_checked_full(_ForwardIterator __first, _ForwardIterator __last, 341 | const _Tp &__val, _Compare __comp, bool do_check_range, 342 | const char *file, int line) { 343 | if (do_check_range) 344 | check_range(__first, __last, __comp, file, line); 345 | check_sorted(__first, __last, __comp, file, line); 346 | return lower_bound_checked(__first, __last, __val, __comp, file, line); 347 | } 348 | 349 | template 350 | inline _ForwardIterator 351 | lower_bound_checked_full(_ForwardIterator __first, _ForwardIterator __last, 352 | const _Tp &__val, bool do_check_range, 353 | const char *file, int line) { 354 | return lower_bound_checked_full(__first, __last, __val, Compare(), 355 | do_check_range, file, line); 356 | } 357 | 358 | // upper_bound overloads 359 | 360 | template struct CompareSwapped { 361 | Compare comp; 362 | CompareSwapped(Compare c) : comp(c) {} 363 | template bool operator()(A a, B b) { 364 | return !comp(b, a); 365 | } 366 | }; 367 | 368 | template 369 | inline _ForwardIterator upper_bound_checked(_ForwardIterator __first, 370 | _ForwardIterator __last, 371 | const _Tp &__val, _Compare __comp, 372 | const char *file, int line) { 373 | CompareSwapped<_Compare> __comp_swapped(__comp); 374 | check_ordered_simple(__first, __last, __comp_swapped, __val, file, line); 375 | return std::upper_bound(__first, __last, __val, __comp); 376 | } 377 | 378 | template 379 | inline _ForwardIterator 380 | upper_bound_checked(_ForwardIterator __first, _ForwardIterator __last, 381 | const _Tp &__val, const char *file, int line) { 382 | return upper_bound_checked(__first, __last, __val, Compare(), file, line); 383 | } 384 | 385 | template 386 | inline _ForwardIterator 387 | upper_bound_checked_full(_ForwardIterator __first, _ForwardIterator __last, 388 | const _Tp &__val, _Compare __comp, bool do_check_range, 389 | const char *file, int line) { 390 | if (do_check_range) 391 | check_range(__first, __last, __comp, file, line); 392 | check_sorted(__first, __last, __comp, file, line); 393 | return upper_bound_checked(__first, __last, __val, __comp, file, line); 394 | } 395 | 396 | template 397 | inline _ForwardIterator 398 | upper_bound_checked_full(_ForwardIterator __first, _ForwardIterator __last, 399 | const _Tp &__val, bool do_check_range, 400 | const char *file, int line) { 401 | return upper_bound_checked_full(__first, __last, __val, Compare(), 402 | do_check_range, file, line); 403 | } 404 | 405 | // equal_range overloads 406 | 407 | template 408 | inline std::pair<_ForwardIterator, _ForwardIterator> 409 | equal_range_checked(_ForwardIterator __first, _ForwardIterator __last, 410 | const _Tp &__val, _Compare __comp, const char *file, 411 | int line) { 412 | check_ordered_simple(__first, __last, __comp, __val, file, line); 413 | return std::equal_range(__first, __last, __val, __comp); 414 | } 415 | 416 | template 417 | inline std::pair<_ForwardIterator, _ForwardIterator> 418 | equal_range_checked(_ForwardIterator __first, _ForwardIterator __last, 419 | const _Tp &__val, const char *file, int line) { 420 | return equal_range_checked(__first, __last, __val, Compare(), file, line); 421 | } 422 | 423 | template 424 | inline std::pair<_ForwardIterator, _ForwardIterator> 425 | equal_range_checked_full(_ForwardIterator __first, _ForwardIterator __last, 426 | const _Tp &__val, _Compare __comp, bool do_check_range, 427 | const char *file, int line) { 428 | if (do_check_range) 429 | check_range(__first, __last, __comp, file, line); 430 | check_sorted(__first, __last, __comp, file, line); 431 | return equal_range_checked(__first, __last, __val, __comp, file, line); 432 | } 433 | 434 | template 435 | inline std::pair<_ForwardIterator, _ForwardIterator> 436 | equal_range_checked_full(_ForwardIterator __first, _ForwardIterator __last, 437 | const _Tp &__val, bool do_check_range, 438 | const char *file, int line) { 439 | return equal_range_checked_full(__first, __last, __val, Compare(), 440 | do_check_range, file, line); 441 | } 442 | 443 | // sort overloads 444 | 445 | template 446 | inline void sort_checked(_RandomAccessIterator __first, 447 | _RandomAccessIterator __last, _Compare __comp, 448 | const char *file, int line) { 449 | const Options &opts = get_options(); 450 | if (opts.shuffle != UINT_MAX) 451 | shuffle(__first, __last); 452 | check_range(__first, __last, __comp, file, line); 453 | std::sort(__first, __last, __comp); 454 | } 455 | 456 | template 457 | inline void sort_checked(_RandomAccessIterator __first, 458 | _RandomAccessIterator __last, const char *file, 459 | int line) { 460 | sort_checked(__first, __last, Compare(), file, line); 461 | } 462 | 463 | // stable_sort overloads 464 | 465 | template 466 | inline void stable_sort_checked(_RandomAccessIterator __first, 467 | _RandomAccessIterator __last, _Compare __comp, 468 | const char *file, int line) { 469 | check_range(__first, __last, __comp, file, line); 470 | std::stable_sort(__first, __last, __comp); 471 | } 472 | 473 | template 474 | inline void stable_sort_checked(_RandomAccessIterator __first, 475 | _RandomAccessIterator __last, const char *file, 476 | int line) { 477 | stable_sort_checked(__first, __last, Compare(), file, line); 478 | } 479 | 480 | // max_element overloads 481 | 482 | template 483 | inline _RandomAccessIterator 484 | max_element_checked(_RandomAccessIterator __first, _RandomAccessIterator __last, 485 | _Compare __comp, const char *file, int line) { 486 | check_range(__first, __last, __comp, file, line); 487 | return std::max_element(__first, __last, __comp); 488 | } 489 | 490 | template 491 | inline _RandomAccessIterator max_element_checked(_RandomAccessIterator __first, 492 | _RandomAccessIterator __last, 493 | const char *file, int line) { 494 | return max_element_checked(__first, __last, Compare(), file, line); 495 | } 496 | 497 | // min_element overloads 498 | 499 | template 500 | inline _RandomAccessIterator 501 | min_element_checked(_RandomAccessIterator __first, _RandomAccessIterator __last, 502 | _Compare __comp, const char *file, int line) { 503 | check_range(__first, __last, __comp, file, line); 504 | return std::min_element(__first, __last, __comp); 505 | } 506 | 507 | template 508 | inline _RandomAccessIterator min_element_checked(_RandomAccessIterator __first, 509 | _RandomAccessIterator __last, 510 | const char *file, int line) { 511 | return min_element_checked(__first, __last, Compare(), file, line); 512 | } 513 | 514 | // std::map/set checks 515 | 516 | template struct ComparePointers { 517 | Compare comp; 518 | ComparePointers(Compare c) : comp(c) {} 519 | template bool operator()(A *a, B *b) { 520 | return comp(*a, *b); 521 | } 522 | }; 523 | 524 | template void check_map(Map *m, const char *file, int line) { 525 | std::vector keys; 526 | for (typename Map::iterator i = m->begin(), end = m->end(); i != end; ++i) 527 | keys.push_back(&i->first); 528 | const Options &opts = get_options(); 529 | if (opts.shuffle != UINT_MAX) 530 | shuffle(keys.begin(), keys.end()); 531 | check_range(keys.begin(), keys.end(), ComparePointers(m->key_comp()), file, line); 532 | } 533 | 534 | template void check_set(Set *m, const char *file, int line) { 535 | std::vector keys; 536 | for (typename Set::iterator i = m->begin(), end = m->end(); i != end; ++i) 537 | keys.push_back(&*i); 538 | const Options &opts = get_options(); 539 | if (opts.shuffle != UINT_MAX) 540 | shuffle(keys.begin(), keys.end()); 541 | check_range(keys.begin(), keys.end(), ComparePointers(m->key_comp()), file, line); 542 | } 543 | 544 | } // namespace sortcheck 545 | 546 | #endif 547 | --------------------------------------------------------------------------------